From bb524450f09c10122836969427ad252020f11d0b Mon Sep 17 00:00:00 2001 From: ahjephson <16685186+ahjephson@users.noreply.github.com> Date: Sun, 19 Oct 2025 11:06:45 +0100 Subject: [PATCH] Fix slowness issues with FilesTab when torrents with large file lists are being rendered. --- Lantean.QBTMud/Components/FilesTab.razor.cs | 16 +- Lantean.QBTMud/Services/DataManager.cs | 160 +++++++++++++------- Lantean.QBTMud/Services/IDataManager.cs | 2 +- 3 files changed, 118 insertions(+), 60 deletions(-) diff --git a/Lantean.QBTMud/Components/FilesTab.razor.cs b/Lantean.QBTMud/Components/FilesTab.razor.cs index 23f198f..1883f65 100644 --- a/Lantean.QBTMud/Components/FilesTab.razor.cs +++ b/Lantean.QBTMud/Components/FilesTab.razor.cs @@ -8,6 +8,7 @@ using Lantean.QBTMud.Models; using Lantean.QBTMud.Services; using Microsoft.AspNetCore.Components; using MudBlazor; +using System; using System.Collections.ObjectModel; using System.Net; @@ -368,16 +369,16 @@ namespace Lantean.QBTMud.Components return FileList!.Values.Where(f => f.Name.StartsWith(contentItem.Name + Extensions.DirectorySeparator) && !f.IsFolder); } - private IEnumerable GetChildren(ContentItem folder, int level) + private IEnumerable GetChildren(ContentItem folder) { - level++; - var descendantsKey = folder.GetDescendantsKey(level); + var childLevel = folder.Level + 1; + var prefix = string.Concat(folder.Name, Extensions.DirectorySeparator); - foreach (var item in FileList!.Values.Where(f => f.Name.StartsWith(descendantsKey) && f.Level == level).OrderByDirection(_sortDirection, GetSortSelector())) + foreach (var item in FileList!.Values.Where(f => f.Level == childLevel && f.Name.StartsWith(prefix, StringComparison.Ordinal)).OrderByDirection(_sortDirection, GetSortSelector())) { if (item.IsFolder) { - var descendants = GetChildren(item, level); + var descendants = GetChildren(item); // if the filter returns some results then show folder item if (descendants.Any()) { @@ -451,8 +452,7 @@ namespace Lantean.QBTMud.Components if (item.IsFolder && ExpandedNodes.Contains(item.Name)) { - var level = 0; - var descendants = GetChildren(item, level); + var descendants = GetChildren(item); foreach (var descendant in descendants) { list.Add(descendant); @@ -552,4 +552,4 @@ namespace Lantean.QBTMud.Components ColumnDefinitionHelper.CreateColumnDefinition("Availability", c => c.Availability, c => c.Availability.ToString("0.00")), ]; } -} \ No newline at end of file +} diff --git a/Lantean.QBTMud/Services/DataManager.cs b/Lantean.QBTMud/Services/DataManager.cs index 7a5e8aa..a0dd518 100644 --- a/Lantean.QBTMud/Services/DataManager.cs +++ b/Lantean.QBTMud/Services/DataManager.cs @@ -625,82 +625,140 @@ namespace Lantean.QBTMud.Services public Dictionary CreateContentsList(IReadOnlyList files) { - var contents = new Dictionary(); + return BuildContentsTree(files); + } + + private static Dictionary BuildContentsTree(IReadOnlyList files) + { + var result = new Dictionary(); if (files.Count == 0) { - return contents; + return result; } var folderIndex = files.Min(f => f.Index) - 1; + var nodes = new Dictionary(files.Count * 2); + var root = new ContentTreeNode(null, null); foreach (var file in files) { - if (!file.Name.Contains(Extensions.DirectorySeparator)) + var parent = root; + string? parentPath = parent.Item?.Name; + + var segments = file.Name.Split(Extensions.DirectorySeparator); + var directoriesLength = segments.Length - 1; + + for (var i = 0; i < directoriesLength; i++) { - contents.Add(file.Name, new ContentItem(file.Name, file.Name, file.Index, (Priority)(int)file.Priority, file.Progress, file.Size, file.Availability)); - } - else - { - var nameAndPath = file.Name.Split(Extensions.DirectorySeparator); - var paths = nameAndPath[..^1]; - for (var i = 0; i < paths.Length; i++) + var folderName = segments[i]; + if (folderName == ".unwanted") { - var directoryName = paths[i]; - var directoryPath = string.Join(Extensions.DirectorySeparator, paths[0..(i + 1)]); - if (!contents.ContainsKey(directoryPath)) - { - contents.Add(directoryPath, new ContentItem(directoryPath, directoryName, folderIndex--, Priority.Normal, 0, 0, 0, true, i)); - } + continue; } - var displayName = nameAndPath[^1]; + var folderPath = string.IsNullOrEmpty(parentPath) + ? folderName + : string.Concat(parentPath, Extensions.DirectorySeparator, folderName); - contents.Add(file.Name, new ContentItem(file.Name, displayName, file.Index, (Priority)(int)file.Priority, file.Progress, file.Size, file.Availability, false, paths.Length)); + if (!nodes.TryGetValue(folderPath, out var folderNode)) + { + var level = (parent.Item?.Level ?? -1) + 1; + var folderItem = new ContentItem(folderPath, folderName, folderIndex--, Priority.Normal, 0, 0, 0, true, level); + folderNode = new ContentTreeNode(folderItem, parent); + nodes[folderPath] = folderNode; + parent.Children[folderPath] = folderNode; + } + + parent = folderNode; + parentPath = parent.Item!.Name; } + + var displayName = segments[^1]; + var fileLevel = (parent.Item?.Level ?? -1) + 1; + var fileItem = new ContentItem(file.Name, displayName, file.Index, (Priority)(int)file.Priority, file.Progress, file.Size, file.Availability, false, fileLevel); + var fileNode = new ContentTreeNode(fileItem, parent); + nodes[file.Name] = fileNode; + parent.Children[fileItem.Name] = fileNode; } - var directories = contents.Where(c => c.Value.IsFolder).OrderByDescending(c => c.Value.Level); + var folders = nodes.Values + .Where(n => n.Item is not null && n.Item.IsFolder) + .OrderByDescending(n => n.Item!.Level) + .ToList(); - foreach (var directory in directories) + foreach (var folder in folders) { - var key = directory.Key; - var level = directory.Value.Level; - var filesContents = contents.Where(c => c.Value.Name.StartsWith(key + Extensions.DirectorySeparator) && !c.Value.IsFolder).ToList(); - var directoriesContents = contents.Where(c => c.Value.Name.StartsWith(key + Extensions.DirectorySeparator) && c.Value.IsFolder && c.Value.Level == level + 1).ToList(); - var allContents = filesContents.Concat(directoriesContents); - var priorities = allContents.Select(d => d.Value.Priority).Distinct(); - var downloadingContents = allContents.Where(c => c.Value.Priority != Priority.DoNotDownload && !c.Value.IsFolder).ToList(); - - long size = 0; - float availability = 0; - long downloaded = 0; - float progress = 0; - if (downloadingContents.Count != 0) + var folderItem = folder.Item!; + if (folder.Children.Count == 0) { - size = downloadingContents.Sum(c => c.Value.Size); - availability = downloadingContents.Average(c => c.Value.Availability); - downloaded = downloadingContents.Sum(c => c.Value.Downloaded); - progress = (float)downloaded / size; + folderItem.Size = 0; + folderItem.Progress = 0; + folderItem.Availability = 0; + folderItem.Priority = Priority.Normal; + continue; } - if (!contents.TryGetValue(key, out var dir)) + long sizeSum = 0; + double progressSum = 0; + double availabilitySum = 0; + var firstChild = true; + var aggregatedPriority = Priority.Normal; + + foreach (var child in folder.Children.Values) + { + var childItem = child.Item!; + sizeSum += childItem.Size; + + if (firstChild) + { + aggregatedPriority = childItem.Priority; + firstChild = false; + } + else if (aggregatedPriority != childItem.Priority) + { + aggregatedPriority = Priority.Mixed; + } + + if (childItem.Priority != Priority.DoNotDownload) + { + progressSum += childItem.Progress * childItem.Size; + availabilitySum += childItem.Availability * childItem.Size; + } + } + + folderItem.Size = sizeSum; + folderItem.Progress = sizeSum > 0 ? (float)(progressSum / sizeSum) : 0; + folderItem.Availability = sizeSum > 0 ? (float)(availabilitySum / sizeSum) : 0; + folderItem.Priority = firstChild ? Priority.Normal : aggregatedPriority; + } + + foreach (var node in nodes.Values) + { + if (node.Item is null) { continue; } - dir.Availability = availability; - dir.Size = size; - dir.Progress = progress; - if (priorities.Count() == 1) - { - dir.Priority = priorities.First(); - } - else - { - dir.Priority = Priority.Mixed; - } + + result[node.Item.Name] = node.Item; } - return contents; + return result; + } + + private sealed class ContentTreeNode + { + public ContentTreeNode(ContentItem? item, ContentTreeNode? parent) + { + Item = item; + Parent = parent; + Children = new Dictionary(); + } + + public ContentItem? Item { get; } + + public ContentTreeNode? Parent { get; } + + public Dictionary Children { get; } } public QBitTorrentClient.Models.UpdatePreferences MergePreferences(QBitTorrentClient.Models.UpdatePreferences? original, QBitTorrentClient.Models.UpdatePreferences changed) @@ -1175,4 +1233,4 @@ namespace Lantean.QBTMud.Services return new RssList(feeds, articles); } } -} \ No newline at end of file +} diff --git a/Lantean.QBTMud/Services/IDataManager.cs b/Lantean.QBTMud/Services/IDataManager.cs index 0463ec6..ddecdfb 100644 --- a/Lantean.QBTMud/Services/IDataManager.cs +++ b/Lantean.QBTMud/Services/IDataManager.cs @@ -22,4 +22,4 @@ namespace Lantean.QBTMud.Services RssList CreateRssList(IReadOnlyDictionary rssItems); } -} \ No newline at end of file +}