mirror of
https://github.com/lantean-code/qbtmud.git
synced 2025-10-23 04:52:22 +00:00
Compare commits
4 Commits
bb524450f0
...
2c744cd972
Author | SHA1 | Date | |
---|---|---|---|
|
2c744cd972 | ||
|
b02bb7cfae | ||
|
e4dac8556e | ||
|
a9a8a4eba8 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -361,3 +361,4 @@ MigrationBackup/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
/output
|
||||
|
@@ -21,6 +21,9 @@ namespace Lantean.QBTMud.Components
|
||||
|
||||
private readonly CancellationTokenSource _timerCancellationToken = new();
|
||||
private bool _disposedValue;
|
||||
private static readonly ReadOnlyCollection<ContentItem> EmptyContentItems = new ReadOnlyCollection<ContentItem>(Array.Empty<ContentItem>());
|
||||
private ReadOnlyCollection<ContentItem> _visibleFiles = EmptyContentItems;
|
||||
private bool _filesDirty = true;
|
||||
|
||||
private List<PropertyFilterDefinition<ContentItem>>? _filterDefinitions;
|
||||
private readonly Dictionary<string, RenderFragment<RowContext<ContentItem>>> _columnRenderFragments = [];
|
||||
@@ -103,6 +106,7 @@ namespace Lantean.QBTMud.Components
|
||||
if (_filterDefinitions is null)
|
||||
{
|
||||
Filters = null;
|
||||
MarkFilesDirty();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -114,11 +118,13 @@ namespace Lantean.QBTMud.Components
|
||||
}
|
||||
|
||||
Filters = filters;
|
||||
MarkFilesDirty();
|
||||
}
|
||||
|
||||
protected void RemoveFilter()
|
||||
{
|
||||
Filters = null;
|
||||
MarkFilesDirty();
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
@@ -158,6 +164,7 @@ namespace Lantean.QBTMud.Components
|
||||
protected void SearchTextChanged(string value)
|
||||
{
|
||||
SearchText = value;
|
||||
MarkFilesDirty();
|
||||
}
|
||||
|
||||
protected Task TableDataContextMenu(TableDataContextMenuEventArgs<ContentItem> eventArgs)
|
||||
@@ -198,6 +205,7 @@ namespace Lantean.QBTMud.Components
|
||||
{
|
||||
while (!_timerCancellationToken.IsCancellationRequested && await timer.WaitForNextTickAsync())
|
||||
{
|
||||
var hasUpdates = false;
|
||||
if (Active && Hash is not null)
|
||||
{
|
||||
IReadOnlyList<QBitTorrentClient.Models.FileData> files;
|
||||
@@ -214,14 +222,20 @@ namespace Lantean.QBTMud.Components
|
||||
if (FileList is null)
|
||||
{
|
||||
FileList = DataManager.CreateContentsList(files);
|
||||
hasUpdates = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
DataManager.MergeContentsList(files, FileList);
|
||||
hasUpdates = DataManager.MergeContentsList(files, FileList);
|
||||
}
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
if (hasUpdates)
|
||||
{
|
||||
MarkFilesDirty();
|
||||
PruneSelectionIfMissing();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -247,6 +261,8 @@ namespace Lantean.QBTMud.Components
|
||||
|
||||
var contents = await ApiClient.GetTorrentContents(Hash);
|
||||
FileList = DataManager.CreateContentsList(contents);
|
||||
MarkFilesDirty();
|
||||
PruneSelectionIfMissing();
|
||||
|
||||
var expandedNodes = await LocalStorage.GetItemAsync<HashSet<string>>($"{_expandedNodesStorageKey}.{Hash}");
|
||||
if (expandedNodes is not null)
|
||||
@@ -257,6 +273,8 @@ namespace Lantean.QBTMud.Components
|
||||
{
|
||||
ExpandedNodes.Clear();
|
||||
}
|
||||
|
||||
MarkFilesDirty();
|
||||
}
|
||||
|
||||
protected async Task PriorityValueChanged(ContentItem contentItem, Priority priority)
|
||||
@@ -321,11 +339,13 @@ namespace Lantean.QBTMud.Components
|
||||
protected void SortColumnChanged(string sortColumn)
|
||||
{
|
||||
_sortColumn = sortColumn;
|
||||
MarkFilesDirty();
|
||||
}
|
||||
|
||||
protected void SortDirectionChanged(SortDirection sortDirection)
|
||||
{
|
||||
_sortDirection = sortDirection;
|
||||
MarkFilesDirty();
|
||||
}
|
||||
|
||||
protected void SelectedItemChanged(ContentItem item)
|
||||
@@ -344,6 +364,7 @@ namespace Lantean.QBTMud.Components
|
||||
ExpandedNodes.Add(contentItem.Name);
|
||||
}
|
||||
|
||||
MarkFilesDirty();
|
||||
await LocalStorage.SetItemAsync($"{_expandedNodesStorageKey}.{Hash}", ExpandedNodes);
|
||||
}
|
||||
|
||||
@@ -369,44 +390,6 @@ namespace Lantean.QBTMud.Components
|
||||
return FileList!.Values.Where(f => f.Name.StartsWith(contentItem.Name + Extensions.DirectorySeparator) && !f.IsFolder);
|
||||
}
|
||||
|
||||
private IEnumerable<ContentItem> GetChildren(ContentItem folder)
|
||||
{
|
||||
var childLevel = folder.Level + 1;
|
||||
var prefix = string.Concat(folder.Name, Extensions.DirectorySeparator);
|
||||
|
||||
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);
|
||||
// if the filter returns some results then show folder item
|
||||
if (descendants.Any())
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
|
||||
// if the folder is not expanded - don't return children
|
||||
if (!ExpandedNodes.Contains(item.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// then show children
|
||||
foreach (var descendant in descendants)
|
||||
{
|
||||
yield return descendant;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FilterContentItem(item))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool FilterContentItem(ContentItem item)
|
||||
{
|
||||
if (Filters is not null)
|
||||
@@ -430,37 +413,130 @@ namespace Lantean.QBTMud.Components
|
||||
}
|
||||
|
||||
private ReadOnlyCollection<ContentItem> GetFiles()
|
||||
{
|
||||
if (!_filesDirty)
|
||||
{
|
||||
return _visibleFiles;
|
||||
}
|
||||
|
||||
_visibleFiles = BuildVisibleFiles();
|
||||
_filesDirty = false;
|
||||
|
||||
return _visibleFiles;
|
||||
}
|
||||
|
||||
private ReadOnlyCollection<ContentItem> BuildVisibleFiles()
|
||||
{
|
||||
if (FileList is null || FileList.Values.Count == 0)
|
||||
{
|
||||
return new ReadOnlyCollection<ContentItem>([]);
|
||||
return EmptyContentItems;
|
||||
}
|
||||
|
||||
var maxLevel = FileList.Values.Max(f => f.Level);
|
||||
// this is a flat file structure
|
||||
if (maxLevel == 0)
|
||||
var lookup = BuildChildrenLookup();
|
||||
if (!lookup.TryGetValue(string.Empty, out var roots))
|
||||
{
|
||||
return FileList.Values.Where(FilterContentItem).OrderByDirection(_sortDirection, GetSortSelector()).ToList().AsReadOnly();
|
||||
return EmptyContentItems;
|
||||
}
|
||||
|
||||
var list = new List<ContentItem>();
|
||||
var sortSelector = GetSortSelector();
|
||||
var orderedRoots = roots.OrderByDirection(_sortDirection, sortSelector).ToList();
|
||||
var result = new List<ContentItem>(FileList.Values.Count);
|
||||
|
||||
var rootItems = FileList.Values.Where(c => c.Level == 0).OrderByDirection(_sortDirection, GetSortSelector()).ToList();
|
||||
foreach (var item in rootItems)
|
||||
foreach (var item in orderedRoots)
|
||||
{
|
||||
list.Add(item);
|
||||
|
||||
if (item.IsFolder && ExpandedNodes.Contains(item.Name))
|
||||
if (item.IsFolder)
|
||||
{
|
||||
var descendants = GetChildren(item);
|
||||
foreach (var descendant in descendants)
|
||||
result.Add(item);
|
||||
|
||||
if (!ExpandedNodes.Contains(item.Name))
|
||||
{
|
||||
list.Add(descendant);
|
||||
continue;
|
||||
}
|
||||
|
||||
var descendants = GetVisibleDescendants(item, lookup, sortSelector);
|
||||
result.AddRange(descendants);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FilterContentItem(item))
|
||||
{
|
||||
result.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list.AsReadOnly();
|
||||
return new ReadOnlyCollection<ContentItem>(result);
|
||||
}
|
||||
|
||||
private Dictionary<string, List<ContentItem>> BuildChildrenLookup()
|
||||
{
|
||||
var lookup = new Dictionary<string, List<ContentItem>>(FileList!.Count);
|
||||
|
||||
foreach (var item in FileList!.Values)
|
||||
{
|
||||
var parentPath = item.Level == 0 ? string.Empty : item.Name.GetDirectoryPath();
|
||||
if (!lookup.TryGetValue(parentPath, out var children))
|
||||
{
|
||||
children = [];
|
||||
lookup[parentPath] = children;
|
||||
}
|
||||
|
||||
children.Add(item);
|
||||
}
|
||||
|
||||
return lookup;
|
||||
}
|
||||
|
||||
private List<ContentItem> GetVisibleDescendants(ContentItem folder, Dictionary<string, List<ContentItem>> lookup, Func<ContentItem, object?> sortSelector)
|
||||
{
|
||||
if (!lookup.TryGetValue(folder.Name, out var children))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var orderedChildren = children.OrderByDirection(_sortDirection, sortSelector).ToList();
|
||||
var visible = new List<ContentItem>();
|
||||
|
||||
foreach (var child in orderedChildren)
|
||||
{
|
||||
if (child.IsFolder)
|
||||
{
|
||||
var descendants = GetVisibleDescendants(child, lookup, sortSelector);
|
||||
if (descendants.Count != 0)
|
||||
{
|
||||
visible.Add(child);
|
||||
|
||||
if (ExpandedNodes.Contains(child.Name))
|
||||
{
|
||||
visible.AddRange(descendants);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (FilterContentItem(child))
|
||||
{
|
||||
visible.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
return visible;
|
||||
}
|
||||
|
||||
private void MarkFilesDirty()
|
||||
{
|
||||
_filesDirty = true;
|
||||
}
|
||||
|
||||
private void PruneSelectionIfMissing()
|
||||
{
|
||||
if (SelectedItem is not null && (FileList is null || !FileList.ContainsKey(SelectedItem.Name)))
|
||||
{
|
||||
SelectedItem = null;
|
||||
}
|
||||
|
||||
if (ContextMenuItem is not null && (FileList is null || !FileList.ContainsKey(ContextMenuItem.Name)))
|
||||
{
|
||||
ContextMenuItem = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task DoNotDownloadLessThan100PercentAvailability()
|
||||
|
@@ -4,6 +4,7 @@ using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
using System;
|
||||
|
||||
namespace Lantean.QBTMud.Components.UI
|
||||
{
|
||||
@@ -81,6 +82,8 @@ namespace Lantean.QBTMud.Components.UI
|
||||
|
||||
protected HashSet<string> SelectedColumns { get; set; } = [];
|
||||
|
||||
private static readonly IReadOnlyList<ColumnDefinition<T>> EmptyColumns = Array.Empty<ColumnDefinition<T>>();
|
||||
|
||||
private Dictionary<string, int?> _columnWidths = [];
|
||||
|
||||
private Dictionary<string, int> _columnOrder = [];
|
||||
@@ -91,6 +94,12 @@ namespace Lantean.QBTMud.Components.UI
|
||||
|
||||
private readonly Dictionary<string, TdExtended> _tds = [];
|
||||
|
||||
private IReadOnlyList<ColumnDefinition<T>> _visibleColumns = EmptyColumns;
|
||||
|
||||
private bool _columnsDirty = true;
|
||||
|
||||
private IEnumerable<ColumnDefinition<T>>? _lastColumnDefinitions;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
HashSet<string> selectedColumns;
|
||||
@@ -109,6 +118,13 @@ namespace Lantean.QBTMud.Components.UI
|
||||
SelectedColumns = selectedColumns;
|
||||
await SelectedColumnsChanged.InvokeAsync(SelectedColumns);
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedColumns = selectedColumns;
|
||||
}
|
||||
|
||||
_lastColumnDefinitions = ColumnDefinitions;
|
||||
MarkColumnsDirty();
|
||||
|
||||
string? sortColumn;
|
||||
SortDirection sortDirection;
|
||||
@@ -137,11 +153,24 @@ namespace Lantean.QBTMud.Components.UI
|
||||
await SortDirectionChanged.InvokeAsync(_sortDirection);
|
||||
}
|
||||
|
||||
MarkColumnsDirty();
|
||||
|
||||
var storedColumnsWidths = await LocalStorage.GetItemAsync<Dictionary<string, int?>>(_columnWidthsStorageKey);
|
||||
if (storedColumnsWidths is not null)
|
||||
{
|
||||
_columnWidths = storedColumnsWidths;
|
||||
}
|
||||
MarkColumnsDirty();
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
base.OnParametersSet();
|
||||
if (!ReferenceEquals(_lastColumnDefinitions, ColumnDefinitions))
|
||||
{
|
||||
_lastColumnDefinitions = ColumnDefinitions;
|
||||
MarkColumnsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<T>? GetOrderedItems()
|
||||
@@ -165,39 +194,74 @@ namespace Lantean.QBTMud.Components.UI
|
||||
return Items.OrderByDirection(_sortDirection, sortSelector);
|
||||
}
|
||||
|
||||
protected IEnumerable<ColumnDefinition<T>> GetColumns()
|
||||
protected IReadOnlyList<ColumnDefinition<T>> GetColumns()
|
||||
{
|
||||
var filteredColumns = ColumnDefinitions.Where(c => SelectedColumns.Contains(c.Id)).Where(ColumnFilter);
|
||||
if (_columnOrder.Count == 0)
|
||||
if (!_columnsDirty)
|
||||
{
|
||||
foreach (var column in filteredColumns)
|
||||
{
|
||||
if (_columnWidths.TryGetValue(column.Id, out var value))
|
||||
{
|
||||
column.Width = value;
|
||||
}
|
||||
|
||||
yield return column;
|
||||
}
|
||||
|
||||
yield break;
|
||||
return _visibleColumns;
|
||||
}
|
||||
|
||||
var columnDictionary = filteredColumns.ToDictionary(c => c.Id);
|
||||
foreach (var columnId in _columnOrder.OrderBy(c => c.Value).Select(c => c.Key))
|
||||
_visibleColumns = BuildVisibleColumns();
|
||||
_columnsDirty = false;
|
||||
|
||||
return _visibleColumns;
|
||||
}
|
||||
|
||||
private IReadOnlyList<ColumnDefinition<T>> BuildVisibleColumns()
|
||||
{
|
||||
var filteredColumns = ColumnDefinitions
|
||||
.Where(c => SelectedColumns.Contains(c.Id))
|
||||
.Where(ColumnFilter)
|
||||
.ToList();
|
||||
|
||||
if (filteredColumns.Count == 0)
|
||||
{
|
||||
if (!columnDictionary.TryGetValue(columnId, out var column))
|
||||
return EmptyColumns;
|
||||
}
|
||||
|
||||
List<ColumnDefinition<T>> orderedColumns;
|
||||
if (_columnOrder.Count == 0)
|
||||
{
|
||||
orderedColumns = filteredColumns;
|
||||
}
|
||||
else
|
||||
{
|
||||
var orderLookup = _columnOrder.OrderBy(entry => entry.Value).ToList();
|
||||
var columnDictionary = filteredColumns.ToDictionary(c => c.Id);
|
||||
orderedColumns = new List<ColumnDefinition<T>>(filteredColumns.Count);
|
||||
|
||||
foreach (var (columnId, _) in orderLookup)
|
||||
{
|
||||
continue;
|
||||
if (!columnDictionary.TryGetValue(columnId, out var column))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
orderedColumns.Add(column);
|
||||
}
|
||||
|
||||
if (orderedColumns.Count != filteredColumns.Count)
|
||||
{
|
||||
var existingIds = new HashSet<string>(orderedColumns.Select(c => c.Id));
|
||||
foreach (var column in filteredColumns)
|
||||
{
|
||||
if (existingIds.Add(column.Id))
|
||||
{
|
||||
orderedColumns.Add(column);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var column in orderedColumns)
|
||||
{
|
||||
if (_columnWidths.TryGetValue(column.Id, out var value))
|
||||
{
|
||||
column.Width = value;
|
||||
}
|
||||
|
||||
yield return column;
|
||||
}
|
||||
|
||||
return orderedColumns;
|
||||
}
|
||||
|
||||
private async Task SetSort(string columnId, SortDirection sortDirection)
|
||||
@@ -316,18 +380,21 @@ namespace Lantean.QBTMud.Components.UI
|
||||
SelectedColumns = result.SelectedColumns;
|
||||
await LocalStorage.SetItemAsync(_columnSelectionStorageKey, SelectedColumns);
|
||||
await SelectedColumnsChanged.InvokeAsync(SelectedColumns);
|
||||
MarkColumnsDirty();
|
||||
}
|
||||
|
||||
if (!DictionaryEqual(_columnWidths, result.ColumnWidths))
|
||||
{
|
||||
_columnWidths = result.ColumnWidths;
|
||||
await LocalStorage.SetItemAsync(_columnWidthsStorageKey, _columnWidths);
|
||||
MarkColumnsDirty();
|
||||
}
|
||||
|
||||
if (!DictionaryEqual(_columnOrder, result.ColumnOrder))
|
||||
{
|
||||
_columnOrder = result.ColumnOrder;
|
||||
await LocalStorage.SetItemAsync(_columnOrderStorageKey, _columnOrder);
|
||||
MarkColumnsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,6 +446,12 @@ namespace Lantean.QBTMud.Components.UI
|
||||
return className;
|
||||
}
|
||||
|
||||
private void MarkColumnsDirty()
|
||||
{
|
||||
_columnsDirty = true;
|
||||
_visibleColumns = EmptyColumns;
|
||||
}
|
||||
|
||||
private sealed record SortData
|
||||
{
|
||||
public SortData(string sortColumn, SortDirection sortDirection)
|
||||
|
@@ -10,7 +10,8 @@
|
||||
}
|
||||
|
||||
<CascadingValue Value="Torrents">
|
||||
<CascadingValue Value="MainData">
|
||||
<CascadingValue Value="_torrentsVersion" Name="TorrentsVersion">
|
||||
<CascadingValue Value="MainData">
|
||||
<CascadingValue Value="Preferences">
|
||||
<CascadingValue Value="SortColumnChanged" Name="SortColumnChanged">
|
||||
<CascadingValue Value="SortColumn" Name="SortColumn">
|
||||
@@ -65,5 +66,6 @@
|
||||
@DisplayHelpers.Size(MainData?.ServerState.UploadInfoData, "(", ")")
|
||||
</MudText>
|
||||
</MudAppBar>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
@@ -1,4 +1,6 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMud.Components;
|
||||
using Lantean.QBTMud.Helpers;
|
||||
using Lantean.QBTMud.Models;
|
||||
@@ -52,22 +54,36 @@ namespace Lantean.QBTMud.Layout
|
||||
|
||||
protected string? SearchText { get; set; }
|
||||
|
||||
protected IEnumerable<Torrent> Torrents => GetTorrents();
|
||||
protected IReadOnlyList<Torrent> Torrents => GetTorrents();
|
||||
|
||||
protected bool IsAuthenticated { get; set; }
|
||||
|
||||
protected bool LostConnection { get; set; }
|
||||
|
||||
private List<Torrent> GetTorrents()
|
||||
private IReadOnlyList<Torrent> _visibleTorrents = Array.Empty<Torrent>();
|
||||
|
||||
private bool _torrentsDirty = true;
|
||||
private int _torrentsVersion;
|
||||
|
||||
private IReadOnlyList<Torrent> GetTorrents()
|
||||
{
|
||||
if (!_torrentsDirty)
|
||||
{
|
||||
return _visibleTorrents;
|
||||
}
|
||||
|
||||
if (MainData is null)
|
||||
{
|
||||
return [];
|
||||
_visibleTorrents = Array.Empty<Torrent>();
|
||||
_torrentsDirty = false;
|
||||
return _visibleTorrents;
|
||||
}
|
||||
|
||||
var filterState = new FilterState(Category, Status, Tag, Tracker, MainData.ServerState.UseSubcategories, SearchText);
|
||||
_visibleTorrents = MainData.Torrents.Values.Filter(filterState).ToList();
|
||||
_torrentsDirty = false;
|
||||
|
||||
return MainData.Torrents.Values.Filter(filterState).ToList();
|
||||
return _visibleTorrents;
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
@@ -84,6 +100,7 @@ namespace Lantean.QBTMud.Layout
|
||||
Version = await ApiClient.GetApplicationVersion();
|
||||
var data = await ApiClient.GetMainData(_requestId);
|
||||
MainData = DataManager.CreateMainData(data, Version);
|
||||
MarkTorrentsDirty();
|
||||
|
||||
_requestId = data.ResponseId;
|
||||
_refreshInterval = MainData.ServerState.RefreshInterval;
|
||||
@@ -126,32 +143,51 @@ namespace Lantean.QBTMud.Layout
|
||||
return;
|
||||
}
|
||||
|
||||
var shouldRender = false;
|
||||
|
||||
if (MainData is null || data.FullUpdate)
|
||||
{
|
||||
MainData = DataManager.CreateMainData(data, Version);
|
||||
MarkTorrentsDirty();
|
||||
shouldRender = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
DataManager.MergeMainData(data, MainData);
|
||||
var dataChanged = DataManager.MergeMainData(data, MainData, out var filterChanged);
|
||||
if (filterChanged)
|
||||
{
|
||||
MarkTorrentsDirty();
|
||||
}
|
||||
else if (dataChanged)
|
||||
{
|
||||
IncrementTorrentsVersion();
|
||||
}
|
||||
shouldRender = dataChanged;
|
||||
}
|
||||
|
||||
_refreshInterval = MainData.ServerState.RefreshInterval;
|
||||
if (MainData is not null)
|
||||
{
|
||||
_refreshInterval = MainData.ServerState.RefreshInterval;
|
||||
}
|
||||
_requestId = data.ResponseId;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
if (shouldRender)
|
||||
{
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected EventCallback<string> CategoryChanged => EventCallback.Factory.Create<string>(this, category => Category = category);
|
||||
protected EventCallback<string> CategoryChanged => EventCallback.Factory.Create<string>(this, OnCategoryChanged);
|
||||
|
||||
protected EventCallback<Status> StatusChanged => EventCallback.Factory.Create<Status>(this, status => Status = status);
|
||||
protected EventCallback<Status> StatusChanged => EventCallback.Factory.Create<Status>(this, OnStatusChanged);
|
||||
|
||||
protected EventCallback<string> TagChanged => EventCallback.Factory.Create<string>(this, tag => Tag = tag);
|
||||
protected EventCallback<string> TagChanged => EventCallback.Factory.Create<string>(this, OnTagChanged);
|
||||
|
||||
protected EventCallback<string> TrackerChanged => EventCallback.Factory.Create<string>(this, tracker => Tracker = tracker);
|
||||
protected EventCallback<string> TrackerChanged => EventCallback.Factory.Create<string>(this, OnTrackerChanged);
|
||||
|
||||
protected EventCallback<string> SearchTermChanged => EventCallback.Factory.Create<string>(this, term => SearchText = term);
|
||||
protected EventCallback<string> SearchTermChanged => EventCallback.Factory.Create<string>(this, OnSearchTermChanged);
|
||||
|
||||
protected EventCallback<string> SortColumnChanged => EventCallback.Factory.Create<string>(this, columnId => SortColumn = columnId);
|
||||
|
||||
@@ -167,6 +203,76 @@ namespace Lantean.QBTMud.Layout
|
||||
return (Icons.Material.Outlined.SignalWifi4Bar, Color.Success);
|
||||
}
|
||||
|
||||
private void OnCategoryChanged(string category)
|
||||
{
|
||||
if (Category == category)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Category = category;
|
||||
MarkTorrentsDirty();
|
||||
}
|
||||
|
||||
private void OnStatusChanged(Status status)
|
||||
{
|
||||
if (Status == status)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Status = status;
|
||||
MarkTorrentsDirty();
|
||||
}
|
||||
|
||||
private void OnTagChanged(string tag)
|
||||
{
|
||||
if (Tag == tag)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Tag = tag;
|
||||
MarkTorrentsDirty();
|
||||
}
|
||||
|
||||
private void OnTrackerChanged(string tracker)
|
||||
{
|
||||
if (Tracker == tracker)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Tracker = tracker;
|
||||
MarkTorrentsDirty();
|
||||
}
|
||||
|
||||
private void OnSearchTermChanged(string term)
|
||||
{
|
||||
if (SearchText == term)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SearchText = term;
|
||||
MarkTorrentsDirty();
|
||||
}
|
||||
|
||||
private void MarkTorrentsDirty()
|
||||
{
|
||||
_torrentsDirty = true;
|
||||
IncrementTorrentsVersion();
|
||||
}
|
||||
|
||||
private void IncrementTorrentsVersion()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
_torrentsVersion++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMud.Components.UI;
|
||||
using Lantean.QBTMud.Helpers;
|
||||
using Lantean.QBTMud.Models;
|
||||
@@ -35,11 +35,17 @@ namespace Lantean.QBTMud.Pages
|
||||
public QBitTorrentClient.Models.Preferences? Preferences { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public IEnumerable<Torrent>? Torrents { get; set; }
|
||||
public IReadOnlyList<Torrent>? Torrents { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public MainData MainData { get; set; } = default!;
|
||||
|
||||
[CascadingParameter(Name = "LostConnection")]
|
||||
public bool LostConnection { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "TorrentsVersion")]
|
||||
public int TorrentsVersion { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "SearchTermChanged")]
|
||||
public EventCallback<string> SearchTermChanged { get; set; }
|
||||
|
||||
@@ -56,7 +62,7 @@ namespace Lantean.QBTMud.Pages
|
||||
|
||||
protected HashSet<Torrent> SelectedItems { get; set; } = [];
|
||||
|
||||
protected bool ToolbarButtonsEnabled => SelectedItems.Count > 0;
|
||||
protected bool ToolbarButtonsEnabled => _toolbarButtonsEnabled;
|
||||
|
||||
protected DynamicTable<Torrent>? Table { get; set; }
|
||||
|
||||
@@ -64,6 +70,15 @@ namespace Lantean.QBTMud.Pages
|
||||
|
||||
protected ContextMenu? ContextMenu { get; set; }
|
||||
|
||||
private object? _lastRenderedTorrents;
|
||||
private QBitTorrentClient.Models.Preferences? _lastPreferences;
|
||||
private bool _lastLostConnection;
|
||||
private bool _hasRendered;
|
||||
private int _lastSelectionCount;
|
||||
private int _lastTorrentsVersion = -1;
|
||||
private bool _pendingSelectionChange;
|
||||
|
||||
private bool _toolbarButtonsEnabled;
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
@@ -73,9 +88,81 @@ namespace Lantean.QBTMud.Pages
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool ShouldRender()
|
||||
{
|
||||
if (!_hasRendered)
|
||||
{
|
||||
_hasRendered = true;
|
||||
_lastRenderedTorrents = Torrents;
|
||||
_lastPreferences = Preferences;
|
||||
_lastLostConnection = LostConnection;
|
||||
_lastTorrentsVersion = TorrentsVersion;
|
||||
_lastSelectionCount = SelectedItems.Count;
|
||||
_toolbarButtonsEnabled = _lastSelectionCount > 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_pendingSelectionChange)
|
||||
{
|
||||
_pendingSelectionChange = false;
|
||||
_lastSelectionCount = SelectedItems.Count;
|
||||
_toolbarButtonsEnabled = _lastSelectionCount > 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_lastTorrentsVersion != TorrentsVersion)
|
||||
{
|
||||
_lastTorrentsVersion = TorrentsVersion;
|
||||
_lastRenderedTorrents = Torrents;
|
||||
_lastPreferences = Preferences;
|
||||
_lastLostConnection = LostConnection;
|
||||
_lastSelectionCount = SelectedItems.Count;
|
||||
_toolbarButtonsEnabled = _lastSelectionCount > 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ReferenceEquals(_lastRenderedTorrents, Torrents))
|
||||
{
|
||||
_lastRenderedTorrents = Torrents;
|
||||
_lastPreferences = Preferences;
|
||||
_lastLostConnection = LostConnection;
|
||||
_lastSelectionCount = SelectedItems.Count;
|
||||
_toolbarButtonsEnabled = _lastSelectionCount > 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ReferenceEquals(_lastPreferences, Preferences))
|
||||
{
|
||||
_lastPreferences = Preferences;
|
||||
_lastSelectionCount = SelectedItems.Count;
|
||||
_toolbarButtonsEnabled = _lastSelectionCount > 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_lastLostConnection != LostConnection)
|
||||
{
|
||||
_lastLostConnection = LostConnection;
|
||||
_lastSelectionCount = SelectedItems.Count;
|
||||
_toolbarButtonsEnabled = _lastSelectionCount > 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_lastSelectionCount != SelectedItems.Count)
|
||||
{
|
||||
_lastSelectionCount = SelectedItems.Count;
|
||||
_toolbarButtonsEnabled = _lastSelectionCount > 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void SelectedItemsChanged(HashSet<Torrent> selectedItems)
|
||||
{
|
||||
SelectedItems = selectedItems;
|
||||
_toolbarButtonsEnabled = SelectedItems.Count > 0;
|
||||
_pendingSelectionChange = true;
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected async Task SortDirectionChangedHandler(SortDirection sortDirection)
|
||||
@@ -249,3 +336,4 @@ namespace Lantean.QBTMud.Pages
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using Lantean.QBTMud.Helpers;
|
||||
using Lantean.QBTMud.Helpers;
|
||||
using Lantean.QBTMud.Models;
|
||||
using System.Linq;
|
||||
|
||||
namespace Lantean.QBTMud.Services
|
||||
{
|
||||
@@ -142,14 +143,24 @@ namespace Lantean.QBTMud.Services
|
||||
serverState.WriteCacheOverload.GetValueOrDefault());
|
||||
}
|
||||
|
||||
public void MergeMainData(QBitTorrentClient.Models.MainData mainData, MainData torrentList)
|
||||
public bool MergeMainData(QBitTorrentClient.Models.MainData mainData, MainData torrentList, out bool filterChanged)
|
||||
{
|
||||
filterChanged = false;
|
||||
var dataChanged = false;
|
||||
|
||||
if (mainData.CategoriesRemoved is not null)
|
||||
{
|
||||
foreach (var category in mainData.CategoriesRemoved)
|
||||
{
|
||||
torrentList.Categories.Remove(category);
|
||||
torrentList.CategoriesState.Remove(category);
|
||||
if (torrentList.Categories.Remove(category))
|
||||
{
|
||||
dataChanged = true;
|
||||
filterChanged = true;
|
||||
}
|
||||
if (torrentList.CategoriesState.Remove(category))
|
||||
{
|
||||
filterChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,8 +168,15 @@ namespace Lantean.QBTMud.Services
|
||||
{
|
||||
foreach (var tag in mainData.TagsRemoved)
|
||||
{
|
||||
torrentList.Tags.Remove(tag);
|
||||
torrentList.TagState.Remove(tag);
|
||||
if (torrentList.Tags.Remove(tag))
|
||||
{
|
||||
dataChanged = true;
|
||||
filterChanged = true;
|
||||
}
|
||||
if (torrentList.TagState.Remove(tag))
|
||||
{
|
||||
filterChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,8 +184,15 @@ namespace Lantean.QBTMud.Services
|
||||
{
|
||||
foreach (var tracker in mainData.TrackersRemoved)
|
||||
{
|
||||
torrentList.Trackers.Remove(tracker);
|
||||
torrentList.TrackersState.Remove(tracker);
|
||||
if (torrentList.Trackers.Remove(tracker))
|
||||
{
|
||||
dataChanged = true;
|
||||
filterChanged = true;
|
||||
}
|
||||
if (torrentList.TrackersState.Remove(tracker))
|
||||
{
|
||||
filterChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,8 +200,12 @@ namespace Lantean.QBTMud.Services
|
||||
{
|
||||
foreach (var hash in mainData.TorrentsRemoved)
|
||||
{
|
||||
RemoveTorrentFromStates(torrentList, hash);
|
||||
torrentList.Torrents.Remove(hash);
|
||||
if (torrentList.Torrents.Remove(hash))
|
||||
{
|
||||
RemoveTorrentFromStates(torrentList, hash);
|
||||
dataChanged = true;
|
||||
filterChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,10 +217,12 @@ namespace Lantean.QBTMud.Services
|
||||
{
|
||||
var newCategory = CreateCategory(category);
|
||||
torrentList.Categories.Add(name, newCategory);
|
||||
dataChanged = true;
|
||||
filterChanged = true;
|
||||
}
|
||||
else
|
||||
else if (UpdateCategory(existingCategory, category))
|
||||
{
|
||||
UpdateCategory(existingCategory, category);
|
||||
dataChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,7 +231,11 @@ namespace Lantean.QBTMud.Services
|
||||
{
|
||||
foreach (var tag in mainData.Tags)
|
||||
{
|
||||
torrentList.Tags.Add(tag);
|
||||
if (torrentList.Tags.Add(tag))
|
||||
{
|
||||
dataChanged = true;
|
||||
filterChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,13 +243,16 @@ namespace Lantean.QBTMud.Services
|
||||
{
|
||||
foreach (var (url, hashes) in mainData.Trackers)
|
||||
{
|
||||
if (!torrentList.Trackers.TryGetValue(url, out _))
|
||||
if (!torrentList.Trackers.TryGetValue(url, out var existingHashes))
|
||||
{
|
||||
torrentList.Trackers.Add(url, hashes);
|
||||
dataChanged = true;
|
||||
filterChanged = true;
|
||||
}
|
||||
else
|
||||
else if (!existingHashes.SequenceEqual(hashes))
|
||||
{
|
||||
torrentList.Trackers[url] = hashes;
|
||||
dataChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,19 +266,34 @@ namespace Lantean.QBTMud.Services
|
||||
var newTorrent = CreateTorrent(hash, torrent);
|
||||
torrentList.Torrents.Add(hash, newTorrent);
|
||||
AddTorrentToStates(torrentList, hash, torrentList.MajorVersion);
|
||||
dataChanged = true;
|
||||
filterChanged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateTorrentStates(torrentList, hash);
|
||||
UpdateTorrent(existingTorrent, torrent);
|
||||
var updateResult = UpdateTorrent(existingTorrent, torrent);
|
||||
if (updateResult.FilterChanged)
|
||||
{
|
||||
UpdateTorrentStates(torrentList, hash);
|
||||
filterChanged = true;
|
||||
}
|
||||
if (updateResult.DataChanged)
|
||||
{
|
||||
dataChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mainData.ServerState is not null)
|
||||
{
|
||||
UpdateServerState(torrentList.ServerState, mainData.ServerState);
|
||||
if (UpdateServerState(torrentList.ServerState, mainData.ServerState))
|
||||
{
|
||||
dataChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
return dataChanged;
|
||||
}
|
||||
|
||||
private static void AddTorrentToStates(MainData torrentList, string hash, int version)
|
||||
@@ -403,33 +456,161 @@ namespace Lantean.QBTMud.Services
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateServerState(ServerState existingServerState, QBitTorrentClient.Models.ServerState serverState)
|
||||
private static bool UpdateServerState(ServerState existingServerState, QBitTorrentClient.Models.ServerState serverState)
|
||||
{
|
||||
existingServerState.AllTimeDownloaded = serverState.AllTimeDownloaded ?? existingServerState.AllTimeDownloaded;
|
||||
existingServerState.AllTimeUploaded = serverState.AllTimeUploaded ?? existingServerState.AllTimeUploaded;
|
||||
existingServerState.AverageTimeQueue = serverState.AverageTimeQueue ?? existingServerState.AverageTimeQueue;
|
||||
existingServerState.ConnectionStatus = serverState.ConnectionStatus ?? existingServerState.ConnectionStatus;
|
||||
existingServerState.DHTNodes = serverState.DHTNodes ?? existingServerState.DHTNodes;
|
||||
existingServerState.DownloadInfoData = serverState.DownloadInfoData ?? existingServerState.DownloadInfoData;
|
||||
existingServerState.DownloadInfoSpeed = serverState.DownloadInfoSpeed ?? existingServerState.DownloadInfoSpeed;
|
||||
existingServerState.DownloadRateLimit = serverState.DownloadRateLimit ?? existingServerState.DownloadRateLimit;
|
||||
existingServerState.FreeSpaceOnDisk = serverState.FreeSpaceOnDisk ?? existingServerState.FreeSpaceOnDisk;
|
||||
existingServerState.GlobalRatio = serverState.GlobalRatio ?? existingServerState.GlobalRatio;
|
||||
existingServerState.QueuedIOJobs = serverState.QueuedIOJobs ?? existingServerState.QueuedIOJobs;
|
||||
existingServerState.Queuing = serverState.Queuing ?? existingServerState.Queuing;
|
||||
existingServerState.ReadCacheHits = serverState.ReadCacheHits ?? existingServerState.ReadCacheHits;
|
||||
existingServerState.ReadCacheOverload = serverState.ReadCacheOverload ?? existingServerState.ReadCacheOverload;
|
||||
existingServerState.RefreshInterval = serverState.RefreshInterval ?? existingServerState.RefreshInterval;
|
||||
existingServerState.TotalBuffersSize = serverState.TotalBuffersSize ?? existingServerState.TotalBuffersSize;
|
||||
existingServerState.TotalPeerConnections = serverState.TotalPeerConnections ?? existingServerState.TotalPeerConnections;
|
||||
existingServerState.TotalQueuedSize = serverState.TotalQueuedSize ?? existingServerState.TotalQueuedSize;
|
||||
existingServerState.TotalWastedSession = serverState.TotalWastedSession ?? existingServerState.TotalWastedSession;
|
||||
existingServerState.UploadInfoData = serverState.UploadInfoData ?? existingServerState.UploadInfoData;
|
||||
existingServerState.UploadInfoSpeed = serverState.UploadInfoSpeed ?? existingServerState.UploadInfoSpeed;
|
||||
existingServerState.UploadRateLimit = serverState.UploadRateLimit ?? existingServerState.UploadRateLimit;
|
||||
existingServerState.UseAltSpeedLimits = serverState.UseAltSpeedLimits ?? existingServerState.UseAltSpeedLimits;
|
||||
existingServerState.UseSubcategories = serverState.UseSubcategories ?? existingServerState.UseSubcategories;
|
||||
existingServerState.WriteCacheOverload = serverState.WriteCacheOverload ?? existingServerState.WriteCacheOverload;
|
||||
var changed = false;
|
||||
|
||||
if (serverState.AllTimeDownloaded.HasValue && existingServerState.AllTimeDownloaded != serverState.AllTimeDownloaded.Value)
|
||||
{
|
||||
existingServerState.AllTimeDownloaded = serverState.AllTimeDownloaded.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.AllTimeUploaded.HasValue && existingServerState.AllTimeUploaded != serverState.AllTimeUploaded.Value)
|
||||
{
|
||||
existingServerState.AllTimeUploaded = serverState.AllTimeUploaded.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.AverageTimeQueue.HasValue && existingServerState.AverageTimeQueue != serverState.AverageTimeQueue.Value)
|
||||
{
|
||||
existingServerState.AverageTimeQueue = serverState.AverageTimeQueue.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.ConnectionStatus is not null && existingServerState.ConnectionStatus != serverState.ConnectionStatus)
|
||||
{
|
||||
existingServerState.ConnectionStatus = serverState.ConnectionStatus;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.DHTNodes.HasValue && existingServerState.DHTNodes != serverState.DHTNodes.Value)
|
||||
{
|
||||
existingServerState.DHTNodes = serverState.DHTNodes.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.DownloadInfoData.HasValue && existingServerState.DownloadInfoData != serverState.DownloadInfoData.Value)
|
||||
{
|
||||
existingServerState.DownloadInfoData = serverState.DownloadInfoData.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.DownloadInfoSpeed.HasValue && existingServerState.DownloadInfoSpeed != serverState.DownloadInfoSpeed.Value)
|
||||
{
|
||||
existingServerState.DownloadInfoSpeed = serverState.DownloadInfoSpeed.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.DownloadRateLimit.HasValue && existingServerState.DownloadRateLimit != serverState.DownloadRateLimit.Value)
|
||||
{
|
||||
existingServerState.DownloadRateLimit = serverState.DownloadRateLimit.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.FreeSpaceOnDisk.HasValue && existingServerState.FreeSpaceOnDisk != serverState.FreeSpaceOnDisk.Value)
|
||||
{
|
||||
existingServerState.FreeSpaceOnDisk = serverState.FreeSpaceOnDisk.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.GlobalRatio.HasValue && existingServerState.GlobalRatio != serverState.GlobalRatio.Value)
|
||||
{
|
||||
existingServerState.GlobalRatio = serverState.GlobalRatio.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.QueuedIOJobs.HasValue && existingServerState.QueuedIOJobs != serverState.QueuedIOJobs.Value)
|
||||
{
|
||||
existingServerState.QueuedIOJobs = serverState.QueuedIOJobs.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.Queuing.HasValue && existingServerState.Queuing != serverState.Queuing.Value)
|
||||
{
|
||||
existingServerState.Queuing = serverState.Queuing.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.ReadCacheHits.HasValue && existingServerState.ReadCacheHits != serverState.ReadCacheHits.Value)
|
||||
{
|
||||
existingServerState.ReadCacheHits = serverState.ReadCacheHits.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.ReadCacheOverload.HasValue && existingServerState.ReadCacheOverload != serverState.ReadCacheOverload.Value)
|
||||
{
|
||||
existingServerState.ReadCacheOverload = serverState.ReadCacheOverload.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.RefreshInterval.HasValue && existingServerState.RefreshInterval != serverState.RefreshInterval.Value)
|
||||
{
|
||||
existingServerState.RefreshInterval = serverState.RefreshInterval.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.TotalBuffersSize.HasValue && existingServerState.TotalBuffersSize != serverState.TotalBuffersSize.Value)
|
||||
{
|
||||
existingServerState.TotalBuffersSize = serverState.TotalBuffersSize.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.TotalPeerConnections.HasValue && existingServerState.TotalPeerConnections != serverState.TotalPeerConnections.Value)
|
||||
{
|
||||
existingServerState.TotalPeerConnections = serverState.TotalPeerConnections.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.TotalQueuedSize.HasValue && existingServerState.TotalQueuedSize != serverState.TotalQueuedSize.Value)
|
||||
{
|
||||
existingServerState.TotalQueuedSize = serverState.TotalQueuedSize.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.TotalWastedSession.HasValue && existingServerState.TotalWastedSession != serverState.TotalWastedSession.Value)
|
||||
{
|
||||
existingServerState.TotalWastedSession = serverState.TotalWastedSession.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.UploadInfoData.HasValue && existingServerState.UploadInfoData != serverState.UploadInfoData.Value)
|
||||
{
|
||||
existingServerState.UploadInfoData = serverState.UploadInfoData.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.UploadInfoSpeed.HasValue && existingServerState.UploadInfoSpeed != serverState.UploadInfoSpeed.Value)
|
||||
{
|
||||
existingServerState.UploadInfoSpeed = serverState.UploadInfoSpeed.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.UploadRateLimit.HasValue && existingServerState.UploadRateLimit != serverState.UploadRateLimit.Value)
|
||||
{
|
||||
existingServerState.UploadRateLimit = serverState.UploadRateLimit.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.UseAltSpeedLimits.HasValue && existingServerState.UseAltSpeedLimits != serverState.UseAltSpeedLimits.Value)
|
||||
{
|
||||
existingServerState.UseAltSpeedLimits = serverState.UseAltSpeedLimits.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.UseSubcategories.HasValue && existingServerState.UseSubcategories != serverState.UseSubcategories.Value)
|
||||
{
|
||||
existingServerState.UseSubcategories = serverState.UseSubcategories.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.WriteCacheOverload.HasValue && existingServerState.WriteCacheOverload != serverState.WriteCacheOverload.Value)
|
||||
{
|
||||
existingServerState.WriteCacheOverload = serverState.WriteCacheOverload.Value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
public void MergeTorrentPeers(QBitTorrentClient.Models.TorrentPeers torrentPeers, PeerList peerList)
|
||||
@@ -561,66 +742,344 @@ namespace Lantean.QBTMud.Services
|
||||
torrent.MaxInactiveSeedingTime.GetValueOrDefault());
|
||||
}
|
||||
|
||||
private static void UpdateCategory(Category existingCategory, QBitTorrentClient.Models.Category category)
|
||||
private static bool UpdateCategory(Category existingCategory, QBitTorrentClient.Models.Category category)
|
||||
{
|
||||
existingCategory.SavePath = category.SavePath ?? existingCategory.SavePath;
|
||||
if (category.SavePath is not null && existingCategory.SavePath != category.SavePath)
|
||||
{
|
||||
existingCategory.SavePath = category.SavePath;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void UpdateTorrent(Torrent existingTorrent, QBitTorrentClient.Models.Torrent torrent)
|
||||
private readonly struct TorrentUpdateResult
|
||||
{
|
||||
existingTorrent.AddedOn = torrent.AddedOn ?? existingTorrent.AddedOn;
|
||||
existingTorrent.AmountLeft = torrent.AmountLeft ?? existingTorrent.AmountLeft;
|
||||
existingTorrent.AutomaticTorrentManagement = torrent.AutomaticTorrentManagement ?? existingTorrent.AutomaticTorrentManagement;
|
||||
existingTorrent.Availability = torrent.Availability ?? existingTorrent.Availability;
|
||||
existingTorrent.Category = torrent.Category ?? existingTorrent.Category;
|
||||
existingTorrent.Completed = torrent.Completed ?? existingTorrent.Completed;
|
||||
existingTorrent.CompletionOn = torrent.CompletionOn ?? existingTorrent.CompletionOn;
|
||||
existingTorrent.ContentPath = torrent.ContentPath ?? existingTorrent.ContentPath;
|
||||
existingTorrent.Downloaded = torrent.Downloaded ?? existingTorrent.Downloaded;
|
||||
existingTorrent.DownloadedSession = torrent.DownloadedSession ?? existingTorrent.DownloadedSession;
|
||||
existingTorrent.DownloadLimit = torrent.DownloadLimit ?? existingTorrent.DownloadLimit;
|
||||
existingTorrent.DownloadSpeed = torrent.DownloadSpeed ?? existingTorrent.DownloadSpeed;
|
||||
existingTorrent.EstimatedTimeOfArrival = torrent.EstimatedTimeOfArrival ?? existingTorrent.EstimatedTimeOfArrival;
|
||||
existingTorrent.FirstLastPiecePriority = torrent.FirstLastPiecePriority ?? existingTorrent.FirstLastPiecePriority;
|
||||
existingTorrent.ForceStart = torrent.ForceStart ?? existingTorrent.ForceStart;
|
||||
existingTorrent.InfoHashV1 = torrent.InfoHashV1 ?? existingTorrent.InfoHashV1;
|
||||
existingTorrent.InfoHashV2 = torrent.InfoHashV2 ?? existingTorrent.InfoHashV2;
|
||||
existingTorrent.LastActivity = torrent.LastActivity ?? existingTorrent.LastActivity;
|
||||
existingTorrent.MagnetUri = torrent.MagnetUri ?? existingTorrent.MagnetUri;
|
||||
existingTorrent.MaxRatio = torrent.MaxRatio ?? existingTorrent.MaxRatio;
|
||||
existingTorrent.MaxSeedingTime = torrent.MaxSeedingTime ?? existingTorrent.MaxSeedingTime;
|
||||
existingTorrent.Name = torrent.Name ?? existingTorrent.Name;
|
||||
existingTorrent.NumberComplete = torrent.NumberComplete ?? existingTorrent.NumberComplete;
|
||||
existingTorrent.NumberIncomplete = torrent.NumberIncomplete ?? existingTorrent.NumberIncomplete;
|
||||
existingTorrent.NumberLeeches = torrent.NumberLeeches ?? existingTorrent.NumberLeeches;
|
||||
existingTorrent.NumberSeeds = torrent.NumberSeeds ?? existingTorrent.NumberSeeds;
|
||||
existingTorrent.Priority = torrent.Priority ?? existingTorrent.Priority;
|
||||
existingTorrent.Progress = torrent.Progress ?? existingTorrent.Progress;
|
||||
existingTorrent.Ratio = torrent.Ratio ?? existingTorrent.Ratio;
|
||||
existingTorrent.RatioLimit = torrent.RatioLimit ?? existingTorrent.RatioLimit;
|
||||
existingTorrent.SavePath = torrent.SavePath ?? existingTorrent.SavePath;
|
||||
existingTorrent.SeedingTime = torrent.SeedingTime ?? existingTorrent.SeedingTime;
|
||||
existingTorrent.SeedingTimeLimit = torrent.SeedingTimeLimit ?? existingTorrent.SeedingTimeLimit;
|
||||
existingTorrent.SeenComplete = torrent.SeenComplete ?? existingTorrent.SeenComplete;
|
||||
existingTorrent.SequentialDownload = torrent.SequentialDownload ?? existingTorrent.SequentialDownload;
|
||||
existingTorrent.Size = torrent.Size ?? existingTorrent.Size;
|
||||
existingTorrent.State = torrent.State ?? existingTorrent.State;
|
||||
existingTorrent.SuperSeeding = torrent.SuperSeeding ?? existingTorrent.SuperSeeding;
|
||||
public TorrentUpdateResult(bool dataChanged, bool filterChanged)
|
||||
{
|
||||
DataChanged = dataChanged;
|
||||
FilterChanged = filterChanged;
|
||||
}
|
||||
|
||||
public bool DataChanged { get; }
|
||||
|
||||
public bool FilterChanged { get; }
|
||||
}
|
||||
|
||||
private static TorrentUpdateResult UpdateTorrent(Torrent existingTorrent, QBitTorrentClient.Models.Torrent torrent)
|
||||
{
|
||||
var dataChanged = false;
|
||||
var filterChanged = false;
|
||||
|
||||
if (torrent.AddedOn.HasValue && existingTorrent.AddedOn != torrent.AddedOn.Value)
|
||||
{
|
||||
existingTorrent.AddedOn = torrent.AddedOn.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.AmountLeft.HasValue && existingTorrent.AmountLeft != torrent.AmountLeft.Value)
|
||||
{
|
||||
existingTorrent.AmountLeft = torrent.AmountLeft.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.AutomaticTorrentManagement.HasValue && existingTorrent.AutomaticTorrentManagement != torrent.AutomaticTorrentManagement.Value)
|
||||
{
|
||||
existingTorrent.AutomaticTorrentManagement = torrent.AutomaticTorrentManagement.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.Availability.HasValue && existingTorrent.Availability != torrent.Availability.Value)
|
||||
{
|
||||
existingTorrent.Availability = torrent.Availability.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.Category is not null && existingTorrent.Category != torrent.Category)
|
||||
{
|
||||
existingTorrent.Category = torrent.Category;
|
||||
dataChanged = true;
|
||||
filterChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.Completed.HasValue && existingTorrent.Completed != torrent.Completed.Value)
|
||||
{
|
||||
existingTorrent.Completed = torrent.Completed.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.CompletionOn.HasValue && existingTorrent.CompletionOn != torrent.CompletionOn.Value)
|
||||
{
|
||||
existingTorrent.CompletionOn = torrent.CompletionOn.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.ContentPath is not null && existingTorrent.ContentPath != torrent.ContentPath)
|
||||
{
|
||||
existingTorrent.ContentPath = torrent.ContentPath;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.Downloaded.HasValue && existingTorrent.Downloaded != torrent.Downloaded.Value)
|
||||
{
|
||||
existingTorrent.Downloaded = torrent.Downloaded.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.DownloadedSession.HasValue && existingTorrent.DownloadedSession != torrent.DownloadedSession.Value)
|
||||
{
|
||||
existingTorrent.DownloadedSession = torrent.DownloadedSession.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.DownloadLimit.HasValue && existingTorrent.DownloadLimit != torrent.DownloadLimit.Value)
|
||||
{
|
||||
existingTorrent.DownloadLimit = torrent.DownloadLimit.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.DownloadSpeed.HasValue && existingTorrent.DownloadSpeed != torrent.DownloadSpeed.Value)
|
||||
{
|
||||
existingTorrent.DownloadSpeed = torrent.DownloadSpeed.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.EstimatedTimeOfArrival.HasValue && existingTorrent.EstimatedTimeOfArrival != torrent.EstimatedTimeOfArrival.Value)
|
||||
{
|
||||
existingTorrent.EstimatedTimeOfArrival = torrent.EstimatedTimeOfArrival.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.FirstLastPiecePriority.HasValue && existingTorrent.FirstLastPiecePriority != torrent.FirstLastPiecePriority.Value)
|
||||
{
|
||||
existingTorrent.FirstLastPiecePriority = torrent.FirstLastPiecePriority.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.ForceStart.HasValue && existingTorrent.ForceStart != torrent.ForceStart.Value)
|
||||
{
|
||||
existingTorrent.ForceStart = torrent.ForceStart.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.InfoHashV1 is not null && existingTorrent.InfoHashV1 != torrent.InfoHashV1)
|
||||
{
|
||||
existingTorrent.InfoHashV1 = torrent.InfoHashV1;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.InfoHashV2 is not null && existingTorrent.InfoHashV2 != torrent.InfoHashV2)
|
||||
{
|
||||
existingTorrent.InfoHashV2 = torrent.InfoHashV2;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.LastActivity.HasValue && existingTorrent.LastActivity != torrent.LastActivity.Value)
|
||||
{
|
||||
existingTorrent.LastActivity = torrent.LastActivity.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.MagnetUri is not null && existingTorrent.MagnetUri != torrent.MagnetUri)
|
||||
{
|
||||
existingTorrent.MagnetUri = torrent.MagnetUri;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.MaxRatio.HasValue && existingTorrent.MaxRatio != torrent.MaxRatio.Value)
|
||||
{
|
||||
existingTorrent.MaxRatio = torrent.MaxRatio.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.MaxSeedingTime.HasValue && existingTorrent.MaxSeedingTime != torrent.MaxSeedingTime.Value)
|
||||
{
|
||||
existingTorrent.MaxSeedingTime = torrent.MaxSeedingTime.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.Name is not null && existingTorrent.Name != torrent.Name)
|
||||
{
|
||||
existingTorrent.Name = torrent.Name;
|
||||
dataChanged = true;
|
||||
filterChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.NumberComplete.HasValue && existingTorrent.NumberComplete != torrent.NumberComplete.Value)
|
||||
{
|
||||
existingTorrent.NumberComplete = torrent.NumberComplete.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.NumberIncomplete.HasValue && existingTorrent.NumberIncomplete != torrent.NumberIncomplete.Value)
|
||||
{
|
||||
existingTorrent.NumberIncomplete = torrent.NumberIncomplete.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.NumberLeeches.HasValue && existingTorrent.NumberLeeches != torrent.NumberLeeches.Value)
|
||||
{
|
||||
existingTorrent.NumberLeeches = torrent.NumberLeeches.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.NumberSeeds.HasValue && existingTorrent.NumberSeeds != torrent.NumberSeeds.Value)
|
||||
{
|
||||
existingTorrent.NumberSeeds = torrent.NumberSeeds.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.Priority.HasValue && existingTorrent.Priority != torrent.Priority.Value)
|
||||
{
|
||||
existingTorrent.Priority = torrent.Priority.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.Progress.HasValue && existingTorrent.Progress != torrent.Progress.Value)
|
||||
{
|
||||
existingTorrent.Progress = torrent.Progress.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.Ratio.HasValue && existingTorrent.Ratio != torrent.Ratio.Value)
|
||||
{
|
||||
existingTorrent.Ratio = torrent.Ratio.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.RatioLimit.HasValue && existingTorrent.RatioLimit != torrent.RatioLimit.Value)
|
||||
{
|
||||
existingTorrent.RatioLimit = torrent.RatioLimit.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.SavePath is not null && existingTorrent.SavePath != torrent.SavePath)
|
||||
{
|
||||
existingTorrent.SavePath = torrent.SavePath;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.SeedingTime.HasValue && existingTorrent.SeedingTime != torrent.SeedingTime.Value)
|
||||
{
|
||||
existingTorrent.SeedingTime = torrent.SeedingTime.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.SeedingTimeLimit.HasValue && existingTorrent.SeedingTimeLimit != torrent.SeedingTimeLimit.Value)
|
||||
{
|
||||
existingTorrent.SeedingTimeLimit = torrent.SeedingTimeLimit.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.SeenComplete.HasValue && existingTorrent.SeenComplete != torrent.SeenComplete.Value)
|
||||
{
|
||||
existingTorrent.SeenComplete = torrent.SeenComplete.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.SequentialDownload.HasValue && existingTorrent.SequentialDownload != torrent.SequentialDownload.Value)
|
||||
{
|
||||
existingTorrent.SequentialDownload = torrent.SequentialDownload.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.Size.HasValue && existingTorrent.Size != torrent.Size.Value)
|
||||
{
|
||||
existingTorrent.Size = torrent.Size.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.State is not null && existingTorrent.State != torrent.State)
|
||||
{
|
||||
existingTorrent.State = torrent.State;
|
||||
dataChanged = true;
|
||||
filterChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.SuperSeeding.HasValue && existingTorrent.SuperSeeding != torrent.SuperSeeding.Value)
|
||||
{
|
||||
existingTorrent.SuperSeeding = torrent.SuperSeeding.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.Tags is not null)
|
||||
{
|
||||
existingTorrent.Tags.Clear();
|
||||
existingTorrent.Tags.AddRange(torrent.Tags);
|
||||
if (!existingTorrent.Tags.SequenceEqual(torrent.Tags))
|
||||
{
|
||||
existingTorrent.Tags.Clear();
|
||||
existingTorrent.Tags.AddRange(torrent.Tags);
|
||||
dataChanged = true;
|
||||
filterChanged = true;
|
||||
}
|
||||
}
|
||||
existingTorrent.TimeActive = torrent.TimeActive ?? existingTorrent.TimeActive;
|
||||
existingTorrent.TotalSize = torrent.TotalSize ?? existingTorrent.TotalSize;
|
||||
existingTorrent.Tracker = torrent.Tracker ?? existingTorrent.Tracker;
|
||||
existingTorrent.UploadLimit = torrent.UploadLimit ?? existingTorrent.UploadLimit;
|
||||
existingTorrent.Uploaded = torrent.Uploaded ?? existingTorrent.Uploaded;
|
||||
existingTorrent.UploadedSession = torrent.UploadedSession ?? existingTorrent.UploadedSession;
|
||||
existingTorrent.UploadSpeed = torrent.UploadSpeed ?? existingTorrent.UploadSpeed;
|
||||
existingTorrent.Reannounce = torrent.Reannounce ?? existingTorrent.Reannounce;
|
||||
existingTorrent.InactiveSeedingTimeLimit = torrent.InactiveSeedingTimeLimit ?? existingTorrent.InactiveSeedingTimeLimit;
|
||||
existingTorrent.MaxInactiveSeedingTime = torrent.MaxInactiveSeedingTime ?? existingTorrent.MaxInactiveSeedingTime;
|
||||
|
||||
if (torrent.TimeActive.HasValue && existingTorrent.TimeActive != torrent.TimeActive.Value)
|
||||
{
|
||||
existingTorrent.TimeActive = torrent.TimeActive.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.TotalSize.HasValue && existingTorrent.TotalSize != torrent.TotalSize.Value)
|
||||
{
|
||||
existingTorrent.TotalSize = torrent.TotalSize.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.Tracker is not null && existingTorrent.Tracker != torrent.Tracker)
|
||||
{
|
||||
existingTorrent.Tracker = torrent.Tracker;
|
||||
dataChanged = true;
|
||||
filterChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.UploadLimit.HasValue && existingTorrent.UploadLimit != torrent.UploadLimit.Value)
|
||||
{
|
||||
existingTorrent.UploadLimit = torrent.UploadLimit.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.Uploaded.HasValue && existingTorrent.Uploaded != torrent.Uploaded.Value)
|
||||
{
|
||||
existingTorrent.Uploaded = torrent.Uploaded.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.UploadedSession.HasValue && existingTorrent.UploadedSession != torrent.UploadedSession.Value)
|
||||
{
|
||||
existingTorrent.UploadedSession = torrent.UploadedSession.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
var previousUploadSpeed = existingTorrent.UploadSpeed;
|
||||
if (torrent.UploadSpeed.HasValue && previousUploadSpeed != torrent.UploadSpeed.Value)
|
||||
{
|
||||
existingTorrent.UploadSpeed = torrent.UploadSpeed.Value;
|
||||
dataChanged = true;
|
||||
if ((previousUploadSpeed > 0) != (torrent.UploadSpeed.Value > 0))
|
||||
{
|
||||
filterChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (torrent.Reannounce.HasValue && existingTorrent.Reannounce != torrent.Reannounce.Value)
|
||||
{
|
||||
existingTorrent.Reannounce = torrent.Reannounce.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.InactiveSeedingTimeLimit.HasValue && existingTorrent.InactiveSeedingTimeLimit != torrent.InactiveSeedingTimeLimit.Value)
|
||||
{
|
||||
existingTorrent.InactiveSeedingTimeLimit = torrent.InactiveSeedingTimeLimit.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.MaxInactiveSeedingTime.HasValue && existingTorrent.MaxInactiveSeedingTime != torrent.MaxInactiveSeedingTime.Value)
|
||||
{
|
||||
existingTorrent.MaxInactiveSeedingTime = torrent.MaxInactiveSeedingTime.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
return new TorrentUpdateResult(dataChanged, filterChanged);
|
||||
}
|
||||
|
||||
public Dictionary<string, ContentItem> CreateContentsList(IReadOnlyList<QBitTorrentClient.Models.FileData> files)
|
||||
@@ -745,6 +1204,111 @@ namespace Lantean.QBTMud.Services
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool UpdateContentItem(ContentItem destination, ContentItem source)
|
||||
{
|
||||
const float floatTolerance = 0.0001f;
|
||||
var changed = false;
|
||||
|
||||
if (destination.Priority != source.Priority)
|
||||
{
|
||||
destination.Priority = source.Priority;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (System.Math.Abs(destination.Progress - source.Progress) > floatTolerance)
|
||||
{
|
||||
destination.Progress = source.Progress;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (destination.Size != source.Size)
|
||||
{
|
||||
destination.Size = source.Size;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (System.Math.Abs(destination.Availability - source.Availability) > floatTolerance)
|
||||
{
|
||||
destination.Availability = source.Availability;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
private struct DirectoryAccumulator
|
||||
{
|
||||
public long TotalSize { get; private set; }
|
||||
|
||||
private long _activeSize;
|
||||
private double _progressSum;
|
||||
private double _availabilitySum;
|
||||
private Priority? _priority;
|
||||
private bool _mixedPriority;
|
||||
|
||||
public void Add(Priority priority, float progress, long size, float availability)
|
||||
{
|
||||
TotalSize += size;
|
||||
|
||||
if (priority != Priority.DoNotDownload)
|
||||
{
|
||||
_activeSize += size;
|
||||
_progressSum += progress * size;
|
||||
_availabilitySum += availability * size;
|
||||
}
|
||||
|
||||
if (!_priority.HasValue)
|
||||
{
|
||||
_priority = priority;
|
||||
}
|
||||
else if (_priority.Value != priority)
|
||||
{
|
||||
_mixedPriority = true;
|
||||
}
|
||||
}
|
||||
|
||||
public Priority ResolvePriority()
|
||||
{
|
||||
if (_mixedPriority)
|
||||
{
|
||||
return Priority.Mixed;
|
||||
}
|
||||
|
||||
return _priority ?? Priority.Normal;
|
||||
}
|
||||
|
||||
public float ResolveProgress()
|
||||
{
|
||||
if (_activeSize == 0 || TotalSize == 0)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
var value = _progressSum / _activeSize;
|
||||
if (value < 0)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
if (value > 1)
|
||||
{
|
||||
return 1f;
|
||||
}
|
||||
|
||||
return (float)value;
|
||||
}
|
||||
|
||||
public float ResolveAvailability()
|
||||
{
|
||||
if (_activeSize == 0 || TotalSize == 0)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
return (float)(_availabilitySum / _activeSize);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ContentTreeNode
|
||||
{
|
||||
public ContentTreeNode(ContentItem? item, ContentTreeNode? parent)
|
||||
@@ -1180,24 +1744,120 @@ namespace Lantean.QBTMud.Services
|
||||
return original;
|
||||
}
|
||||
|
||||
public void MergeContentsList(IReadOnlyList<QBitTorrentClient.Models.FileData> files, Dictionary<string, ContentItem> contents)
|
||||
public bool MergeContentsList(IReadOnlyList<QBitTorrentClient.Models.FileData> files, Dictionary<string, ContentItem> contents)
|
||||
{
|
||||
var contentsList = CreateContentsList(files);
|
||||
|
||||
foreach (var (key, value) in contentsList)
|
||||
if (files.Count == 0)
|
||||
{
|
||||
if (contents.TryGetValue(key, out var content))
|
||||
if (contents.Count == 0)
|
||||
{
|
||||
content.Availability = value.Availability;
|
||||
content.Priority = value.Priority;
|
||||
content.Progress = value.Progress;
|
||||
content.Size = value.Size;
|
||||
return false;
|
||||
}
|
||||
|
||||
contents.Clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
var hasChanges = false;
|
||||
var seenPaths = new HashSet<string>(files.Count * 2);
|
||||
var directoryAccumulators = new Dictionary<string, DirectoryAccumulator>();
|
||||
|
||||
var minExistingIndex = contents.Count == 0
|
||||
? int.MaxValue
|
||||
: contents.Values.Min(c => c.Index);
|
||||
var minFileIndex = files.Min(f => f.Index);
|
||||
var nextFolderIndex = System.Math.Min(minExistingIndex, minFileIndex) - 1;
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var priority = (Priority)(int)file.Priority;
|
||||
var pathSegments = file.Name.Split(Extensions.DirectorySeparator);
|
||||
var level = pathSegments.Length - 1;
|
||||
var displayName = pathSegments[^1];
|
||||
var filePath = file.Name;
|
||||
seenPaths.Add(filePath);
|
||||
|
||||
if (contents.TryGetValue(filePath, out var existingFile))
|
||||
{
|
||||
var updatedFile = new ContentItem(filePath, displayName, file.Index, priority, file.Progress, file.Size, file.Availability, false, level);
|
||||
if (UpdateContentItem(existingFile, updatedFile))
|
||||
{
|
||||
hasChanges = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
contents[key] = value;
|
||||
var newFile = new ContentItem(filePath, displayName, file.Index, priority, file.Progress, file.Size, file.Availability, false, level);
|
||||
contents[filePath] = newFile;
|
||||
hasChanges = true;
|
||||
}
|
||||
|
||||
string directoryPath = string.Empty;
|
||||
for (var i = 0; i < level; i++)
|
||||
{
|
||||
var segment = pathSegments[i];
|
||||
if (segment == ".unwanted")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
directoryPath = string.IsNullOrEmpty(directoryPath)
|
||||
? segment
|
||||
: string.Concat(directoryPath, Extensions.DirectorySeparator, segment);
|
||||
|
||||
seenPaths.Add(directoryPath);
|
||||
|
||||
if (!contents.TryGetValue(directoryPath, out var directoryItem))
|
||||
{
|
||||
var newDirectory = new ContentItem(directoryPath, segment, nextFolderIndex--, Priority.Normal, 0, 0, 0, true, i);
|
||||
contents[directoryPath] = newDirectory;
|
||||
hasChanges = true;
|
||||
}
|
||||
|
||||
if (!directoryAccumulators.TryGetValue(directoryPath, out var accumulator))
|
||||
{
|
||||
accumulator = new DirectoryAccumulator();
|
||||
}
|
||||
|
||||
accumulator.Add(priority, file.Progress, file.Size, file.Availability);
|
||||
directoryAccumulators[directoryPath] = accumulator;
|
||||
}
|
||||
}
|
||||
|
||||
var keysToRemove = contents.Keys.Where(key => !seenPaths.Contains(key)).ToList();
|
||||
if (keysToRemove.Count != 0)
|
||||
{
|
||||
hasChanges = true;
|
||||
foreach (var key in keysToRemove)
|
||||
{
|
||||
contents.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (directoryPath, accumulator) in directoryAccumulators)
|
||||
{
|
||||
if (!contents.TryGetValue(directoryPath, out var directoryItem))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var updatedDirectory = new ContentItem(
|
||||
directoryPath,
|
||||
directoryItem.DisplayName,
|
||||
directoryItem.Index,
|
||||
accumulator.ResolvePriority(),
|
||||
accumulator.ResolveProgress(),
|
||||
accumulator.TotalSize,
|
||||
accumulator.ResolveAvailability(),
|
||||
true,
|
||||
directoryItem.Level);
|
||||
|
||||
if (UpdateContentItem(directoryItem, updatedDirectory))
|
||||
{
|
||||
hasChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
public RssList CreateRssList(IReadOnlyDictionary<string, QBitTorrentClient.Models.RssItem> rssItems)
|
||||
|
@@ -8,7 +8,7 @@ namespace Lantean.QBTMud.Services
|
||||
|
||||
Torrent CreateTorrent(string hash, QBitTorrentClient.Models.Torrent torrent);
|
||||
|
||||
void MergeMainData(QBitTorrentClient.Models.MainData mainData, MainData torrentList);
|
||||
bool MergeMainData(QBitTorrentClient.Models.MainData mainData, MainData torrentList, out bool filterChanged);
|
||||
|
||||
PeerList CreatePeerList(QBitTorrentClient.Models.TorrentPeers torrentPeers);
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Lantean.QBTMud.Services
|
||||
|
||||
Dictionary<string, ContentItem> CreateContentsList(IReadOnlyList<QBitTorrentClient.Models.FileData> files);
|
||||
|
||||
void MergeContentsList(IReadOnlyList<QBitTorrentClient.Models.FileData> files, Dictionary<string, ContentItem> contents);
|
||||
bool MergeContentsList(IReadOnlyList<QBitTorrentClient.Models.FileData> files, Dictionary<string, ContentItem> contents);
|
||||
|
||||
QBitTorrentClient.Models.UpdatePreferences MergePreferences(QBitTorrentClient.Models.UpdatePreferences? original, QBitTorrentClient.Models.UpdatePreferences changed);
|
||||
|
||||
|
5
global.json
Normal file
5
global.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "9.0.306"
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user