mirror of
https://github.com/lantean-code/qbtmud.git
synced 2025-10-23 04:52:22 +00:00
Add initial RSS implementation
This commit is contained in:
4
.editorconfig
Normal file
4
.editorconfig
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[*.cs]
|
||||||
|
|
||||||
|
# IDE0290: Use primary constructor
|
||||||
|
csharp_style_prefer_primary_constructors = false
|
@@ -9,6 +9,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lantean.QBitTorrentClient",
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lantean.QBTMudBlade", "Lantean.QBTMudBlade\Lantean.QBTMudBlade.csproj", "{83BC76CC-D51B-42AF-A6EE-FA400C300098}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lantean.QBTMudBlade", "Lantean.QBTMudBlade\Lantean.QBTMudBlade.csproj", "{83BC76CC-D51B-42AF-A6EE-FA400C300098}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1BF1A631-87D7-4039-A701-88C5E0234B63}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
.editorconfig = .editorconfig
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@@ -4,9 +4,6 @@ using Microsoft.AspNetCore.Components;
|
|||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
using Lantean.QBTMudBlade.Helpers;
|
using Lantean.QBTMudBlade.Helpers;
|
||||||
using Lantean.QBTMudBlade.Models;
|
using Lantean.QBTMudBlade.Models;
|
||||||
using System;
|
|
||||||
using Lantean.QBTMudBlade.Pages;
|
|
||||||
using static MudBlazor.CategoryTypes;
|
|
||||||
|
|
||||||
namespace Lantean.QBTMudBlade.Components
|
namespace Lantean.QBTMudBlade.Components
|
||||||
{
|
{
|
||||||
@@ -26,20 +23,39 @@ namespace Lantean.QBTMudBlade.Components
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public bool IsMenu { get; set; }
|
public bool IsMenu { get; set; }
|
||||||
|
|
||||||
protected IEnumerable<UIAction> Actions => _actions ?? [];
|
[Parameter]
|
||||||
|
[EditorRequired]
|
||||||
|
public Preferences? Preferences { get; set; }
|
||||||
|
|
||||||
|
protected IEnumerable<UIAction> Actions => GetActions();
|
||||||
|
|
||||||
|
private IEnumerable<UIAction> GetActions()
|
||||||
|
{
|
||||||
|
if (_actions is not null)
|
||||||
|
{
|
||||||
|
foreach (var action in _actions)
|
||||||
|
{
|
||||||
|
if (action.Name != "rss" || Preferences is not null && Preferences.RssProcessingEnabled)
|
||||||
|
{
|
||||||
|
yield return action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
_actions =
|
_actions =
|
||||||
[
|
[
|
||||||
new("Statistics", "Statistics", Icons.Material.Filled.PieChart, Color.Default, "/statistics"),
|
new("statistics", "Statistics", Icons.Material.Filled.PieChart, Color.Default, "/statistics"),
|
||||||
new("Search", "Search", Icons.Material.Filled.Search, Color.Default, "/search"),
|
new("search", "Search", Icons.Material.Filled.Search, Color.Default, "/search"),
|
||||||
new("RSS", "RSS", Icons.Material.Filled.RssFeed, Color.Default, "/rss"),
|
new("rss", "RSS", Icons.Material.Filled.RssFeed, Color.Default, "/rss"),
|
||||||
new("Execution Log", "Execution Log", Icons.Material.Filled.List, Color.Default, "/log"),
|
new("log", "Execution Log", Icons.Material.Filled.List, Color.Default, "/log"),
|
||||||
new("Blocked IPs", "Blocked IPs", Icons.Material.Filled.DisabledByDefault, Color.Default, "/blocks"),
|
new("blocks", "Blocked IPs", Icons.Material.Filled.DisabledByDefault, Color.Default, "/blocks"),
|
||||||
new("Tag Management", "Tag Management", Icons.Material.Filled.Label, Color.Default, "/tags", separatorBefore: true),
|
new("tags", "Tag Management", Icons.Material.Filled.Label, Color.Default, "/tags", separatorBefore: true),
|
||||||
new("Category Management", "Category Management", Icons.Material.Filled.List, Color.Default, "/categories"),
|
new("categories", "Category Management", Icons.Material.Filled.List, Color.Default, "/categories"),
|
||||||
new("Settings", "Settings", Icons.Material.Filled.Settings, Color.Default, "/settings", separatorBefore: true),
|
new("settings", "Settings", Icons.Material.Filled.Settings, Color.Default, "/settings", separatorBefore: true),
|
||||||
|
new("about", "About", Icons.Material.Filled.Info, Color.Default, "/about"),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +82,7 @@ namespace Lantean.QBTMudBlade.Components
|
|||||||
{
|
{
|
||||||
await ApiClient.Logout();
|
await ApiClient.Logout();
|
||||||
|
|
||||||
NavigationManager.NavigateTo("/login", true);
|
NavigationManager.NavigateTo("/", true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -19,12 +19,23 @@ namespace Lantean.QBTMudBlade.Components.Dialogs
|
|||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string? Url { get; set; }
|
||||||
|
|
||||||
protected MudTextField<string?>? UrlsTextField { get; set; }
|
protected MudTextField<string?>? UrlsTextField { get; set; }
|
||||||
|
|
||||||
protected string? Urls { get; set; }
|
protected string? Urls { get; set; }
|
||||||
|
|
||||||
protected AddTorrentOptions TorrentOptions { get; set; } = default!;
|
protected AddTorrentOptions TorrentOptions { get; set; } = default!;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
if (Url is not null)
|
||||||
|
{
|
||||||
|
Urls = Url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
<FieldSwitch Label="Ratio" Value="RatioEnabled" ValueChanged="RatioEnabledChanged" Disabled="@(!CustomEnabled)" />
|
<FieldSwitch Label="Ratio" Value="RatioEnabled" ValueChanged="RatioEnabledChanged" Disabled="@(!CustomEnabled)" />
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="9">
|
<MudItem xs="9">
|
||||||
<MudNumericField T="float" Value="Ratio" ValueChanged="RatioChanged" Disabled="@(!(CustomEnabled && RatioEnabled))" Min="1" Max="1024000" Format="F2" Variant="Variant.Outlined" />
|
<MudNumericField T="float" Value="Ratio" ValueChanged="RatioChanged" Disabled="@(!(CustomEnabled && RatioEnabled))" Min="0" Max="1024000" Step="0.1F" Format="F2" Variant="Variant.Outlined" />
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="3">
|
<MudItem xs="3">
|
||||||
<FieldSwitch Label="Total minutes" Value="TotalMinutesEnabled" ValueChanged="TotalMinutesEnabledChanged" Disabled="@(!CustomEnabled)" />
|
<FieldSwitch Label="Total minutes" Value="TotalMinutesEnabled" ValueChanged="TotalMinutesEnabledChanged" Disabled="@(!CustomEnabled)" />
|
||||||
|
@@ -22,7 +22,7 @@ namespace Lantean.QBTMudBlade.Components
|
|||||||
|
|
||||||
protected override Task OnErrorAsync(Exception exception)
|
protected override Task OnErrorAsync(Exception exception)
|
||||||
{
|
{
|
||||||
Logger.LogError(exception, exception.Message);
|
Logger.LogError(exception, "An application error occurred: {message}.", exception.Message);
|
||||||
_exceptions.Add(exception);
|
_exceptions.Add(exception);
|
||||||
|
|
||||||
if (Disabled)
|
if (Disabled)
|
||||||
|
@@ -1,3 +1,6 @@
|
|||||||
<MudMenu Icon="@Icons.Material.Filled.MoreVert" Color="Color.Inherit" Dense="true" AnchorOrigin="Origin.BottomRight" TransformOrigin="Origin.TopLeft">
|
@if (_isVisible)
|
||||||
<ApplicationActions IsMenu="true" />
|
{
|
||||||
</MudMenu>
|
<MudMenu Icon="@Icons.Material.Filled.MoreVert" Color="Color.Inherit" Dense="true" AnchorOrigin="Origin.BottomRight" TransformOrigin="Origin.TopLeft">
|
||||||
|
<ApplicationActions IsMenu="true" Preferences="Preferences" />
|
||||||
|
</MudMenu>
|
||||||
|
}
|
@@ -8,6 +8,10 @@ namespace Lantean.QBTMudBlade.Components
|
|||||||
{
|
{
|
||||||
public partial class Menu
|
public partial class Menu
|
||||||
{
|
{
|
||||||
|
private bool _isVisible = false;
|
||||||
|
|
||||||
|
private Preferences? _preferences;
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
protected NavigationManager NavigationManager { get; set; } = default!;
|
protected NavigationManager NavigationManager { get; set; } = default!;
|
||||||
|
|
||||||
@@ -17,6 +21,16 @@ namespace Lantean.QBTMudBlade.Components
|
|||||||
[Inject]
|
[Inject]
|
||||||
protected IApiClient ApiClient { get; set; } = default!;
|
protected IApiClient ApiClient { get; set; } = default!;
|
||||||
|
|
||||||
|
protected Preferences? Preferences => _preferences;
|
||||||
|
|
||||||
|
public void ShowMenu(Preferences? preferences = null)
|
||||||
|
{
|
||||||
|
_isVisible = true;
|
||||||
|
_preferences = preferences;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
protected async Task ResetWebUI()
|
protected async Task ResetWebUI()
|
||||||
{
|
{
|
||||||
var preferences = new UpdatePreferences
|
var preferences = new UpdatePreferences
|
||||||
|
@@ -90,21 +90,21 @@ namespace Lantean.QBTMudBlade.Components
|
|||||||
new("firstLastPiecePrio", "Download first and last pieces first", Icons.Material.Filled.Check, Color.Info, CreateCallback(DownloadFirstLast)),
|
new("firstLastPiecePrio", "Download first and last pieces first", Icons.Material.Filled.Check, Color.Info, CreateCallback(DownloadFirstLast)),
|
||||||
new("forceRecheck", "Force recheck", Icons.Material.Filled.Loop, Color.Info, CreateCallback(ForceRecheck), separatorBefore: true),
|
new("forceRecheck", "Force recheck", Icons.Material.Filled.Loop, Color.Info, CreateCallback(ForceRecheck), separatorBefore: true),
|
||||||
new("forceReannounce", "Force reannounce", Icons.Material.Filled.BroadcastOnHome, Color.Info, CreateCallback(ForceReannounce)),
|
new("forceReannounce", "Force reannounce", Icons.Material.Filled.BroadcastOnHome, Color.Info, CreateCallback(ForceReannounce)),
|
||||||
new("queue", "Queue", Icons.Material.Filled.Queue, Color.Transparent, new List<UIAction>
|
new("queue", "Queue", Icons.Material.Filled.Queue, Color.Transparent,
|
||||||
{
|
[
|
||||||
new("queueTop", "Move to top", Icons.Material.Filled.VerticalAlignTop, Color.Inherit, CreateCallback(MoveToTop)),
|
new("queueTop", "Move to top", Icons.Material.Filled.VerticalAlignTop, Color.Inherit, CreateCallback(MoveToTop)),
|
||||||
new("queueUp", "Move up", Icons.Material.Filled.ArrowUpward, Color.Inherit, CreateCallback(MoveUp)),
|
new("queueUp", "Move up", Icons.Material.Filled.ArrowUpward, Color.Inherit, CreateCallback(MoveUp)),
|
||||||
new("queueDown", "Move down", Icons.Material.Filled.ArrowDownward, Color.Inherit, CreateCallback(MoveDown)),
|
new("queueDown", "Move down", Icons.Material.Filled.ArrowDownward, Color.Inherit, CreateCallback(MoveDown)),
|
||||||
new("queueBottom", "Move to bottom", Icons.Material.Filled.VerticalAlignBottom, Color.Inherit, CreateCallback(MoveToBottom)),
|
new("queueBottom", "Move to bottom", Icons.Material.Filled.VerticalAlignBottom, Color.Inherit, CreateCallback(MoveToBottom)),
|
||||||
}, separatorBefore: true),
|
], separatorBefore: true),
|
||||||
new("copy", "Copy", Icons.Material.Filled.FolderCopy, Color.Info, new List<UIAction>
|
new("copy", "Copy", Icons.Material.Filled.FolderCopy, Color.Info,
|
||||||
{
|
[
|
||||||
new("copyName", "Name", Icons.Material.Filled.TextFields, Color.Info, CreateCallback(() => Copy(t => t.Name))),
|
new("copyName", "Name", Icons.Material.Filled.TextFields, Color.Info, CreateCallback(() => Copy(t => t.Name))),
|
||||||
new("copyHashv1", "Info hash v1", Icons.Material.Filled.Tag, Color.Info, CreateCallback(() => Copy(t => t.InfoHashV1))),
|
new("copyHashv1", "Info hash v1", Icons.Material.Filled.Tag, Color.Info, CreateCallback(() => Copy(t => t.InfoHashV1))),
|
||||||
new("copyHashv2", "Info hash v2", Icons.Material.Filled.Tag, Color.Info, CreateCallback(() => Copy(t => t.InfoHashV2))),
|
new("copyHashv2", "Info hash v2", Icons.Material.Filled.Tag, Color.Info, CreateCallback(() => Copy(t => t.InfoHashV2))),
|
||||||
new("copyMagnet", "Magnet link", Icons.Material.Filled.TextFields, Color.Info, CreateCallback(() => Copy(t => t.MagnetUri))),
|
new("copyMagnet", "Magnet link", Icons.Material.Filled.TextFields, Color.Info, CreateCallback(() => Copy(t => t.MagnetUri))),
|
||||||
new("copyId", "Torrent ID", Icons.Material.Filled.TextFields, Color.Info, CreateCallback(() => Copy(t => t.Hash))),
|
new("copyId", "Torrent ID", Icons.Material.Filled.TextFields, Color.Info, CreateCallback(() => Copy(t => t.Hash))),
|
||||||
}),
|
]),
|
||||||
new("export", "Export", Icons.Material.Filled.SaveAlt, Color.Info, CreateCallback(Export)),
|
new("export", "Export", Icons.Material.Filled.SaveAlt, Color.Info, CreateCallback(Export)),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ using Lantean.QBitTorrentClient.Models;
|
|||||||
using Lantean.QBTMudBlade.Components.UI;
|
using Lantean.QBTMudBlade.Components.UI;
|
||||||
using Lantean.QBTMudBlade.Helpers;
|
using Lantean.QBTMudBlade.Helpers;
|
||||||
using Lantean.QBTMudBlade.Interop;
|
using Lantean.QBTMudBlade.Interop;
|
||||||
|
using Lantean.QBTMudBlade.Models;
|
||||||
using Lantean.QBTMudBlade.Services;
|
using Lantean.QBTMudBlade.Services;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using Blazored.LocalStorage;
|
using Blazored.LocalStorage;
|
||||||
using Lantean.QBTMudBlade.Helpers;
|
using Lantean.QBTMudBlade.Helpers;
|
||||||
|
using Lantean.QBTMudBlade.Models;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.AspNetCore.Components.Web;
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using Lantean.QBitTorrentClient;
|
using Lantean.QBitTorrentClient;
|
||||||
using Lantean.QBitTorrentClient.Models;
|
using Lantean.QBitTorrentClient.Models;
|
||||||
|
using Lantean.QBTMudBlade.Models;
|
||||||
using Lantean.QBTMudBlade.Services;
|
using Lantean.QBTMudBlade.Services;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
@@ -5,4 +5,3 @@
|
|||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
[assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "<Pending>", Scope = "member", Target = "~M:qtmud.Models.Torrent.#ctor(System.String,System.DateTimeOffset,System.Int64,System.Boolean,System.Single,System.String,System.Int64,System.DateTimeOffset,System.String,System.Int64,System.Int64,System.Int64,System.Int64,System.Int64,System.Boolean,System.Boolean,System.String,System.String,System.DateTimeOffset,System.String,System.Single,System.Int32,System.String,System.Int32,System.Int32,System.Int32,System.Int32,System.Single,System.Single,System.Single,System.String,System.DateTimeOffset,System.Int32,System.DateTimeOffset,System.Boolean,System.Int64,System.String,System.Boolean,System.Collections.Generic.IEnumerable{System.String},System.DateTimeOffset,System.Int64,System.String,System.Int64,System.Int64,System.Int64,System.Int64)")]
|
|
@@ -64,9 +64,14 @@ namespace Lantean.QBTMudBlade.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task InvokeAddTorrentLinkDialog(this IDialogService dialogService, IApiClient apiClient)
|
public static async Task InvokeAddTorrentLinkDialog(this IDialogService dialogService, IApiClient apiClient, string? url = null)
|
||||||
{
|
{
|
||||||
var result = await dialogService.ShowAsync<AddTorrentLinkDialog>("Download from URLs", FormDialogOptions);
|
var parameters = new DialogParameters
|
||||||
|
{
|
||||||
|
{ nameof(AddTorrentLinkDialog.Url), url }
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await dialogService.ShowAsync<AddTorrentLinkDialog>("Download from URLs", parameters, FormDialogOptions);
|
||||||
var dialogResult = await result.Result;
|
var dialogResult = await result.Result;
|
||||||
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
|
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
|
||||||
{
|
{
|
||||||
|
@@ -11,11 +11,13 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
||||||
<PackageReference Include="ByteSize" Version="2.1.2" />
|
<PackageReference Include="ByteSize" Version="2.1.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.7" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.8" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.7" PrivateAssets="all" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.8" PrivateAssets="all" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||||
<PackageReference Include="MudBlazor" Version="7.5.0" />
|
<PackageReference Include="MudBlazor" Version="7.6.0" />
|
||||||
<PackageReference Include="MudBlazor.ThemeManager" Version="2.0.0" />
|
<PackageReference Include="MudBlazor.ThemeManager" Version="2.0.0" />
|
||||||
|
<!-- added to fix vuln in dependency -->
|
||||||
|
<PackageReference Include="System.Text.Json" Version="8.0.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using Lantean.QBitTorrentClient;
|
using Lantean.QBitTorrentClient;
|
||||||
|
using Lantean.QBTMudBlade.Components;
|
||||||
using Lantean.QBTMudBlade.Helpers;
|
using Lantean.QBTMudBlade.Helpers;
|
||||||
using Lantean.QBTMudBlade.Models;
|
using Lantean.QBTMudBlade.Models;
|
||||||
using Lantean.QBTMudBlade.Services;
|
using Lantean.QBTMudBlade.Services;
|
||||||
@@ -28,6 +29,9 @@ namespace Lantean.QBTMudBlade.Layout
|
|||||||
[CascadingParameter(Name = "DrawerOpen")]
|
[CascadingParameter(Name = "DrawerOpen")]
|
||||||
public bool DrawerOpen { get; set; }
|
public bool DrawerOpen { get; set; }
|
||||||
|
|
||||||
|
[CascadingParameter]
|
||||||
|
public Menu? Menu { get; set; }
|
||||||
|
|
||||||
protected MainData? MainData { get; set; }
|
protected MainData? MainData { get; set; }
|
||||||
|
|
||||||
protected string Category { get; set; } = FilterHelper.CATEGORY_ALL;
|
protected string Category { get; set; } = FilterHelper.CATEGORY_ALL;
|
||||||
@@ -85,6 +89,8 @@ namespace Lantean.QBTMudBlade.Layout
|
|||||||
_refreshInterval = MainData.ServerState.RefreshInterval;
|
_refreshInterval = MainData.ServerState.RefreshInterval;
|
||||||
|
|
||||||
IsAuthenticated = true;
|
IsAuthenticated = true;
|
||||||
|
|
||||||
|
Menu?.ShowMenu(Preferences);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
@@ -21,17 +21,16 @@
|
|||||||
</MudBadge>
|
</MudBadge>
|
||||||
}
|
}
|
||||||
<MudSwitch T="bool" Label="Dark Mode" LabelPosition="LabelPosition.End" Value="IsDarkMode" ValueChanged="DarkModeChanged" Class="pl-3" />
|
<MudSwitch T="bool" Label="Dark Mode" LabelPosition="LabelPosition.End" Value="IsDarkMode" ValueChanged="DarkModeChanged" Class="pl-3" />
|
||||||
@if (ShowMenu)
|
<Menu @ref="Menu" />
|
||||||
{
|
|
||||||
<Menu />
|
|
||||||
}
|
|
||||||
</MudAppBar>
|
</MudAppBar>
|
||||||
<MudDrawer Open="ErrorDrawerOpen" ClipMode="DrawerClipMode.Docked" Elevation="2" Anchor="Anchor.Right">
|
<MudDrawer Open="ErrorDrawerOpen" ClipMode="DrawerClipMode.Docked" Elevation="2" Anchor="Anchor.Right">
|
||||||
<ErrorDisplay ErrorBoundary="ErrorBoundary" />
|
<ErrorDisplay ErrorBoundary="ErrorBoundary" />
|
||||||
</MudDrawer>
|
</MudDrawer>
|
||||||
<CascadingValue Value="Theme">
|
<CascadingValue Value="Theme">
|
||||||
<CascadingValue Value="IsDarkMode" Name="IsDarkMode">
|
<CascadingValue Value="IsDarkMode" Name="IsDarkMode">
|
||||||
@Body
|
<CascadingValue Value="Menu">
|
||||||
|
@Body
|
||||||
|
</CascadingValue>
|
||||||
</CascadingValue>
|
</CascadingValue>
|
||||||
</CascadingValue>
|
</CascadingValue>
|
||||||
</MudLayout>
|
</MudLayout>
|
||||||
|
@@ -30,8 +30,6 @@ namespace Lantean.QBTMudBlade.Layout
|
|||||||
|
|
||||||
protected bool ErrorDrawerOpen { get; set; } = false;
|
protected bool ErrorDrawerOpen { get; set; } = false;
|
||||||
|
|
||||||
protected bool ShowMenu { get; set; } = false;
|
|
||||||
|
|
||||||
public Guid Id => Guid.NewGuid();
|
public Guid Id => Guid.NewGuid();
|
||||||
|
|
||||||
protected EnhancedErrorBoundary? ErrorBoundary { get; set; }
|
protected EnhancedErrorBoundary? ErrorBoundary { get; set; }
|
||||||
@@ -40,6 +38,8 @@ namespace Lantean.QBTMudBlade.Layout
|
|||||||
|
|
||||||
protected MudThemeProvider MudThemeProvider { get; set; } = default!;
|
protected MudThemeProvider MudThemeProvider { get; set; } = default!;
|
||||||
|
|
||||||
|
private Menu Menu { get; set; } = default!;
|
||||||
|
|
||||||
ResizeOptions IBrowserViewportObserver.ResizeOptions { get; } = new()
|
ResizeOptions IBrowserViewportObserver.ResizeOptions { get; } = new()
|
||||||
{
|
{
|
||||||
ReportRate = 50,
|
ReportRate = 50,
|
||||||
@@ -62,11 +62,6 @@ namespace Lantean.QBTMudBlade.Layout
|
|||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
if (!ShowMenu)
|
|
||||||
{
|
|
||||||
ShowMenu = await ApiClient.CheckAuthState();
|
|
||||||
}
|
|
||||||
|
|
||||||
var drawerOpen = await LocalStorage.GetItemAsync<bool?>(_drawerOpenStorageKey);
|
var drawerOpen = await LocalStorage.GetItemAsync<bool?>(_drawerOpenStorageKey);
|
||||||
if (drawerOpen is not null)
|
if (drawerOpen is not null)
|
||||||
{
|
{
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<MudDrawer Open="DrawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2" Overlay="false">
|
<MudDrawer Open="DrawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2" Overlay="false">
|
||||||
<MudNavMenu>
|
<MudNavMenu>
|
||||||
<ApplicationActions IsMenu="false" />
|
<ApplicationActions IsMenu="false" Preferences="Preferences" />
|
||||||
</MudNavMenu>
|
</MudNavMenu>
|
||||||
</MudDrawer>
|
</MudDrawer>
|
||||||
<MudMainContent>
|
<MudMainContent>
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.AspNetCore.Components;
|
using Lantean.QBitTorrentClient.Models;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
namespace Lantean.QBTMudBlade.Layout
|
namespace Lantean.QBTMudBlade.Layout
|
||||||
{
|
{
|
||||||
@@ -6,5 +7,8 @@ namespace Lantean.QBTMudBlade.Layout
|
|||||||
{
|
{
|
||||||
[CascadingParameter(Name = "DrawerOpen")]
|
[CascadingParameter(Name = "DrawerOpen")]
|
||||||
public bool DrawerOpen { get; set; }
|
public bool DrawerOpen { get; set; }
|
||||||
|
|
||||||
|
[CascadingParameter]
|
||||||
|
public Preferences? Preferences { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,16 +1,8 @@
|
|||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
|
|
||||||
namespace Lantean.QBTMudBlade
|
namespace Lantean.QBTMudBlade.Models
|
||||||
{
|
{
|
||||||
public static class TableHelper
|
|
||||||
{
|
|
||||||
public static void CreateColumn(string name)
|
|
||||||
{
|
|
||||||
// do it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ColumnDefinition<T>
|
public class ColumnDefinition<T>
|
||||||
{
|
{
|
||||||
public ColumnDefinition(string header, Func<T, object?> sortSelector, Func<T, string>? formatter = null, string? tdClass = null, int? width = null)
|
public ColumnDefinition(string header, Func<T, object?> sortSelector, Func<T, string>? formatter = null, string? tdClass = null, int? width = null)
|
||||||
@@ -61,25 +53,4 @@ namespace Lantean.QBTMudBlade
|
|||||||
return new RowContext<T>(Header, data, Formatter is null ? SortSelector : Formatter);
|
return new RowContext<T>(Header, data, Formatter is null ? SortSelector : Formatter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public record RowContext<T>
|
|
||||||
{
|
|
||||||
private readonly Func<T, object?> _valueGetter;
|
|
||||||
|
|
||||||
public RowContext(string headerText, T data, Func<T, object?> valueGetter)
|
|
||||||
{
|
|
||||||
HeaderText = headerText;
|
|
||||||
Data = data;
|
|
||||||
_valueGetter = valueGetter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string HeaderText { get; }
|
|
||||||
|
|
||||||
public T Data { get; set; }
|
|
||||||
|
|
||||||
public object? GetValue()
|
|
||||||
{
|
|
||||||
return _valueGetter(Data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
namespace Lantean.QBTMudBlade.Models
|
namespace Lantean.QBTMudBlade.Models
|
||||||
{
|
{
|
||||||
public struct FilterState
|
public readonly struct FilterState
|
||||||
{
|
{
|
||||||
public FilterState(string category, Status status, string tag, string tracker, bool useSubcategories, string? terms)
|
public FilterState(string category, Status status, string tag, string tracker, bool useSubcategories, string? terms)
|
||||||
{
|
{
|
||||||
|
@@ -9,7 +9,9 @@
|
|||||||
|
|
||||||
public int? LastKnownId { get; set; }
|
public int? LastKnownId { get; set; }
|
||||||
|
|
||||||
|
#pragma warning disable IDE0028 // Simplify collection initialization - the SelectedValues of MudSelect has issues with the type being HashSet<string> but it needs to be.
|
||||||
public IEnumerable<string> SelectedTypes { get; set; } = new HashSet<string>();
|
public IEnumerable<string> SelectedTypes { get; set; } = new HashSet<string>();
|
||||||
|
#pragma warning restore IDE0028 // Simplify collection initialization
|
||||||
|
|
||||||
public string? Criteria { get; set; }
|
public string? Criteria { get; set; }
|
||||||
}
|
}
|
||||||
|
24
Lantean.QBTMudBlade/Models/RowContext.cs
Normal file
24
Lantean.QBTMudBlade/Models/RowContext.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
namespace Lantean.QBTMudBlade.Models
|
||||||
|
{
|
||||||
|
|
||||||
|
public record RowContext<T>
|
||||||
|
{
|
||||||
|
private readonly Func<T, object?> _valueGetter;
|
||||||
|
|
||||||
|
public RowContext(string headerText, T data, Func<T, object?> valueGetter)
|
||||||
|
{
|
||||||
|
HeaderText = headerText;
|
||||||
|
Data = data;
|
||||||
|
_valueGetter = valueGetter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string HeaderText { get; }
|
||||||
|
|
||||||
|
public T Data { get; set; }
|
||||||
|
|
||||||
|
public object? GetValue()
|
||||||
|
{
|
||||||
|
return _valueGetter(Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -48,7 +48,7 @@ namespace Lantean.QBTMudBlade.Pages
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
protected override Task OnInitializedAsync()
|
protected override Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
return DoLogin("admin", "V9VpmhCvv");
|
return DoLogin("admin", "dhT67PMAe");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@@ -81,21 +81,8 @@ namespace Lantean.QBTMudBlade.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async Task NavigateBack()
|
protected void NavigateBack()
|
||||||
{
|
{
|
||||||
//if (UpdatePreferences is null)
|
|
||||||
//{
|
|
||||||
// NavigationManager.NavigateTo("/");
|
|
||||||
// return;
|
|
||||||
//}
|
|
||||||
|
|
||||||
//await DialogService.ShowConfirmDialog(
|
|
||||||
// "Unsaved Changed",
|
|
||||||
// "Are you sure you want to leave without saving your changes?",
|
|
||||||
// () => NavigationManager.NavigateTo("/"));
|
|
||||||
|
|
||||||
await Task.CompletedTask;
|
|
||||||
|
|
||||||
NavigationManager.NavigateTo("/");
|
NavigationManager.NavigateTo("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,10 +7,66 @@
|
|||||||
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" title="Back to torrent list" />
|
<MudIconButton Icon="@Icons.Material.Outlined.NavigateBefore" OnClick="NavigateBack" title="Back to torrent list" />
|
||||||
<MudDivider Vertical="true" />
|
<MudDivider Vertical="true" />
|
||||||
}
|
}
|
||||||
|
<MudIconButton Icon="@Icons.Material.Outlined.Subscriptions" OnClick="NewSubscription" title="New subscription" />
|
||||||
|
<MudIconButton Icon="@Icons.Material.Outlined.MarkEmailRead" OnClick="MarkAsRead" title="Mark items read" />
|
||||||
|
<MudIconButton Icon="@Icons.Material.Outlined.Update" OnClick="UpdateAll" title="Update all" />
|
||||||
|
<MudDivider Vertical="true" />
|
||||||
|
<MudIconButton Icon="@Icons.Material.Outlined.DownloadForOffline" OnClick="EditDownloadRules" title="Edit auto downloading rules" />
|
||||||
<MudDivider Vertical="true" />
|
<MudDivider Vertical="true" />
|
||||||
<MudText Class="pl-5 no-wrap">RSS</MudText>
|
<MudText Class="pl-5 no-wrap">RSS</MudText>
|
||||||
</MudToolBar>
|
</MudToolBar>
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="details-tab-contents">
|
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge">
|
||||||
<p>Coming soon.</p>
|
<MudGrid Class="rss-contents">
|
||||||
</MudContainer>
|
<MudItem xs="4" Style="height: 100%">
|
||||||
|
<MudList T="string" SelectionMode="SelectionMode.SingleSelection" SelectedValue="SelectedFeed" SelectedValueChanged="SelectedFeedChanged">
|
||||||
|
@foreach (var (key, feed) in Items)
|
||||||
|
{
|
||||||
|
<MudListItem Icon="@Icons.Material.Filled.Wifi" Text="@feed.Title" Value="@key" />
|
||||||
|
}
|
||||||
|
</MudList>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="4" Style="height: 100%">
|
||||||
|
@if (SelectedFeed is not null && SelectedRssItem?.Articles is not null)
|
||||||
|
{
|
||||||
|
<MudList T="string" SelectionMode="SelectionMode.SingleSelection" SelectedValue="SelectedArticle" SelectedValueChanged="SelectedArticleChanged">
|
||||||
|
@foreach (var article in SelectedRssItem.Articles)
|
||||||
|
{
|
||||||
|
<MudListItem Text="@article.Title" Value="article.Id" />
|
||||||
|
}
|
||||||
|
</MudList>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudSkeleton SkeletonType="SkeletonType.Rectangle" Height="100%" Animation="Animation.False" Width="100%" />
|
||||||
|
}
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="4" Style="height: 100%">
|
||||||
|
@if (SelectedFeed is not null && SelectedArticle is not null && SelectedRssArticle is not null)
|
||||||
|
{
|
||||||
|
<MudCard>
|
||||||
|
<MudCardHeader>
|
||||||
|
<CardHeaderContent>
|
||||||
|
<MudText Typo="Typo.h6">@SelectedRssArticle.Title</MudText>
|
||||||
|
</CardHeaderContent>
|
||||||
|
<CardHeaderActions>
|
||||||
|
<MudMenu Icon="@Icons.Material.Filled.MoreVert" Dense>
|
||||||
|
<MudMenuItem Icon="@Icons.Material.Filled.Download" OnClick="c => DownloadItem(SelectedRssArticle.TorrentURL)" title="Download">Download</MudMenuItem>
|
||||||
|
<MudMenuItem Icon="@Icons.Material.Filled.Link" Href="@SelectedRssArticle.TorrentURL" Target="@SelectedRssArticle.TorrentURL" title="Download">Open torrent URL</MudMenuItem>
|
||||||
|
</MudMenu>
|
||||||
|
</CardHeaderActions>
|
||||||
|
</MudCardHeader>
|
||||||
|
|
||||||
|
<MudCardContent>
|
||||||
|
<MudText Typo="Typo.subtitle2" Style="overflow-wrap: anywhere">@SelectedRssArticle.Date</MudText>
|
||||||
|
<MudText Typo="Typo.body1">@SelectedRssArticle.Description</MudText>
|
||||||
|
</MudCardContent>
|
||||||
|
</MudCard>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudSkeleton SkeletonType="SkeletonType.Rectangle" Height="100%" Animation="Animation.False" Width="100%" />
|
||||||
|
}
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
</MudContainer>
|
@@ -1,4 +1,5 @@
|
|||||||
using Lantean.QBitTorrentClient;
|
using Lantean.QBitTorrentClient;
|
||||||
|
using Lantean.QBTMudBlade.Helpers;
|
||||||
using Lantean.QBTMudBlade.Models;
|
using Lantean.QBTMudBlade.Models;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
@@ -31,9 +32,96 @@ namespace Lantean.QBTMudBlade.Pages
|
|||||||
|
|
||||||
protected ServerState? ServerState => MainData?.ServerState;
|
protected ServerState? ServerState => MainData?.ServerState;
|
||||||
|
|
||||||
|
protected string? SelectedFeed { get; set; }
|
||||||
|
|
||||||
|
protected string? SelectedArticle { get; set; }
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, QBitTorrentClient.Models.RssItem> Items { get; private set; } = new Dictionary<string, QBitTorrentClient.Models.RssItem>();
|
||||||
|
|
||||||
|
protected IReadOnlyList<int> ColumnsSizes => GetColumnSizes();
|
||||||
|
|
||||||
|
private IReadOnlyList<int> GetColumnSizes()
|
||||||
|
{
|
||||||
|
if (SelectedFeed is null)
|
||||||
|
{
|
||||||
|
return [12, 0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SelectedFeed is not null && SelectedArticle is null)
|
||||||
|
{
|
||||||
|
return [6, 6, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [4, 4, 4];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected QBitTorrentClient.Models.RssItem? SelectedRssItem
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (SelectedFeed == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Items.TryGetValue(SelectedFeed, out var feed);
|
||||||
|
|
||||||
|
return feed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected QBitTorrentClient.Models.RssArticle? SelectedRssArticle
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return SelectedRssItem?.Articles?.FirstOrDefault(a => a.Id == SelectedArticle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void SelectedFeedChanged(string value)
|
||||||
|
{
|
||||||
|
SelectedFeed = value;
|
||||||
|
SelectedArticle = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void SelectedArticleChanged(string value)
|
||||||
|
{
|
||||||
|
SelectedArticle = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
Items = await ApiClient.GetAllRssItems(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task DownloadItem(string? url)
|
||||||
|
{
|
||||||
|
await DialogService.InvokeAddTorrentLinkDialog(ApiClient, url);
|
||||||
|
}
|
||||||
|
|
||||||
protected void NavigateBack()
|
protected void NavigateBack()
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo("/");
|
NavigationManager.NavigateTo("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async Task NewSubscription()
|
||||||
|
{
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task MarkAsRead()
|
||||||
|
{
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task UpdateAll()
|
||||||
|
{
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task EditDownloadRules()
|
||||||
|
{
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -14,7 +14,7 @@ namespace Lantean.QBTMudBlade.Pages
|
|||||||
private int? _searchId;
|
private int? _searchId;
|
||||||
private bool _disposedValue;
|
private bool _disposedValue;
|
||||||
private readonly CancellationTokenSource _timerCancellationToken = new();
|
private readonly CancellationTokenSource _timerCancellationToken = new();
|
||||||
private int _refreshInterval = 1500;
|
private readonly int _refreshInterval = 1500;
|
||||||
|
|
||||||
private QBitTorrentClient.Models.SearchResults? _searchResults;
|
private QBitTorrentClient.Models.SearchResults? _searchResults;
|
||||||
|
|
||||||
|
@@ -14,8 +14,8 @@ namespace Lantean.QBTMudBlade.Pages
|
|||||||
{
|
{
|
||||||
private bool _disposedValue;
|
private bool _disposedValue;
|
||||||
|
|
||||||
private static KeyboardEvent _addTorrentFileKey = new KeyboardEvent("a") { AltKey = true };
|
private static readonly KeyboardEvent _addTorrentFileKey = new("a") { AltKey = true };
|
||||||
private static KeyboardEvent _addTorrentLinkKey = new KeyboardEvent("l") { AltKey = true };
|
private static readonly KeyboardEvent _addTorrentLinkKey = new("l") { AltKey = true };
|
||||||
|
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
|
@@ -20,7 +20,9 @@ namespace Lantean.QBTMudBlade
|
|||||||
|
|
||||||
Uri baseAddress;
|
Uri baseAddress;
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
#pragma warning disable S1075 // URIs should not be hardcoded - used for debugging only
|
||||||
baseAddress = new Uri("http://localhost:8080");
|
baseAddress = new Uri("http://localhost:8080");
|
||||||
|
#pragma warning restore S1075 // URIs should not be hardcoded
|
||||||
#else
|
#else
|
||||||
baseAddress = new Uri(builder.HostEnvironment.BaseAddress);
|
baseAddress = new Uri(builder.HostEnvironment.BaseAddress);
|
||||||
#endif
|
#endif
|
||||||
|
@@ -69,9 +69,11 @@ namespace Lantean.QBTMudBlade.Services
|
|||||||
|
|
||||||
var serverState = CreateServerState(mainData.ServerState);
|
var serverState = CreateServerState(mainData.ServerState);
|
||||||
|
|
||||||
var tagState = new Dictionary<string, HashSet<string>>(tags.Count + 2);
|
var tagState = new Dictionary<string, HashSet<string>>(tags.Count + 2)
|
||||||
tagState.Add(FilterHelper.TAG_ALL, torrents.Keys.ToHashSet());
|
{
|
||||||
tagState.Add(FilterHelper.TAG_UNTAGGED, torrents.Values.Where(t => FilterHelper.FilterTag(t, FilterHelper.TAG_UNTAGGED)).ToHashesHashSet());
|
{ FilterHelper.TAG_ALL, torrents.Keys.ToHashSet() },
|
||||||
|
{ FilterHelper.TAG_UNTAGGED, torrents.Values.Where(t => FilterHelper.FilterTag(t, FilterHelper.TAG_UNTAGGED)).ToHashesHashSet() }
|
||||||
|
};
|
||||||
foreach (var tag in tags)
|
foreach (var tag in tags)
|
||||||
{
|
{
|
||||||
tagState.Add(tag, torrents.Values.Where(t => FilterHelper.FilterTag(t, tag)).ToHashesHashSet());
|
tagState.Add(tag, torrents.Values.Where(t => FilterHelper.FilterTag(t, tag)).ToHashesHashSet());
|
||||||
|
@@ -13,22 +13,27 @@ namespace Lantean.QBTMudBlade.Services
|
|||||||
|
|
||||||
public object? LogRequestStart(HttpRequestMessage request)
|
public object? LogRequestStart(HttpRequestMessage request)
|
||||||
{
|
{
|
||||||
//_logger.LogInformation(
|
#if DEBUG
|
||||||
// "Sending '{Request.Method}' to '{Request.Host}{Request.Path}'",
|
_logger.LogInformation(
|
||||||
// request.Method,
|
"Sending '{Request.Method}' to '{Request.Host}{Request.Path}'",
|
||||||
// request.RequestUri?.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped),
|
request.Method,
|
||||||
// request.RequestUri!.PathAndQuery);
|
request.RequestUri?.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped),
|
||||||
|
request.RequestUri!.PathAndQuery);
|
||||||
|
#endif
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LogRequestStop(
|
public void LogRequestStop(
|
||||||
object? context, HttpRequestMessage request, HttpResponseMessage response, TimeSpan elapsed)
|
object? context, HttpRequestMessage request, HttpResponseMessage response, TimeSpan elapsed)
|
||||||
{
|
{
|
||||||
//_logger.LogInformation(
|
#if DEBUG
|
||||||
// "Received '{Response.StatusCodeInt} {Response.StatusCodeString}' after {Response.ElapsedMilliseconds}ms",
|
_logger.LogInformation(
|
||||||
// (int)response.StatusCode,
|
"Received '{Response.StatusCodeInt} {Response.StatusCodeString}' after {Response.ElapsedMilliseconds}ms",
|
||||||
// response.StatusCode,
|
(int)response.StatusCode,
|
||||||
// elapsed.TotalMilliseconds.ToString("F1"));
|
response.StatusCode,
|
||||||
|
elapsed.TotalMilliseconds.ToString("F1"));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LogRequestFailed(
|
public void LogRequestFailed(
|
||||||
|
@@ -218,4 +218,8 @@ td.icon-cell {
|
|||||||
|
|
||||||
td .folder-button {
|
td .folder-button {
|
||||||
padding: 6px 16px 6px 16px !important;
|
padding: 6px 16px 6px 16px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rss-contents {
|
||||||
|
height: calc(100vh - 149px);
|
||||||
|
}
|
||||||
|
@@ -942,14 +942,146 @@ namespace Lantean.QBitTorrentClient
|
|||||||
|
|
||||||
#region RSS
|
#region RSS
|
||||||
|
|
||||||
// not implementing RSS right now
|
public async Task AddRssFolder(string path)
|
||||||
|
{
|
||||||
|
var content = new FormUrlEncodedBuilder()
|
||||||
|
.Add("path", path)
|
||||||
|
.ToFormUrlEncodedContent();
|
||||||
|
|
||||||
|
var response = await _httpClient.PostAsync("rss/addFolder", content);
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddRssFeed(string url, string? path = null)
|
||||||
|
{
|
||||||
|
var content = new FormUrlEncodedBuilder()
|
||||||
|
.Add("url", url)
|
||||||
|
.AddIfNotNullOrEmpty("path", path)
|
||||||
|
.ToFormUrlEncodedContent();
|
||||||
|
|
||||||
|
var response = await _httpClient.PostAsync("rss/addFeed", content);
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveRssItem(string path)
|
||||||
|
{
|
||||||
|
var content = new FormUrlEncodedBuilder()
|
||||||
|
.Add("path", path)
|
||||||
|
.ToFormUrlEncodedContent();
|
||||||
|
|
||||||
|
var response = await _httpClient.PostAsync("rss/removeItem", content);
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task MoveRssItem(string itemPath, string destPath)
|
||||||
|
{
|
||||||
|
var content = new FormUrlEncodedBuilder()
|
||||||
|
.Add("itemPath", itemPath)
|
||||||
|
.Add("destPath", destPath)
|
||||||
|
.ToFormUrlEncodedContent();
|
||||||
|
|
||||||
|
var response = await _httpClient.PostAsync("rss/moveItem", content);
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyDictionary<string, RssItem>> GetAllRssItems(bool? withData = null)
|
||||||
|
{
|
||||||
|
var content = new QueryBuilder()
|
||||||
|
.AddIfNotNullOrEmpty("withData", withData);
|
||||||
|
|
||||||
|
var response = await _httpClient.GetAsync("rss/items", content);
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
return await GetJsonDictionary<string, RssItem>(response.Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task MarkRssItemAsRead(string itemPath, string? articleId = null)
|
||||||
|
{
|
||||||
|
var content = new FormUrlEncodedBuilder()
|
||||||
|
.Add("itemPath", itemPath)
|
||||||
|
.AddIfNotNullOrEmpty("articleId", articleId)
|
||||||
|
.ToFormUrlEncodedContent();
|
||||||
|
|
||||||
|
var response = await _httpClient.PostAsync("rss/markAsRead", content);
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RefreshRssItem(string itemPath)
|
||||||
|
{
|
||||||
|
var content = new FormUrlEncodedBuilder()
|
||||||
|
.Add("itemPath", itemPath)
|
||||||
|
.ToFormUrlEncodedContent();
|
||||||
|
|
||||||
|
var response = await _httpClient.PostAsync("rss/refresh", content);
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetRssAutoDownloadingRule(string ruleName, AutoDownloadingRule ruleDef)
|
||||||
|
{
|
||||||
|
var content = new FormUrlEncodedBuilder()
|
||||||
|
.Add("ruleName", ruleName)
|
||||||
|
.Add("ruleName", JsonSerializer.Serialize(ruleDef))
|
||||||
|
.ToFormUrlEncodedContent();
|
||||||
|
|
||||||
|
var response = await _httpClient.PostAsync("rss/setRule", content);
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RenameRssAutoDownloadingRule(string ruleName, string newRuleName)
|
||||||
|
{
|
||||||
|
var content = new FormUrlEncodedBuilder()
|
||||||
|
.Add("ruleName", ruleName)
|
||||||
|
.Add("newRuleName", newRuleName)
|
||||||
|
.ToFormUrlEncodedContent();
|
||||||
|
|
||||||
|
var response = await _httpClient.PostAsync("rss/renameRule", content);
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveRssAutoDownloadingRule(string ruleName)
|
||||||
|
{
|
||||||
|
var content = new FormUrlEncodedBuilder()
|
||||||
|
.Add("ruleName", ruleName)
|
||||||
|
.ToFormUrlEncodedContent();
|
||||||
|
|
||||||
|
var response = await _httpClient.PostAsync("rss/removeRule", content);
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyDictionary<string, AutoDownloadingRule>> GetAllRssAutoDownloadingRules()
|
||||||
|
{
|
||||||
|
var response = await _httpClient.GetAsync("rss/rules");
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
return await GetJsonDictionary<string, AutoDownloadingRule>(response.Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyDictionary<string, IReadOnlyList<string>>> GetRssMatchingArticles(string ruleName)
|
||||||
|
{
|
||||||
|
var response = await _httpClient.GetAsync("rss/matchingArticles");
|
||||||
|
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var dictionary = await GetJsonDictionary<string, IEnumerable<string>>(response.Content);
|
||||||
|
|
||||||
|
return ((IDictionary<string, IReadOnlyList<string>>)dictionary.ToDictionary(d => d.Key, d => d.Value.ToList().AsReadOnly())).AsReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
#endregion RSS
|
#endregion RSS
|
||||||
|
|
||||||
#region Search
|
#region Search
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public async Task<int> StartSearch(string pattern, IEnumerable<string> plugins, string category = "all")
|
public async Task<int> StartSearch(string pattern, IEnumerable<string> plugins, string category = "all")
|
||||||
{
|
{
|
||||||
var content = new FormUrlEncodedBuilder()
|
var content = new FormUrlEncodedBuilder()
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FormUrlEncodedBuilder AddIfNotNullOrEmpty(string key, string value)
|
public FormUrlEncodedBuilder AddIfNotNullOrEmpty(string key, string? value)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(value))
|
if (!string.IsNullOrEmpty(value))
|
||||||
{
|
{
|
||||||
@@ -30,6 +30,16 @@
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FormUrlEncodedBuilder AddIfNotNullOrEmpty<T>(string key, T? value) where T : struct
|
||||||
|
{
|
||||||
|
if (value.HasValue)
|
||||||
|
{
|
||||||
|
_parameters.Add(new KeyValuePair<string, string>(key, value.ToString()!));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public FormUrlEncodedContent ToFormUrlEncodedContent()
|
public FormUrlEncodedContent ToFormUrlEncodedContent()
|
||||||
{
|
{
|
||||||
return new FormUrlEncodedContent(_parameters);
|
return new FormUrlEncodedContent(_parameters);
|
||||||
|
@@ -172,7 +172,29 @@ namespace Lantean.QBitTorrentClient
|
|||||||
|
|
||||||
#region RSS
|
#region RSS
|
||||||
|
|
||||||
// not implementing RSS right now
|
Task AddRssFolder(string path);
|
||||||
|
|
||||||
|
Task AddRssFeed(string url, string? path = null);
|
||||||
|
|
||||||
|
Task RemoveRssItem(string path);
|
||||||
|
|
||||||
|
Task MoveRssItem(string itemPath, string destPath);
|
||||||
|
|
||||||
|
Task<IReadOnlyDictionary<string, RssItem>> GetAllRssItems(bool? withData = null);
|
||||||
|
|
||||||
|
Task MarkRssItemAsRead(string itemPath, string? articleId = null);
|
||||||
|
|
||||||
|
Task RefreshRssItem(string itemPath);
|
||||||
|
|
||||||
|
Task SetRssAutoDownloadingRule(string ruleName, AutoDownloadingRule ruleDef);
|
||||||
|
|
||||||
|
Task RenameRssAutoDownloadingRule(string ruleName, string newRuleName);
|
||||||
|
|
||||||
|
Task RemoveRssAutoDownloadingRule(string ruleName);
|
||||||
|
|
||||||
|
Task<IReadOnlyDictionary<string, AutoDownloadingRule>> GetAllRssAutoDownloadingRules();
|
||||||
|
|
||||||
|
Task<IReadOnlyDictionary<string, IReadOnlyList<string>>> GetRssMatchingArticles(string ruleName);
|
||||||
|
|
||||||
#endregion RSS
|
#endregion RSS
|
||||||
|
|
||||||
|
77
Lantean.QBitTorrentClient/Models/AutoDownloadingRule.cs
Normal file
77
Lantean.QBitTorrentClient/Models/AutoDownloadingRule.cs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Lantean.QBitTorrentClient.Models
|
||||||
|
{
|
||||||
|
public record AutoDownloadingRule
|
||||||
|
{
|
||||||
|
[JsonConstructor]
|
||||||
|
public AutoDownloadingRule(
|
||||||
|
bool enabled,
|
||||||
|
string mustContain,
|
||||||
|
string mustNotContain,
|
||||||
|
bool useRegex,
|
||||||
|
string episodeFilter,
|
||||||
|
bool smartFilter,
|
||||||
|
IEnumerable<string> previouslyMatchedEpisodes,
|
||||||
|
IEnumerable<string> affectedFeeds,
|
||||||
|
int ignoreDays,
|
||||||
|
string lastMatch,
|
||||||
|
bool addPaused,
|
||||||
|
string assignedCategory,
|
||||||
|
string savePath)
|
||||||
|
{
|
||||||
|
Enabled = enabled;
|
||||||
|
MustContain = mustContain;
|
||||||
|
MustNotContain = mustNotContain;
|
||||||
|
UseRegex = useRegex;
|
||||||
|
EpisodeFilter = episodeFilter;
|
||||||
|
SmartFilter = smartFilter;
|
||||||
|
PreviouslyMatchedEpisodes = previouslyMatchedEpisodes;
|
||||||
|
AffectedFeeds = affectedFeeds;
|
||||||
|
IgnoreDays = ignoreDays;
|
||||||
|
LastMatch = lastMatch;
|
||||||
|
AddPaused = addPaused;
|
||||||
|
AssignedCategory = assignedCategory;
|
||||||
|
SavePath = savePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("enabled")]
|
||||||
|
public bool Enabled { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("mustContain")]
|
||||||
|
public string MustContain { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("mustNotContain")]
|
||||||
|
public string MustNotContain { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("useRegex")]
|
||||||
|
public bool UseRegex { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("episodeFilter")]
|
||||||
|
public string EpisodeFilter { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("smartFilter")]
|
||||||
|
public bool SmartFilter { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("previouslyMatchedEpisodes")]
|
||||||
|
public IEnumerable<string> PreviouslyMatchedEpisodes { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("affectedFeeds")]
|
||||||
|
public IEnumerable<string> AffectedFeeds { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("ignoreDays")]
|
||||||
|
public int IgnoreDays { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("lastMatch")]
|
||||||
|
public string LastMatch { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("addPaused")]
|
||||||
|
public bool AddPaused { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("assignedCategory")]
|
||||||
|
public string AssignedCategory { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("savePath")]
|
||||||
|
public string SavePath { get; }
|
||||||
|
}
|
||||||
|
}
|
57
Lantean.QBitTorrentClient/Models/RssArticle.cs
Normal file
57
Lantean.QBitTorrentClient/Models/RssArticle.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Lantean.QBitTorrentClient.Models
|
||||||
|
{
|
||||||
|
public class RssArticle
|
||||||
|
{
|
||||||
|
[JsonConstructor]
|
||||||
|
public RssArticle(
|
||||||
|
string? category,
|
||||||
|
string? comments,
|
||||||
|
string? date,
|
||||||
|
string? description,
|
||||||
|
string? id,
|
||||||
|
string? link,
|
||||||
|
string? thumbnail,
|
||||||
|
string? title,
|
||||||
|
string? torrentURL)
|
||||||
|
{
|
||||||
|
Category = category;
|
||||||
|
Comments = comments;
|
||||||
|
Date = date;
|
||||||
|
Description = description;
|
||||||
|
Id = id;
|
||||||
|
Link = link;
|
||||||
|
Thumbnail = thumbnail;
|
||||||
|
Title = title;
|
||||||
|
TorrentURL = torrentURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("category")]
|
||||||
|
public string? Category { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("comments")]
|
||||||
|
public string? Comments { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("date")]
|
||||||
|
public string? Date { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("description")]
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public string? Id { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("link")]
|
||||||
|
public string? Link { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("thumbnail")]
|
||||||
|
public string? Thumbnail { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("title")]
|
||||||
|
public string? Title { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("torrentURL")]
|
||||||
|
public string? TorrentURL { get; set; }
|
||||||
|
}
|
||||||
|
}
|
47
Lantean.QBitTorrentClient/Models/RssItem.cs
Normal file
47
Lantean.QBitTorrentClient/Models/RssItem.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Lantean.QBitTorrentClient.Models
|
||||||
|
{
|
||||||
|
public record RssItem
|
||||||
|
{
|
||||||
|
[JsonConstructor]
|
||||||
|
public RssItem(
|
||||||
|
IReadOnlyList<RssArticle>? articles,
|
||||||
|
bool hasError,
|
||||||
|
bool isLoading,
|
||||||
|
string? lastBuildDate,
|
||||||
|
string? title,
|
||||||
|
string uid,
|
||||||
|
string url)
|
||||||
|
{
|
||||||
|
Articles = articles;
|
||||||
|
HasError = hasError;
|
||||||
|
IsLoading = isLoading;
|
||||||
|
LastBuildDate = lastBuildDate;
|
||||||
|
Title = title;
|
||||||
|
Uid = uid;
|
||||||
|
Url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("articles")]
|
||||||
|
public IReadOnlyList<RssArticle>? Articles { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("hasError")]
|
||||||
|
public bool HasError { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("IsLoading")]
|
||||||
|
public bool IsLoading { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("lastBuildDate")]
|
||||||
|
public string? LastBuildDate { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("title")]
|
||||||
|
public string? Title { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("uid")]
|
||||||
|
public string Uid { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("url")]
|
||||||
|
public string Url { get; }
|
||||||
|
}
|
||||||
|
}
|
@@ -22,7 +22,7 @@ namespace Lantean.QBitTorrentClient
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryBuilder AddIfNotNullOrEmpty(string key, string value)
|
public QueryBuilder AddIfNotNullOrEmpty(string key, string? value)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(value))
|
if (!string.IsNullOrEmpty(value))
|
||||||
{
|
{
|
||||||
@@ -32,6 +32,16 @@ namespace Lantean.QBitTorrentClient
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public QueryBuilder AddIfNotNullOrEmpty<T>(string key, T? value) where T : struct
|
||||||
|
{
|
||||||
|
if (value.HasValue)
|
||||||
|
{
|
||||||
|
_parameters.Add(new KeyValuePair<string, string>(key, value.ToString()!));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public string ToQueryString()
|
public string ToQueryString()
|
||||||
{
|
{
|
||||||
if (_parameters.Count == 0)
|
if (_parameters.Count == 0)
|
||||||
|
Reference in New Issue
Block a user