diff --git a/Lantean.QBTMudBlade/App.razor b/Lantean.QBTMudBlade/App.razor index 6fd3ed1..5212a2b 100644 --- a/Lantean.QBTMudBlade/App.razor +++ b/Lantean.QBTMudBlade/App.razor @@ -9,4 +9,4 @@

Sorry, there's nothing at this address.

- + \ No newline at end of file diff --git a/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentFileDialog.razor b/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentFileDialog.razor index 3a0c2d2..91ac264 100644 --- a/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentFileDialog.razor +++ b/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentFileDialog.razor @@ -6,7 +6,7 @@ Choose files @@ -14,8 +14,8 @@ - + Close diff --git a/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentLinkDialog.razor b/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentLinkDialog.razor index 46ab783..b2449fb 100644 --- a/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentLinkDialog.razor +++ b/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentLinkDialog.razor @@ -4,8 +4,8 @@ - + Close diff --git a/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentOptions.razor b/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentOptions.razor index c7770e3..f86abc2 100644 --- a/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentOptions.razor +++ b/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentOptions.razor @@ -1,62 +1,68 @@ - - - Manual - Automatic - - - - - -@if (ShowCookieOption) -{ + - + -} - - - - - - @foreach (var category in Categories) + + + + + + Manual + Automatic + + + + + + @if (ShowCookieOption) { - @category + + + } - - - - - - - - - - - None - Metadata received - Files checked - - - - - - - - - - Original - Create subfolder - Don't create subfolder' - - - - - - - - - - - - - \ No newline at end of file + + + + + + @foreach (var category in Categories) + { + @category + } + + + + + + + + + + + None + Metadata received + Files checked + + + + + + + Original + Create subfolder + Don't create subfolder' + + + + + + + + + + + + + + + diff --git a/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentOptions.razor.cs b/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentOptions.razor.cs index ae030b9..0fb597d 100644 --- a/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentOptions.razor.cs +++ b/Lantean.QBTMudBlade/Components/Dialogs/AddTorrentOptions.razor.cs @@ -12,6 +12,8 @@ namespace Lantean.QBTMudBlade.Components.Dialogs [Parameter] public bool ShowCookieOption { get; set; } + protected bool Expanded { get; set; } + protected bool TorrentManagementMode { get; set; } protected string SavePath { get; set; } = default!; diff --git a/Lantean.QBTMudBlade/Components/Dialogs/ExceptionDialog.razor b/Lantean.QBTMudBlade/Components/Dialogs/ExceptionDialog.razor new file mode 100644 index 0000000..2e17fee --- /dev/null +++ b/Lantean.QBTMudBlade/Components/Dialogs/ExceptionDialog.razor @@ -0,0 +1,34 @@ + + + + @if (Exception is null) + { + + + Missing error information. + + + } + else + { + + @Exception.Message + + + @Exception.Source + + + +
+                            @Exception.StackTrace
+                        
+
+
+ } +
+ +
+ + Close + +
\ No newline at end of file diff --git a/Lantean.QBTMudBlade/Components/Dialogs/ExceptionDialog.razor.cs b/Lantean.QBTMudBlade/Components/Dialogs/ExceptionDialog.razor.cs new file mode 100644 index 0000000..499f423 --- /dev/null +++ b/Lantean.QBTMudBlade/Components/Dialogs/ExceptionDialog.razor.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; +using MudBlazor; + +namespace Lantean.QBTMudBlade.Components.Dialogs +{ + public partial class ExceptionDialog + { + [CascadingParameter] + public MudDialogInstance MudDialog { get; set; } = default!; + + [Parameter] + public Exception? Exception { get; set; } + + protected void Close(MouseEventArgs args) + { + MudDialog.Cancel(); + } + } +} \ No newline at end of file diff --git a/Lantean.QBTMudBlade/Components/EnhancedErrorBoundary.cs b/Lantean.QBTMudBlade/Components/EnhancedErrorBoundary.cs new file mode 100644 index 0000000..7da6003 --- /dev/null +++ b/Lantean.QBTMudBlade/Components/EnhancedErrorBoundary.cs @@ -0,0 +1,73 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; +using System.Collections.ObjectModel; + +namespace Lantean.QBTMudBlade.Components +{ + public class EnhancedErrorBoundary : ErrorBoundaryBase + { + private readonly ObservableCollection _exceptions = []; + + public bool HasErrored => CurrentException != null; + + [Parameter] + public EventCallback OnClear { get; set; } + + protected override Task OnErrorAsync(Exception exception) + { + _exceptions.Add(exception); + + return Task.CompletedTask; + } + + public async Task RecoverAndClearErrors() + { + Recover(); + _exceptions.Clear(); + + await OnClear.InvokeAsync(); + } + + public async Task ClearErrors() + { + _exceptions.Clear(); + await OnClear.InvokeAsync(); + } + + public IReadOnlyList Errors => _exceptions.AsReadOnly(); + + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + builder.AddContent(0, ChildContent); + //builder.OpenComponent>(0); + //builder.AddAttribute(1, "Value", this); + //builder.AddContent(2, ChildContent); + //builder.CloseComponent(); + // if (CurrentException is null) + // { + // builder.AddContent(0, ChildContent); + // } + // else + // { + // if (ErrorContent is not null) + // { + // builder.AddContent(1, ErrorContent(CurrentException)); + // } + // else + // { + // builder.OpenElement(2, "div"); + // builder.AddContent(3, "Blazor School Custom Error Boundary."); + // builder.AddContent(4, __innerBuilder => + // { + // __innerBuilder.OpenElement(5, "button"); + // __innerBuilder.AddAttribute(6, "type", "button"); + // __innerBuilder.AddAttribute(7, "onclick", RecoverAndClearErrors); + // __innerBuilder.AddContent(8, "Continue"); + // __innerBuilder.CloseElement(); + // }); + // builder.CloseElement(); + // } + // } + } + } +} \ No newline at end of file diff --git a/Lantean.QBTMudBlade/Components/ErrorDisplay.razor b/Lantean.QBTMudBlade/Components/ErrorDisplay.razor new file mode 100644 index 0000000..9ba8e5b --- /dev/null +++ b/Lantean.QBTMudBlade/Components/ErrorDisplay.razor @@ -0,0 +1,9 @@ + + Clear Errors + Clear Errors and Resume + +@foreach (var error in Errors) +{ + @error.Message +} + \ No newline at end of file diff --git a/Lantean.QBTMudBlade/Components/ErrorDisplay.razor.cs b/Lantean.QBTMudBlade/Components/ErrorDisplay.razor.cs new file mode 100644 index 0000000..4ce057b --- /dev/null +++ b/Lantean.QBTMudBlade/Components/ErrorDisplay.razor.cs @@ -0,0 +1,38 @@ +using Lantean.QBTMudBlade.Components.Dialogs; +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace Lantean.QBTMudBlade.Components +{ + public partial class ErrorDisplay + { + [Inject] + protected IDialogService DialogService { get; set; } = default!; + + [Parameter] + [EditorRequired] + public EnhancedErrorBoundary ErrorBoundary { get; set; } = default!; + + protected IEnumerable Errors => ErrorBoundary.Errors; + + protected async Task ShowException(Exception exception) + { + var parameters = new DialogParameters + { + { nameof(ExceptionDialog.Exception), exception } + }; + + await DialogService.ShowAsync("Error Details", parameters, DialogHelper.FormDialogOptions); + } + + protected async Task ClearErrors() + { + await ErrorBoundary.ClearErrors(); + } + + protected async Task ClearErrorsAndResumeAsync() + { + await ErrorBoundary.RecoverAndClearErrors(); + } + } +} diff --git a/Lantean.QBTMudBlade/Components/FilesTab.razor b/Lantean.QBTMudBlade/Components/FilesTab.razor index ba336e3..092fc00 100644 --- a/Lantean.QBTMudBlade/Components/FilesTab.razor +++ b/Lantean.QBTMudBlade/Components/FilesTab.razor @@ -1,4 +1,24 @@ - + + + + + + Less Than 100% Availability + Less than 80% Availability + Currently Filtered Files + + + Less Than 100% Availability + Less than 80% Availability + Currently Filtered Files + + + + + + + - - - - - - - - Less Than 100% Availability - Less than 80% Availability - Currently Filtered Files - - - Less Than 100% Availability - Less than 80% Availability - Currently Filtered Files - - - - - - - @foreach (var column in GetColumns()) @@ -73,7 +71,7 @@ @if (context.Data.IsFolder) { - + } @context.Data.DisplayName ; diff --git a/Lantean.QBTMudBlade/Components/FilesTab.razor.cs b/Lantean.QBTMudBlade/Components/FilesTab.razor.cs index 1a12021..c24ccf7 100644 --- a/Lantean.QBTMudBlade/Components/FilesTab.razor.cs +++ b/Lantean.QBTMudBlade/Components/FilesTab.razor.cs @@ -531,7 +531,7 @@ namespace Lantean.QBTMudBlade.Components return; } - var files = FileList.Values.Where(f => f.Availability < value).Select(f => f.Index); + var files = FileList.Values.Where(f => !f.IsFolder && f.Availability < value).Select(f => f.Index); if (!files.Any()) { @@ -548,7 +548,7 @@ namespace Lantean.QBTMudBlade.Components return; } - var files = GetFiles().Select(f => f.Index); + var files = GetFiles().Where(f => !f.IsFolder).Select(f => f.Index); if (!files.Any()) { diff --git a/Lantean.QBTMudBlade/Components/TorrentActions.razor b/Lantean.QBTMudBlade/Components/TorrentActions.razor index 3508a38..bfb442b 100644 --- a/Lantean.QBTMudBlade/Components/TorrentActions.razor +++ b/Lantean.QBTMudBlade/Components/TorrentActions.razor @@ -1,24 +1,24 @@ -@if (Type == ParentType.Toolbar) +@if (Type == RenderType.Toolbar) { @ToolbarContent } -else if (Type == ParentType.ToolbarContents) +else if (Type == RenderType.ToolbarContents) { @ToolbarContent } -else if (Type == ParentType.MixedToolbar) +else if (Type == RenderType.MixedToolbar) { @MixedToolbarContent } -else if (Type == ParentType.MixedToolbarContents) +else if (Type == RenderType.MixedToolbarContents) { @MixedToolbarContent } -else if (Type == ParentType.InitialIconsOnly) +else if (Type == RenderType.InitialIconsOnly) { @foreach (var option in GetOptions().Take(5)) { @@ -28,151 +28,143 @@ else if (Type == ParentType.InitialIconsOnly) } else { - + } } - - @foreach (var option in GetOptions().Skip(5)) - { - @if (option is Divider) - { - - } - else if (!option.Children.Any()) - { - - @option.Name - - } - else - { - - - - @option.Name - - - - @foreach (var childItem in option.Children) - { - @ChildItem(childItem) - } - - - - } - - } - + @Menu(GetOptions().Skip(5)); } else { - - @foreach (var option in GetOptions()) - { - - @if (option is Divider) - { - - } - else if (!option.Children.Any()) - { - @option.Name - } - else - { - - - @option.Name - + @Menu(GetOptions()); +} - +@code { + private RenderFragment ToolbarContent + { + get + { + return __builder => + { + foreach (var option in GetOptions()) + { + if (option is Divider) + { + + } + else if (!option.Children.Any()) + { + if (option.Icon is null) + { + @option.Name + } + else + { + + } + } + else + { + @foreach (var childItem in option.Children) { @ChildItem(childItem) } - - + + } } - + }; } - -} + } -@code { - private RenderFragment ToolbarContent => - @ - @foreach (var option in GetOptions()) + private RenderFragment MixedToolbarContent + { + get { - @if (option is Divider) + return __builder => { - - } - else if (!option.Children.Any()) + foreach (var option in GetOptions()) + { + if (option is Divider) + { + + } + else if (!option.Children.Any()) + { + if (option.Icon is null) + { + @option.Name + } + else + { + + } + } + else + { + + @foreach (var childItem in option.Children) + { + @ChildItem(childItem) + } + + } + } + }; + } + } + + private RenderFragment ChildItem(Action option) + { + return __builder => + { + if (option is Divider) { - if (option.Icon is null) - { - @option.Name - } - else - { - - } + } else { - - @foreach (var childItem in option.Children) - { - @ChildItem(childItem) - } - + @option.Name } - } - - ; + }; + } - private RenderFragment MixedToolbarContent => - @ - @foreach (var option in GetOptions()) + private RenderFragment Menu(IEnumerable actions) + { + return __builder => { - @if (option is Divider) - { - - } - else if (!option.Children.Any()) - { - if (option.Icon is null) + + @foreach (var option in actions) { - @option.Name - } - else - { - - } - } - else - { - - @foreach (var childItem in option.Children) + @if (option is Divider) { - @ChildItem(childItem) + } - - } - } - ; + else if (!option.Children.Any()) + { + + @option.Name + + } + else + { + + + + @option.Name + - private RenderFragment ChildItem(Action option) => - @ - @if (option is Divider) - { - - } - else - { - @option.Name - } - ; + + @foreach (var childItem in option.Children) + { + @ChildItem(childItem) + } + + + + } + } + + }; + } } \ No newline at end of file diff --git a/Lantean.QBTMudBlade/Components/TorrentActions.razor.cs b/Lantean.QBTMudBlade/Components/TorrentActions.razor.cs index b94a6d3..541d319 100644 --- a/Lantean.QBTMudBlade/Components/TorrentActions.razor.cs +++ b/Lantean.QBTMudBlade/Components/TorrentActions.razor.cs @@ -39,7 +39,7 @@ namespace Lantean.QBTMudBlade.Components /// If true this component will render as a otherwise will render as a . /// [Parameter] - public ParentType Type { get; set; } + public RenderType Type { get; set; } [CascadingParameter] public MainData MainData { get; set; } = default!; @@ -49,6 +49,8 @@ namespace Lantean.QBTMudBlade.Components protected MudMenu? ActionsMenu { get; set; } + protected bool Disabled => !Hashes.Any(); + protected async Task Pause() { await ApiClient.PauseTorrents(Hashes); @@ -73,7 +75,9 @@ namespace Lantean.QBTMudBlade.Components { savePath = torrent.SavePath; } - await DialogService.ShowSingleFieldDialog("Set Location", "Location", savePath, v => ApiClient.SetTorrentLocation(v, null, Hashes.ToArray())); + await Task.CompletedTask; + throw new InvalidOperationException("BOoooo"); + //await DialogService.ShowSingleFieldDialog("Set Location", "Location", savePath, v => ApiClient.SetTorrentLocation(v, null, Hashes.ToArray())); } protected async Task Rename() @@ -234,13 +238,12 @@ namespace Lantean.QBTMudBlade.Components private IEnumerable GetOptions() { - if (!Hashes.Any()) + Torrent? torrent = null; + if (Hashes.Any()) { - return []; + torrent = MainData.Torrents[Hashes.First()]; } - var firstTorrent = MainData.Torrents[Hashes.First()]; - var categories = new List { new Action("New", Icons.Material.Filled.Add, Color.Info, EventCallback.Factory.Create(this, AddCategory)), @@ -255,7 +258,7 @@ namespace Lantean.QBTMudBlade.Components new Action("Remove All", Icons.Material.Filled.Remove, Color.Error, EventCallback.Factory.Create(this, RemoveTags)), new Divider() }; - tags.AddRange(MainData.Tags.Select(t => new Action(t, firstTorrent.Tags.Contains(t) ? Icons.Material.Filled.CheckBox : Icons.Material.Filled.CheckBoxOutlineBlank, Color.Default, EventCallback.Factory.Create(this, () => ToggleTag(t))))); + tags.AddRange(MainData.Tags.Select(t => new Action(t, (torrent?.Tags.Contains(t) == true) ? Icons.Material.Filled.CheckBox : Icons.Material.Filled.CheckBoxOutlineBlank, Color.Default, EventCallback.Factory.Create(this, () => ToggleTag(t))))); var options = new List { @@ -268,11 +271,11 @@ namespace Lantean.QBTMudBlade.Components new Action("Rename", Icons.Material.Filled.DriveFileRenameOutline, Color.Info, EventCallback.Factory.Create(this, Rename)), new Action("Category", Icons.Material.Filled.List, Color.Info, categories, true), new Action("Tags", Icons.Material.Filled.Label, Color.Info, tags, true), - new Action("Automatic Torrent Management", Icons.Material.Filled.Check, firstTorrent.AutomaticTorrentManagement ? Color.Info : Color.Transparent, EventCallback.Factory.Create(this, ToggleAutoTMM)), + new Action("Automatic Torrent Management", Icons.Material.Filled.Check, (torrent?.AutomaticTorrentManagement == true) ? Color.Info : Color.Transparent, EventCallback.Factory.Create(this, ToggleAutoTMM)), new Divider(), new Action("Limit upload rate", Icons.Material.Filled.KeyboardDoubleArrowUp, Color.Info, EventCallback.Factory.Create(this, LimitUploadRate)), new Action("Limit share ratio", Icons.Material.Filled.Percent, Color.Warning, EventCallback.Factory.Create(this, LimitShareRatio)), - new Action("Super seeding mode", Icons.Material.Filled.Check, firstTorrent.SuperSeeding ? Color.Info : Color.Transparent, EventCallback.Factory.Create(this, ToggleSuperSeeding)), + new Action("Super seeding mode", Icons.Material.Filled.Check, (torrent?.SuperSeeding == true) ? Color.Info : Color.Transparent, EventCallback.Factory.Create(this, ToggleSuperSeeding)), new Divider(), new Action("Force recheck", Icons.Material.Filled.Loop, Color.Info, EventCallback.Factory.Create(this, ForceRecheck)), new Action("Force reannounce", Icons.Material.Filled.BroadcastOnHome, Color.Info, EventCallback.Factory.Create(this, ForceReannounce)), @@ -304,7 +307,7 @@ namespace Lantean.QBTMudBlade.Components } } - public enum ParentType + public enum RenderType { /// /// Renders toolbar contents without the wrapper. diff --git a/Lantean.QBTMudBlade/Layout/MainLayout.razor b/Lantean.QBTMudBlade/Layout/MainLayout.razor index 6437726..ecea370 100644 --- a/Lantean.QBTMudBlade/Layout/MainLayout.razor +++ b/Lantean.QBTMudBlade/Layout/MainLayout.razor @@ -1,22 +1,33 @@ @inherits LayoutComponentBase - - - + + + + -qBittorrent Web UI + qBittorrent Web UI - - - - qBittorrent Web UI - - @if (ShowMenu) - { - - } - - - @Body - - \ No newline at end of file + + + + qBittorrent Web UI + + @if (ErrorBoundary?.Errors.Count > 0) + { + + + + } + @if (ShowMenu) + { + + } + + + + + + @Body + + + \ No newline at end of file diff --git a/Lantean.QBTMudBlade/Layout/MainLayout.razor.cs b/Lantean.QBTMudBlade/Layout/MainLayout.razor.cs index 0b03b9e..54b9fc5 100644 --- a/Lantean.QBTMudBlade/Layout/MainLayout.razor.cs +++ b/Lantean.QBTMudBlade/Layout/MainLayout.razor.cs @@ -1,5 +1,7 @@ using Lantean.QBitTorrentClient; +using Lantean.QBTMudBlade.Components; using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; using MudBlazor; using MudBlazor.Services; @@ -20,10 +22,14 @@ namespace Lantean.QBTMudBlade.Layout protected bool DrawerOpen { get; set; } = true; + protected bool ErrorDrawerOpen { get; set; } = false; + protected bool ShowMenu { get; set; } = false; public Guid Id => Guid.NewGuid(); + protected EnhancedErrorBoundary? ErrorBoundary { get; set; } + ResizeOptions IBrowserViewportObserver.ResizeOptions { get; } = new() { ReportRate = 50, @@ -66,6 +72,16 @@ namespace Lantean.QBTMudBlade.Layout await InvokeAsync(StateHasChanged); } + protected void ToggleErrorDrawer() + { + ErrorDrawerOpen = !ErrorDrawerOpen; + } + + protected void Cleared() + { + ErrorDrawerOpen = false; + } + protected virtual async Task DisposeAsync(bool disposing) { if (!_disposedValue) diff --git a/Lantean.QBTMudBlade/Pages/Login.razor.cs b/Lantean.QBTMudBlade/Pages/Login.razor.cs index 351c6c5..6ffb550 100644 --- a/Lantean.QBTMudBlade/Pages/Login.razor.cs +++ b/Lantean.QBTMudBlade/Pages/Login.razor.cs @@ -49,7 +49,7 @@ namespace Lantean.QBTMudBlade.Pages #if DEBUG protected override async Task OnInitializedAsync() { - await DoLogin("admin", "p22bMTJDK"); + await DoLogin("admin", "a8hbfvNP2"); } #endif } diff --git a/Lantean.QBTMudBlade/Pages/TorrentList.razor b/Lantean.QBTMudBlade/Pages/TorrentList.razor index ab64a13..252f52c 100644 --- a/Lantean.QBTMudBlade/Pages/TorrentList.razor +++ b/Lantean.QBTMudBlade/Pages/TorrentList.razor @@ -1,6 +1,17 @@ @page "/" @layout ListLayout + + + + + + + + + + + - - - - - - - - - - - - - - - - - - @foreach (var column in GetColumns()) diff --git a/Lantean.QBTMudBlade/Pages/TorrentList.razor.cs b/Lantean.QBTMudBlade/Pages/TorrentList.razor.cs index 2a4e313..95d1849 100644 --- a/Lantean.QBTMudBlade/Pages/TorrentList.razor.cs +++ b/Lantean.QBTMudBlade/Pages/TorrentList.razor.cs @@ -70,6 +70,11 @@ namespace Lantean.QBTMudBlade.Pages } } _sortSelector ??= _columns.First(c => c.Enabled).SortSelector; + + if (SelectedTorrent is not null && Torrents is not null && !Torrents.Contains(SelectedTorrent)) + { + SelectedTorrent = null; + } } private IEnumerable? GetOrderedTorrents() diff --git a/Lantean.QBTMudBlade/Program.cs b/Lantean.QBTMudBlade/Program.cs index d4856e9..c4f311a 100644 --- a/Lantean.QBTMudBlade/Program.cs +++ b/Lantean.QBTMudBlade/Program.cs @@ -26,11 +26,15 @@ namespace Lantean.QBTMudBlade #endif builder.Services.AddTransient(); + builder.Services.AddScoped(); builder.Services .AddScoped(sp => sp .GetRequiredService() .CreateClient("API")) - .AddHttpClient("API", client => client.BaseAddress = new Uri(baseAddress, "/api/v2/")).AddHttpMessageHandler(); + .AddHttpClient("API", client => client.BaseAddress = new Uri(baseAddress, "/api/v2/")) + .AddHttpMessageHandler() + .RemoveAllLoggers() + .AddLogger(wrapHandlersPipeline: true); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -39,6 +43,12 @@ namespace Lantean.QBTMudBlade builder.Services.AddBlazoredLocalStorage(); builder.Services.AddSingleton(); +#if DEBUG + builder.Logging.SetMinimumLevel(LogLevel.Information); +#else + builder.Logging.SetMinimumLevel(LogLevel.Error); +#endif + await builder.Build().RunAsync(); } } diff --git a/Lantean.QBTMudBlade/Services/DataManager.cs b/Lantean.QBTMudBlade/Services/DataManager.cs index 5e6fe28..d3240bc 100644 --- a/Lantean.QBTMudBlade/Services/DataManager.cs +++ b/Lantean.QBTMudBlade/Services/DataManager.cs @@ -1,5 +1,6 @@ using Lantean.QBTMudBlade.Models; using MudBlazor; +using System.Linq; using System.Reflection; using System.Threading.Channels; @@ -173,8 +174,8 @@ namespace Lantean.QBTMudBlade.Services { foreach (var hash in mainData.TorrentsRemoved) { - torrentList.Torrents.Remove(hash); RemoveTorrentFromStates(torrentList, hash); + torrentList.Torrents.Remove(hash); } } @@ -279,12 +280,20 @@ namespace Lantean.QBTMudBlade.Services torrentList.TagState[FilterHelper.TAG_UNTAGGED].AddIfTrueOrRemove(hash, FilterHelper.FilterTag(torrent, FilterHelper.TAG_UNTAGGED)); foreach (var tag in torrentList.Tags) { + if (!torrentList.TagState.ContainsKey(tag)) + { + torrentList.TagState.Add(tag, []); + } torrentList.TagState[tag].AddIfTrueOrRemove(hash, FilterHelper.FilterTag(torrent, tag)); } torrentList.CategoriesState[FilterHelper.CATEGORY_UNCATEGORIZED].AddIfTrueOrRemove(hash, FilterHelper.FilterCategory(torrent, FilterHelper.CATEGORY_UNCATEGORIZED, torrentList.ServerState.UseSubcategories)); foreach (var category in torrentList.Categories.Keys) { + if (!torrentList.CategoriesState.ContainsKey(category)) + { + torrentList.CategoriesState.Add(category, []); + } torrentList.CategoriesState[category].AddIfTrueOrRemove(hash, FilterHelper.FilterCategory(torrent, category, torrentList.ServerState.UseSubcategories)); } @@ -296,6 +305,10 @@ namespace Lantean.QBTMudBlade.Services torrentList.TrackersState[FilterHelper.TRACKER_TRACKERLESS].AddIfTrueOrRemove(hash, FilterHelper.FilterTracker(torrent, FilterHelper.TRACKER_TRACKERLESS)); foreach (var tracker in torrentList.Trackers.Keys) { + if (!torrentList.TrackersState.ContainsKey(tracker)) + { + torrentList.TrackersState.Add(tracker, []); + } torrentList.TrackersState[tracker].AddIfTrueOrRemove(hash, FilterHelper.FilterTracker(torrent, tracker)); } } diff --git a/Lantean.QBTMudBlade/Services/HttpLogger.cs b/Lantean.QBTMudBlade/Services/HttpLogger.cs new file mode 100644 index 0000000..aee0e89 --- /dev/null +++ b/Lantean.QBTMudBlade/Services/HttpLogger.cs @@ -0,0 +1,49 @@ +using Microsoft.Extensions.Http.Logging; + +namespace Lantean.QBTMudBlade.Services +{ + public class HttpLogger : IHttpClientLogger + { + private readonly ILogger _logger; + + public HttpLogger(ILogger logger) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public object? LogRequestStart(HttpRequestMessage request) + { + //_logger.LogInformation( + // "Sending '{Request.Method}' to '{Request.Host}{Request.Path}'", + // request.Method, + // request.RequestUri?.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped), + // request.RequestUri!.PathAndQuery); + return null; + } + + public void LogRequestStop( + object? context, HttpRequestMessage request, HttpResponseMessage response, TimeSpan elapsed) + { + //_logger.LogInformation( + // "Received '{Response.StatusCodeInt} {Response.StatusCodeString}' after {Response.ElapsedMilliseconds}ms", + // (int)response.StatusCode, + // response.StatusCode, + // elapsed.TotalMilliseconds.ToString("F1")); + } + + public void LogRequestFailed( + object? context, + HttpRequestMessage request, + HttpResponseMessage? response, + Exception exception, + TimeSpan elapsed) + { + _logger.LogError( + exception, + "Request towards '{Request.Host}{Request.Path}' failed after {Response.ElapsedMilliseconds}ms", + request.RequestUri?.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped), + request.RequestUri!.PathAndQuery, + elapsed.TotalMilliseconds.ToString("F1")); + } + } +} diff --git a/Lantean.QBTMudBlade/wwwroot/css/app.css b/Lantean.QBTMudBlade/wwwroot/css/app.css index a218aa0..997ca0a 100644 --- a/Lantean.QBTMudBlade/wwwroot/css/app.css +++ b/Lantean.QBTMudBlade/wwwroot/css/app.css @@ -148,4 +148,10 @@ td.no-wrap { top: -2px; right: -5px; transform: rotate(270deg); - } \ No newline at end of file + } + +.overflow { + overflow: auto; + height: 200px; + width: 100%; +} \ No newline at end of file diff --git a/Lantean.QBTMudBlade/wwwroot/index.html b/Lantean.QBTMudBlade/wwwroot/index.html index 7bd6672..ded362e 100644 --- a/Lantean.QBTMudBlade/wwwroot/index.html +++ b/Lantean.QBTMudBlade/wwwroot/index.html @@ -23,7 +23,7 @@
-
+
An unhandled error has occurred. Reload 🗙 diff --git a/Lantean.QBitTorrentClient/ApiClient.cs b/Lantean.QBitTorrentClient/ApiClient.cs index 3461ad6..ce7ca4f 100644 --- a/Lantean.QBitTorrentClient/ApiClient.cs +++ b/Lantean.QBitTorrentClient/ApiClient.cs @@ -476,7 +476,7 @@ namespace Lantean.QBitTorrentClient { foreach (var (name, stream) in torrents) { - content.Add(new StreamContent(stream), name); + content.Add(new StreamContent(stream), "torrents", name); } } if (savePath is not null)