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) {
const stack = [root];
while (stack.length > 0) {
const node = stack.pop();
// don't store root node in map // don't store root node in map
if (node.root !== null) if (node.root !== null)
this.nodeMap[node.rowId] = node; this.nodeMap[node.rowId] = node;
node.children.each((child) => { stack.push(...node.children);
this.generateNodeMap(child); }
});
}, },
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;
while (stack.length > 0) {
const root = stack.at(-1);
if (root.isFolder) {
if (visited.at(-1) !== root) {
visited.push(root);
stack.push(...root.children);
continue;
}
visited.pop();
// process children
root.size = 0;
root.remaining = 0;
root.progress = 0;
root.availability = 0;
root.checked = TriState.Unchecked;
root.priority = FilePriority.Normal;
let isFirstFile = true; let isFirstFile = true;
this.children.each((node) => { for (const child of root.children) {
if (node.isFolder) root.size += child.size;
node.calculateSize();
size += node.size;
if (isFirstFile) { if (isFirstFile) {
priority = node.priority; root.priority = child.priority;
checked = node.checked; root.checked = child.checked;
isFirstFile = false; isFirstFile = false;
} }
else { else {
if (priority !== node.priority) if (root.priority !== child.priority)
priority = FilePriority.Mixed; root.priority = FilePriority.Mixed;
if (checked !== node.checked) if (root.checked !== child.checked)
checked = TriState.Partial; root.checked = TriState.Partial;
} }
const isIgnored = (node.priority === FilePriority.Ignored); const isIgnored = (child.priority === FilePriority.Ignored);
if (!isIgnored) { if (!isIgnored) {
remaining += node.remaining; root.remaining += child.remaining;
progress += (node.progress * node.size); root.progress += (child.progress * child.size);
availability += (node.availability * node.size); root.availability += (child.availability * child.size);
}
} }
});
this.size = size; root.checked = root.autoCheckFolders ? root.checked : TriState.Checked;
this.remaining = remaining; root.progress /= root.size;
this.checked = this.autoCheckFolders ? checked : TriState.Checked; root.availability /= root.size;
this.progress = (progress / size); }
this.priority = priority;
this.availability = (availability / size); stack.pop();
}
} }
}); });