WebUI: migrate away from recursion calls

Browsers have limited recursion depth about ~10000.

PR #22580.
This commit is contained in:
Chocobo1 2025-04-21 17:21:18 +08:00 committed by GitHub
commit 0187f19f60
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -72,14 +72,17 @@ window.qBittorrent.FileTree ??= (() => {
return this.root; return this.root;
}, },
generateNodeMap: function(node) { generateNodeMap: function(root) {
// don't store root node in map const stack = [root];
if (node.root !== null) while (stack.length > 0) {
this.nodeMap[node.rowId] = node; const node = stack.pop();
node.children.each((child) => { // don't store root node in map
this.generateNodeMap(child); if (node.root !== null)
}); this.nodeMap[node.rowId] = node;
stack.push(...node.children);
}
}, },
getNode: function(rowId) { getNode: function(rowId) {
@ -93,21 +96,17 @@ window.qBittorrent.FileTree ??= (() => {
}, },
/** /**
* Returns the nodes in dfs order * Returns the nodes in DFS in-order
*/ */
toArray: function() { toArray: function() {
const nodes = []; const ret = [];
this.root.children.each((node) => { const stack = this.root.children.toReversed();
this._getArrayOfNodes(node, nodes); while (stack.length > 0) {
}); const node = stack.pop();
return nodes; ret.push(node);
}, stack.push(...node.children.toReversed());
}
_getArrayOfNodes: function(node, array) { return ret;
array.push(node);
node.children.each((child) => {
this._getArrayOfNodes(child, array);
});
} }
}); });
@ -140,55 +139,68 @@ window.qBittorrent.FileTree ??= (() => {
this.isFolder = true; this.isFolder = true;
}, },
addChild(node) { addChild: function(node) {
this.children.push(node); this.children.push(node);
}, },
/** /**
* Recursively calculate size of node and its children * Calculate size of node and its children
*/ */
calculateSize: function() { calculateSize: function() {
let size = 0; const stack = [this];
let remaining = 0; const visited = [];
let progress = 0;
let availability = 0;
let checked = TriState.Unchecked;
let priority = FilePriority.Normal;
let isFirstFile = true; while (stack.length > 0) {
const root = stack.at(-1);
this.children.each((node) => { if (root.isFolder) {
if (node.isFolder) if (visited.at(-1) !== root) {
node.calculateSize(); visited.push(root);
stack.push(...root.children);
continue;
}
size += node.size; visited.pop();
if (isFirstFile) { // process children
priority = node.priority; root.size = 0;
checked = node.checked; root.remaining = 0;
isFirstFile = false; root.progress = 0;
} root.availability = 0;
else { root.checked = TriState.Unchecked;
if (priority !== node.priority) root.priority = FilePriority.Normal;
priority = FilePriority.Mixed; let isFirstFile = true;
if (checked !== node.checked)
checked = TriState.Partial; for (const child of root.children) {
root.size += child.size;
if (isFirstFile) {
root.priority = child.priority;
root.checked = child.checked;
isFirstFile = false;
}
else {
if (root.priority !== child.priority)
root.priority = FilePriority.Mixed;
if (root.checked !== child.checked)
root.checked = TriState.Partial;
}
const isIgnored = (child.priority === FilePriority.Ignored);
if (!isIgnored) {
root.remaining += child.remaining;
root.progress += (child.progress * child.size);
root.availability += (child.availability * child.size);
}
}
root.checked = root.autoCheckFolders ? root.checked : TriState.Checked;
root.progress /= root.size;
root.availability /= root.size;
} }
const isIgnored = (node.priority === FilePriority.Ignored); stack.pop();
if (!isIgnored) { }
remaining += node.remaining;
progress += (node.progress * node.size);
availability += (node.availability * node.size);
}
});
this.size = size;
this.remaining = remaining;
this.checked = this.autoCheckFolders ? checked : TriState.Checked;
this.progress = (progress / size);
this.priority = priority;
this.availability = (availability / size);
} }
}); });