mirror of
https://github.com/lantean-code/qbtmud.git
synced 2025-10-22 20:42:24 +00:00
Add project files.
This commit is contained in:
39
.dcignore
Normal file
39
.dcignore
Normal file
@@ -0,0 +1,39 @@
|
||||
# Write glob rules for ignored files.
|
||||
# Check syntax on https://deepcode.freshdesk.com/support/solutions/articles/60000531055-how-can-i-ignore-files-or-directories-
|
||||
# Check examples on https://github.com/github/gitignore
|
||||
|
||||
# Hidden directories and files
|
||||
.*
|
||||
|
||||
# Common binary directories and files
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
*.exe
|
||||
*.dll
|
||||
|
||||
# Logs and temporary files
|
||||
[Tt]emp/
|
||||
*.log
|
||||
|
||||
# Build directories
|
||||
/build/
|
||||
/dist/
|
||||
/out/
|
||||
|
||||
# Node modules and package directories
|
||||
node_modules/
|
||||
**/[Pp]ackages/*
|
||||
|
||||
# Various cache directories
|
||||
*.cache
|
||||
/saved/
|
||||
/intermediates/
|
||||
/generated/
|
||||
/coverage/
|
||||
/tmp/
|
||||
|
||||
# Specific directory exclusions
|
||||
/DocProject/Help/html/
|
||||
|
||||
# Ignore files from .vs directory
|
||||
.vs/
|
3
Lantean.QBTBlazor.Test/ContentItemSizeComparer.cs
Normal file
3
Lantean.QBTBlazor.Test/ContentItemSizeComparer.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace Lantean.QBTFluent.Comparers
|
||||
{
|
||||
}
|
27
Lantean.QBTBlazor.Test/Lantean.QBTBlazor.Test.csproj
Normal file
27
Lantean.QBTBlazor.Test/Lantean.QBTBlazor.Test.csproj
Normal file
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageReference Include="xunit" Version="2.7.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
67
Lantean.QBTBlazor.Test/UnitTest1.cs
Normal file
67
Lantean.QBTBlazor.Test/UnitTest1.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text.Json;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Lantean.QBTBlazor.Test
|
||||
{
|
||||
public class UnitTest1
|
||||
{
|
||||
private readonly ITestOutputHelper _testOutputHelper;
|
||||
|
||||
public UnitTest1(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
_testOutputHelper = testOutputHelper;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test()
|
||||
{
|
||||
Test2(a => a.Name);
|
||||
}
|
||||
|
||||
private void Test2(Expression<Func<TestClass, object>> expr)
|
||||
{
|
||||
var body = expr.Body;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create()
|
||||
{
|
||||
var propInfo = typeof(TestClass).GetProperty("Name")!;
|
||||
|
||||
ParameterExpression expression = Expression.Parameter(typeof(TestClass), "a");
|
||||
var propertyExpression = Expression.Property(expression, "Value");
|
||||
|
||||
var convertExpression = Expression.Convert(propertyExpression, typeof(object));
|
||||
|
||||
var l = Expression.Lambda<Func<TestClass, object>>(convertExpression, expression);
|
||||
|
||||
Expression<Func<TestClass, object>> expr2 = a => a.Name;
|
||||
|
||||
var x = l.Compile();
|
||||
var res = (long)x(new TestClass { Name = "Name", Value = 12 });
|
||||
Assert.Equal(12, res);
|
||||
expr2.Compile();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanDir()
|
||||
{
|
||||
//Dictionary<string, string>
|
||||
var json = "{\r\n\t\"/this/is/path\": 1,\r\n\t\"/this/other\": 0,\r\n\t\"/home\": \"/path\"\r\n}";
|
||||
|
||||
var obj = JsonSerializer.Deserialize<Dictionary<string, SaveLocation>>(json, SerializerOptions.Options);
|
||||
}
|
||||
}
|
||||
|
||||
public class TestClass
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public long Value { get; set; }
|
||||
}
|
||||
}
|
37
Lantean.QBTBlazor.sln
Normal file
37
Lantean.QBTBlazor.sln
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.8.34511.84
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lantean.QBTBlazor.Test", "Lantean.QBTBlazor.Test\Lantean.QBTBlazor.Test.csproj", "{715E075C-1D86-4A7F-BC72-E1E24A294F17}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lantean.QBitTorrentClient", "Lantean.QBitTorrentClient\Lantean.QBitTorrentClient.csproj", "{F0DDAB07-0D6C-40C7-AFE6-08FF2C4CC7E7}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lantean.QBTMudBlade", "Lantean.QBTMudBlade\Lantean.QBTMudBlade.csproj", "{83BC76CC-D51B-42AF-A6EE-FA400C300098}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{715E075C-1D86-4A7F-BC72-E1E24A294F17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{715E075C-1D86-4A7F-BC72-E1E24A294F17}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{715E075C-1D86-4A7F-BC72-E1E24A294F17}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{715E075C-1D86-4A7F-BC72-E1E24A294F17}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F0DDAB07-0D6C-40C7-AFE6-08FF2C4CC7E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F0DDAB07-0D6C-40C7-AFE6-08FF2C4CC7E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F0DDAB07-0D6C-40C7-AFE6-08FF2C4CC7E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F0DDAB07-0D6C-40C7-AFE6-08FF2C4CC7E7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{83BC76CC-D51B-42AF-A6EE-FA400C300098}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{83BC76CC-D51B-42AF-A6EE-FA400C300098}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{83BC76CC-D51B-42AF-A6EE-FA400C300098}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{83BC76CC-D51B-42AF-A6EE-FA400C300098}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {82E46DB7-956A-4971-BB18-1F20650EC1A4}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
12
Lantean.QBTMudBlade/App.razor
Normal file
12
Lantean.QBTMudBlade/App.razor
Normal file
@@ -0,0 +1,12 @@
|
||||
<Router AppAssembly="@typeof(App).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
|
||||
</Found>
|
||||
<NotFound>
|
||||
<PageTitle>Not found</PageTitle>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<p role="alert">Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
@@ -0,0 +1,16 @@
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudTextField Label="Category" @bind-Value="Category" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField Label="Save Path" @bind-Value="SavePath" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Add</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
@@ -0,0 +1,31 @@
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components.Dialogs
|
||||
{
|
||||
public partial class AddCategoryDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
protected string? Category { get; set; }
|
||||
|
||||
protected string SavePath { get; set; } = "";
|
||||
|
||||
protected void Cancel(MouseEventArgs args)
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit(MouseEventArgs args)
|
||||
{
|
||||
if (Category is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
MudDialog.Close(DialogResult.Ok(new Category(Category, SavePath)));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudFileUpload T="IReadOnlyList<IBrowserFile>" FilesChanged="UploadFiles" Accept=".torrent">
|
||||
<ButtonTemplate>
|
||||
<MudButton HtmlTag="label"
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Secondary"
|
||||
StartIcon="@Icons.Material.Filled.CloudUpload"
|
||||
for="@context.Id">
|
||||
Choose files
|
||||
</MudButton>
|
||||
</ButtonTemplate>
|
||||
</MudFileUpload>
|
||||
</MudItem>
|
||||
<AddTorrentOptions @ref="TorrentOptions" ShowCookieOption />
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Close</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Upload Torrents</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
@@ -0,0 +1,34 @@
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components.Dialogs
|
||||
{
|
||||
public partial class AddTorrentFileDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
protected IReadOnlyList<IBrowserFile> Files { get; set; } = [];
|
||||
|
||||
protected AddTorrentOptions TorrentOptions { get; set; } = default!;
|
||||
|
||||
protected void UploadFiles(IReadOnlyList<IBrowserFile> files)
|
||||
{
|
||||
Files = files;
|
||||
}
|
||||
|
||||
protected void Cancel(MouseEventArgs args)
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit(MouseEventArgs args)
|
||||
{
|
||||
var options = new AddTorrentFileOptions(Files, TorrentOptions.GetTorrentOptions());
|
||||
MudDialog.Close(DialogResult.Ok(options));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudTextField Label="Urls" Lines="10" @bind-Value="Urls" Variant="Variant.Filled" />
|
||||
</MudItem>
|
||||
<AddTorrentOptions @ref="TorrentOptions" ShowCookieOption />
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Close</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Upload Torrents</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
@@ -0,0 +1,33 @@
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components.Dialogs
|
||||
{
|
||||
public partial class AddTorrentLinkDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
protected string? Urls { get; set; }
|
||||
|
||||
protected AddTorrentOptions TorrentOptions { get; set; } = default!;
|
||||
|
||||
protected void Cancel(MouseEventArgs args)
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit(MouseEventArgs args)
|
||||
{
|
||||
if (Urls is null)
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
return;
|
||||
}
|
||||
var options = new AddTorrentLinkOptions(Urls, TorrentOptions.GetTorrentOptions());
|
||||
MudDialog.Close(DialogResult.Ok(options));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
<MudItem xs="12">
|
||||
<MudSelect Label="Torrent Management Mode" @bind-Value="TorrentManagementMode" Variant="Variant.Filled">
|
||||
<MudSelectItem Value="false">Manual</MudSelectItem>
|
||||
<MudSelectItem Value="true">Automatic</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField Label="Save files to location" @bind-Value="SavePath" Variant="Variant.Filled"></MudTextField>
|
||||
</MudItem>
|
||||
@if (ShowCookieOption)
|
||||
{
|
||||
<MudItem xs="12">
|
||||
<MudTextField Label="Cookie" @bind-Value="Cookie" Variant="Variant.Filled"></MudTextField>
|
||||
</MudItem>
|
||||
}
|
||||
<MudItem xs="12">
|
||||
<MudTextField Label="Rename" @bind-Value="RenameTorrent" Variant="Variant.Filled"></MudTextField>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect Label="Category" @bind-Value="Category" Variant="Variant.Filled">
|
||||
@foreach (var category in Categories)
|
||||
{
|
||||
<MudSelectItem Value="category">@category</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox Label="Start torrent" @bind-Value="StartTorrent" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox Label="Add to top of queue" @bind-Value="AddToTopOfQueue" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect Label="Stop condition" @bind-Value="StopCondition" Variant="Variant.Filled">
|
||||
<MudSelectItem Value="@("None")">None</MudSelectItem>
|
||||
<MudSelectItem Value="@("MetadataReceived")">Metadata received</MudSelectItem>
|
||||
<MudSelectItem Value="@("FilesChecked")">Files checked</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox Label="Add to top of queue" @bind-Value="AddToTopOfQueue" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox Label="Skip hash check" @bind-Value="SkipHashCheck" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudSelect Label="Content layout" @bind-Value="ContentLayout" Variant="Variant.Filled">
|
||||
<MudSelectItem Value="@("Original")">Original</MudSelectItem>
|
||||
<MudSelectItem Value="@("Subfolder")">Create subfolder</MudSelectItem>
|
||||
<MudSelectItem Value="@("NoSubfolder")">Don't create subfolder'</MudSelectItem>
|
||||
</MudSelect>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox Label="Download in sequentual order" @bind-Value="DownloadInSequentialOrder" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox Label="Download first and last pieces first" @bind-Value="DownloadFirstAndLastPiecesFirst" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField Label="Limit download rate" @bind-Value="DownloadLimit" Variant="Variant.Filled" Min="0" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField Label="Limit upload rate" @bind-Value="UploadLimit" Variant="Variant.Filled" Min="0" />
|
||||
</MudItem>
|
@@ -0,0 +1,79 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components.Dialogs
|
||||
{
|
||||
public partial class AddTorrentOptions
|
||||
{
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public bool ShowCookieOption { get; set; }
|
||||
|
||||
protected bool TorrentManagementMode { get; set; }
|
||||
|
||||
protected string SavePath { get; set; } = default!;
|
||||
|
||||
protected string? Cookie { get; set; }
|
||||
|
||||
protected string? RenameTorrent { get; set; }
|
||||
|
||||
protected IEnumerable<string> Categories { get; set; } = [];
|
||||
|
||||
protected string? Category { get; set; }
|
||||
|
||||
protected bool StartTorrent { get; set; } = true;
|
||||
|
||||
protected bool AddToTopOfQueue { get; set; } = true;
|
||||
|
||||
protected string StopCondition { get; set; } = "None";
|
||||
|
||||
protected bool SkipHashCheck { get; set; } = false;
|
||||
|
||||
protected string ContentLayout { get; set; } = "Original";
|
||||
|
||||
protected bool DownloadInSequentialOrder { get; set; } = false;
|
||||
|
||||
protected bool DownloadFirstAndLastPiecesFirst { get; set; } = false;
|
||||
|
||||
protected long DownloadLimit { get; set; }
|
||||
|
||||
protected long UploadLimit { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var categories = await ApiClient.GetAllCategories();
|
||||
Categories = categories.Select(c => c.Key).ToList();
|
||||
|
||||
var preferences = await ApiClient.GetApplicationPreferences();
|
||||
|
||||
TorrentManagementMode = preferences.AutoTmmEnabled;
|
||||
SavePath = preferences.SavePath;
|
||||
StartTorrent = !preferences.StartPausedEnabled;
|
||||
AddToTopOfQueue = preferences.AddToTopOfQueue;
|
||||
StopCondition = preferences.TorrentStopCondition;
|
||||
ContentLayout = preferences.TorrentContentLayout;
|
||||
}
|
||||
|
||||
public TorrentOptions GetTorrentOptions()
|
||||
{
|
||||
return new TorrentOptions(
|
||||
TorrentManagementMode,
|
||||
SavePath,
|
||||
Cookie,
|
||||
RenameTorrent,
|
||||
Category,
|
||||
StartTorrent,
|
||||
AddToTopOfQueue,
|
||||
StopCondition,
|
||||
SkipHashCheck,
|
||||
ContentLayout,
|
||||
DownloadInSequentialOrder,
|
||||
DownloadFirstAndLastPiecesFirst,
|
||||
DownloadLimit,
|
||||
UploadLimit);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
@typeparam T
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudCard Class="w-100">
|
||||
<MudList>
|
||||
@foreach (var column in Columns)
|
||||
{
|
||||
<MudListItem>
|
||||
<MudCheckBox T="bool" ValueChanged="@(c => SetSelected(c, column.Id))" Label="@column.Header" LabelPosition="LabelPosition.End" Value="@(SelectedColumns.Contains(column.Id))" />
|
||||
</MudListItem>
|
||||
}
|
||||
</MudList>
|
||||
</MudCard>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
@@ -0,0 +1,51 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components.Dialogs
|
||||
{
|
||||
public partial class ColumnOptionsDialog<T>
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public List<ColumnDefinition<T>> Columns { get; set; } = default!;
|
||||
|
||||
protected HashSet<string> SelectedColumns { get; set; } = [];
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (SelectedColumns.Count == 0)
|
||||
{
|
||||
foreach (var column in Columns.Where(c => c.Enabled))
|
||||
{
|
||||
SelectedColumns.Add(column.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void SetSelected(bool selected, string id)
|
||||
{
|
||||
if (selected)
|
||||
{
|
||||
SelectedColumns.Add(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedColumns.Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
protected void Cancel(MouseEventArgs args)
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit(MouseEventArgs args)
|
||||
{
|
||||
MudDialog.Close(DialogResult.Ok(SelectedColumns));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
@Content
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">@CancelText</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">@SuccessText</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
@@ -0,0 +1,31 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components.Dialogs
|
||||
{
|
||||
public partial class ConfirmDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string Content { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string? SuccessText { get; set; } = "Ok";
|
||||
|
||||
[Parameter]
|
||||
public string? CancelText { get; set; } = "Cancel";
|
||||
|
||||
protected void Cancel(MouseEventArgs args)
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit(MouseEventArgs args)
|
||||
{
|
||||
MudDialog.Close(DialogResult.Ok(true));
|
||||
}
|
||||
}
|
||||
}
|
15
Lantean.QBTMudBlade/Components/Dialogs/DeleteDialog.razor
Normal file
15
Lantean.QBTMudBlade/Components/Dialogs/DeleteDialog.razor
Normal file
@@ -0,0 +1,15 @@
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudText>Are you sure you want to remove the selected torrents from the transfer list?</MudText>
|
||||
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox Label="Also permanently delete the files" @bind-Value="DeleteFiles" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Remove</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
24
Lantean.QBTMudBlade/Components/Dialogs/DeleteDialog.razor.cs
Normal file
24
Lantean.QBTMudBlade/Components/Dialogs/DeleteDialog.razor.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components.Dialogs
|
||||
{
|
||||
public partial class DeleteDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
protected bool DeleteFiles { get; set; }
|
||||
|
||||
protected void Cancel(MouseEventArgs args)
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit(MouseEventArgs args)
|
||||
{
|
||||
MudDialog.Close(DialogResult.Ok(DeleteFiles));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
@typeparam T
|
||||
|
||||
<MudDialog ContentStyle="mix-width: 400px">
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
@foreach (var definition in FilterDefinitions ?? [])
|
||||
{
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Column">@definition.Column</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<MudSelect Label="Operator" T="string" Value="@definition.Operator" ValueChanged="@(v => DefinitionOperatorChanged(definition, v))">
|
||||
@foreach (var op in Filter.FilterOperator.GetOperatorByDataType(definition.ColumnType))
|
||||
{
|
||||
<MudSelectItem T="string" Value="op">@op</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudTextField Label="Value" T="object" Value="@definition.Value" ValueChanged="@(v => DefinitionValueChanged(definition, v))" />
|
||||
</MudItem>
|
||||
<MudItem xs="1">
|
||||
<MudIconButton OnClick="@(e => RemoveDefinition(definition))" Icon="@Icons.Material.Outlined.Remove" />
|
||||
</MudItem>
|
||||
<MudDivider />
|
||||
}
|
||||
<MudItem xs="4">
|
||||
<MudSelect Label="Column" T="string" ValueChanged="ColumnChanged">
|
||||
@foreach (var propertyName in GetAvailablePropertyNames())
|
||||
{
|
||||
<MudSelectItem T="string" Value="@propertyName" />
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<MudSelect Label="Operator" T="string" ValueChanged="OperatorChanged">
|
||||
@if (ColumnType is null)
|
||||
{
|
||||
<MudSelectItem T="string" Value="@("")">Please select a column.</MudSelectItem>
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var op in Filter.FilterOperator.GetOperatorByDataType(ColumnType))
|
||||
{
|
||||
<MudSelectItem T="string" Value="op">@op</MudSelectItem>
|
||||
}
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudTextField Label="Value" T="string" ValueChanged="ValueChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="1">
|
||||
<MudIconButton OnClick="AddDefinition" Icon="@Icons.Material.Outlined.Add" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
@@ -0,0 +1,127 @@
|
||||
using Lantean.QBTMudBlade.Filter;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components.Dialogs
|
||||
{
|
||||
public partial class FilterOptionsDialog<T>
|
||||
{
|
||||
private static readonly IReadOnlyList<PropertyInfo> _properties = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
|
||||
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
protected IReadOnlyList<PropertyInfo> Columns => _properties;
|
||||
|
||||
[Parameter]
|
||||
public List<PropertyFilterDefinition<T>>? FilterDefinitions { get; set; }
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
// as
|
||||
}
|
||||
|
||||
protected void RemoveDefinition(PropertyFilterDefinition<T> definition)
|
||||
{
|
||||
if (FilterDefinitions is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
FilterDefinitions.Remove(definition);
|
||||
}
|
||||
|
||||
protected void DefinitionOperatorChanged(PropertyFilterDefinition<T> definition, string @operator)
|
||||
{
|
||||
var existingDefinition = FilterDefinitions?.Find(d => d == definition);
|
||||
if (existingDefinition is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
existingDefinition.Operator = @operator;
|
||||
}
|
||||
|
||||
protected void DefinitionValueChanged(PropertyFilterDefinition<T> definition, object? value)
|
||||
{
|
||||
var existingDefinition = FilterDefinitions?.Find(d => d == definition);
|
||||
if (existingDefinition is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
existingDefinition.Value = value;
|
||||
}
|
||||
|
||||
protected string? Column { get; set; }
|
||||
protected Type? ColumnType { get; set; }
|
||||
protected string? Operator { get; set; }
|
||||
protected string? Value { get; set; }
|
||||
|
||||
protected void ColumnChanged(string column)
|
||||
{
|
||||
Column = column;
|
||||
ColumnType = _properties.FirstOrDefault(p => p.Name == column)?.PropertyType;
|
||||
}
|
||||
|
||||
protected IEnumerable<string> GetAvailablePropertyNames()
|
||||
{
|
||||
foreach (var propertyName in _properties.Select(p => p.Name))
|
||||
{
|
||||
if (!(FilterDefinitions?.Exists(d => d.Column == propertyName) ?? false))
|
||||
{
|
||||
yield return propertyName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void OperatorChanged(string @operator)
|
||||
{
|
||||
Operator = @operator;
|
||||
}
|
||||
|
||||
protected void ValueChanged(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
protected async Task AddDefinition()
|
||||
{
|
||||
if (Column is null || Operator is null || (FilterDefinitions?.Exists(d => d.Column == Column) ?? false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CreateAndAdd(Column, Operator, Value);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private void CreateAndAdd(string column, string @operator, object? value)
|
||||
{
|
||||
FilterDefinitions ??= [];
|
||||
FilterDefinitions.Add(new PropertyFilterDefinition<T>(column, @operator, value));
|
||||
|
||||
Column = null;
|
||||
Operator = null;
|
||||
Value = null;
|
||||
}
|
||||
|
||||
protected void Cancel(MouseEventArgs args)
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit(MouseEventArgs args)
|
||||
{
|
||||
if (Column is not null && Operator is not null && !(FilterDefinitions?.Exists(d => d.Column == Column) ?? false))
|
||||
{
|
||||
CreateAndAdd(Column, Operator, Value);
|
||||
}
|
||||
|
||||
MudDialog.Close(DialogResult.Ok(FilterDefinitions));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
@typeparam T
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudTextField Label="@Label" Value="@Value" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
@@ -0,0 +1,28 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components.Dialogs
|
||||
{
|
||||
public partial class SingleFieldDialog<T>
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string Label { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public T? Value { get; set; }
|
||||
|
||||
protected void Cancel(MouseEventArgs args)
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit(MouseEventArgs args)
|
||||
{
|
||||
MudDialog.Close(DialogResult.Ok(Value));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
@typeparam T
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField Label="@Label" Value="@Value" Min="Min" Max="Max" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSlider ValueLabel="true" Value="@Value" Min="Min" Max="Max" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
@@ -0,0 +1,34 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components.Dialogs
|
||||
{
|
||||
public partial class SliderFieldDialog<T>
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public string Label { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public T? Value { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public T? Min { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public T? Max { get; set; }
|
||||
|
||||
protected void Cancel(MouseEventArgs args)
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
protected void Submit(MouseEventArgs args)
|
||||
{
|
||||
MudDialog.Close(DialogResult.Ok(Value));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudText>Statistics</MudText>
|
||||
</DialogContent>
|
||||
</MudDialog>
|
@@ -0,0 +1,11 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components.Dialogs
|
||||
{
|
||||
public partial class StatisticsDialog
|
||||
{
|
||||
[CascadingParameter]
|
||||
public MudDialogInstance MudDialog { get; set; } = default!;
|
||||
}
|
||||
}
|
63
Lantean.QBTMudBlade/Components/ExtendedTable.razor
Normal file
63
Lantean.QBTMudBlade/Components/ExtendedTable.razor
Normal file
@@ -0,0 +1,63 @@
|
||||
@inherits MudTable<T>
|
||||
@typeparam T
|
||||
|
||||
@{
|
||||
base.BuildRenderTree(__builder);
|
||||
}
|
||||
|
||||
@code {
|
||||
private RenderFragment ColGroupFragment(IEnumerable<ColumnDefinition<T>> columns) =>
|
||||
@<NonRendering>
|
||||
@if (MultiSelection)
|
||||
{
|
||||
<col />
|
||||
}
|
||||
|
||||
@foreach (var column in columns)
|
||||
{
|
||||
var style = column.Width.HasValue ? $"width: {column.Width.Value}px" : null;
|
||||
<col style="@style" />
|
||||
}
|
||||
</NonRendering>;
|
||||
|
||||
private RenderFragment HeaderContentFragment(IEnumerable<ColumnDefinition<T>> columns) =>
|
||||
@<NonRendering>
|
||||
@foreach (var column in columns)
|
||||
{
|
||||
<MudTh>
|
||||
@if (column.SortSelector is not null)
|
||||
{
|
||||
<MudTableSortLabel T="T" SortDirectionChanged="@(c => SetSort(column.SortSelector, c))">@column.Header</MudTableSortLabel>
|
||||
}
|
||||
else
|
||||
{
|
||||
@column.Header
|
||||
}
|
||||
</MudTh>
|
||||
}
|
||||
</NonRendering>;
|
||||
|
||||
private RenderFragment<T> RowTemplateFragment(IEnumerable<ColumnDefinition<T>> columns) => data =>
|
||||
@<NonRendering>
|
||||
@foreach (var column in columns)
|
||||
{
|
||||
<MudTd DataLabel="@column.Header" Class="@column.Class">
|
||||
@column.RowTemplate(column.GetRowContext(data))
|
||||
</MudTd>
|
||||
}
|
||||
</NonRendering>;
|
||||
|
||||
|
||||
private RenderFragment<T> RowTemplateFragment2(IEnumerable<ColumnDefinition<T>> columns)
|
||||
{
|
||||
return context => __builder =>
|
||||
{
|
||||
foreach (var column in columns)
|
||||
{
|
||||
<MudTd DataLabel="@column.Header" Class="@column.Class">
|
||||
@column.RowTemplate(column.GetRowContext(context))
|
||||
</MudTd>
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
153
Lantean.QBTMudBlade/Components/ExtendedTable.razor.cs
Normal file
153
Lantean.QBTMudBlade/Components/ExtendedTable.razor.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
public partial class ExtendedTable<T> : MudTable<T>
|
||||
{
|
||||
[Parameter]
|
||||
public IEnumerable<ColumnDefinition<T>>? ColumnDefinitions { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public HashSet<ColumnDefinition<T>> SelectedColumns { get; set; } = [];
|
||||
|
||||
private Func<T, object?>? _sortSelector;
|
||||
private SortDirection _sortDirection;
|
||||
|
||||
private IEnumerable<string>? _selectedColumns;
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (ColumnDefinitions is not null)
|
||||
{
|
||||
var activeColumns = GetActiveColummns(ColumnDefinitions);
|
||||
ColGroup ??= ColGroupFragment(activeColumns);
|
||||
HeaderContent ??= HeaderContentFragment(activeColumns);
|
||||
RowTemplate ??= RowTemplateFragment(activeColumns);
|
||||
_selectedColumns ??= ColumnDefinitions.Where(c => c.Enabled).Select(c => c.Id).ToList();
|
||||
_sortSelector ??= ColumnDefinitions.First(c => c.Enabled).SortSelector;
|
||||
Items = GetOrderedItems(Items, _sortSelector);
|
||||
}
|
||||
base.OnParametersSet();
|
||||
}
|
||||
|
||||
private IEnumerable<T>? GetOrderedItems(IEnumerable<T>? items, Func<T, object?> sortSelector)
|
||||
{
|
||||
if (items is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return items.OrderByDirection(_sortDirection, sortSelector);
|
||||
}
|
||||
|
||||
private void SetSort(Func<T, object?> sortSelector, SortDirection sortDirection)
|
||||
{
|
||||
_sortSelector = sortSelector;
|
||||
_sortDirection = sortDirection;
|
||||
}
|
||||
|
||||
private IEnumerable<ColumnDefinition<T>>? GetColumns()
|
||||
{
|
||||
if (ColumnDefinitions is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetActiveColummns(ColumnDefinitions);
|
||||
}
|
||||
|
||||
private IEnumerable<ColumnDefinition<T>> GetActiveColummns(IEnumerable<ColumnDefinition<T>> columns)
|
||||
{
|
||||
if (_selectedColumns is null)
|
||||
{
|
||||
return columns;
|
||||
}
|
||||
return columns.Where(c => _selectedColumns.Contains(c.Id));
|
||||
}
|
||||
|
||||
//private RenderFragment CreateColGroup()
|
||||
//{
|
||||
// return builder =>
|
||||
// {
|
||||
// var selectedColumns = GetColumns();
|
||||
// if (selectedColumns is null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (MultiSelection)
|
||||
// {
|
||||
// builder.OpenElement(0, "col");
|
||||
// builder.CloseElement();
|
||||
// }
|
||||
|
||||
// int sequence = 1;
|
||||
// foreach (var width in selectedColumns.Select(c => c.Width))
|
||||
// {
|
||||
// builder.OpenElement(sequence++, "col");
|
||||
// if (width.HasValue)
|
||||
// {
|
||||
// builder.AddAttribute(sequence++, "style", $"width: {width.Value}px");
|
||||
// }
|
||||
// builder.CloseElement();
|
||||
// }
|
||||
// };
|
||||
//}
|
||||
|
||||
//private RenderFragment CreateHeaderContent()
|
||||
//{
|
||||
// return builder =>
|
||||
// {
|
||||
// var selectedColumns = GetColumns();
|
||||
// if (selectedColumns is null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// int sequence = 0;
|
||||
// foreach (var columnDefinition in selectedColumns)
|
||||
// {
|
||||
// builder.OpenComponent<MudTh>(sequence);
|
||||
// if (columnDefinition.SortSelector is not null)
|
||||
// {
|
||||
// builder.OpenComponent<MudTableSortLabel<T>>(sequence++);
|
||||
// builder.AddAttribute(sequence++, "SortDirectionChanged", EventCallback.Factory.Create<SortDirection>(this, c => SetSort(columnDefinition.SortSelector, c)));
|
||||
// RenderFragment childContent = b => b.AddContent(0, columnDefinition.Header);
|
||||
// builder.AddAttribute(sequence++, "ChildContent", childContent);
|
||||
// builder.CloseComponent();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// RenderFragment childContent = b => b.AddContent(0, columnDefinition.Header);
|
||||
// builder.AddAttribute(sequence++, "ChildContent", childContent);
|
||||
// }
|
||||
// builder.CloseComponent();
|
||||
// }
|
||||
// };
|
||||
//}
|
||||
|
||||
//private RenderFragment<T> CreateRowTemplate()
|
||||
//{
|
||||
// return context => builder =>
|
||||
// {
|
||||
// var selectedColumns = GetColumns();
|
||||
// if (selectedColumns is null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// int sequence = 0;
|
||||
// foreach (var columnDefinition in selectedColumns)
|
||||
// {
|
||||
// builder.OpenComponent<MudTd>(sequence++);
|
||||
// builder.AddAttribute(sequence++, "DataLabel", columnDefinition.Header);
|
||||
// builder.AddAttribute(sequence++, "Class", columnDefinition.Class);
|
||||
// RenderFragment childContent = b => b.AddContent(0, columnDefinition.RowTemplate(columnDefinition.GetRowContext(context)));
|
||||
// builder.AddAttribute(sequence++, "ChildContent", childContent);
|
||||
// builder.CloseComponent();
|
||||
// }
|
||||
// };
|
||||
//}
|
||||
}
|
||||
}
|
10
Lantean.QBTMudBlade/Components/FakeNavLink.razor
Normal file
10
Lantean.QBTMudBlade/Components/FakeNavLink.razor
Normal file
@@ -0,0 +1,10 @@
|
||||
<div @onclick="EventUtil.AsNonRenderingEventHandler<MouseEventArgs>(OnClickHandler)"
|
||||
class="@LinkClassname">
|
||||
@if (!string.IsNullOrEmpty(Icon))
|
||||
{
|
||||
<MudIcon Icon="@Icon" Color="@IconColor" Class="@IconClassname" />
|
||||
}
|
||||
<div Class="mud-nav-link-text">
|
||||
@ChildContent
|
||||
</div>
|
||||
</div>
|
72
Lantean.QBTMudBlade/Components/FakeNavLink.razor.cs
Normal file
72
Lantean.QBTMudBlade/Components/FakeNavLink.razor.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
using MudBlazor.Utilities;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
public partial class FakeNavLink
|
||||
{
|
||||
[Parameter]
|
||||
public bool Active { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? Class { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool DisableRipple { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Icon to use if set.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string? Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The color of the icon. It supports the theme colors, default value uses the themes drawer icon color.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Color IconColor { get; set; } = Color.Default;
|
||||
|
||||
|
||||
[Parameter]
|
||||
public string? Target { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<MouseEventArgs> OnClick { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
protected string Classname =>
|
||||
new CssBuilder("mud-nav-item")
|
||||
.AddClass($"mud-ripple", !DisableRipple && !Disabled)
|
||||
.AddClass(Class)
|
||||
.Build();
|
||||
|
||||
protected string LinkClassname =>
|
||||
new CssBuilder("mud-nav-link")
|
||||
.AddClass($"mud-nav-link-disabled", Disabled)
|
||||
.AddClass("active", Active)
|
||||
.Build();
|
||||
|
||||
protected string IconClassname =>
|
||||
new CssBuilder("mud-nav-link-icon")
|
||||
.AddClass($"mud-nav-link-icon-default", IconColor == Color.Default)
|
||||
.Build();
|
||||
|
||||
protected async Task OnClickHandler(MouseEventArgs ev)
|
||||
{
|
||||
if (Disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await OnClick.InvokeAsync(ev);
|
||||
}
|
||||
}
|
||||
}
|
99
Lantean.QBTMudBlade/Components/FilesTab.razor
Normal file
99
Lantean.QBTMudBlade/Components/FilesTab.razor
Normal file
@@ -0,0 +1,99 @@
|
||||
<MudTable T="ContentItem" Hover="true" FixedHeader="true" HeaderClass="table-head-bordered" Breakpoint="Breakpoint.None" Bordered="false"
|
||||
MultiSelection="true" Dense="true" SelectOnRowClick="false"
|
||||
Items="Files"
|
||||
SelectedItems="SelectedItems"
|
||||
SelectedItemsChanged="SelectedItemsChanged"
|
||||
OnRowClick="RowClick"
|
||||
RowStyleFunc="RowStyle"
|
||||
RowClassFunc="RowClass"
|
||||
AllowUnsorted="false">
|
||||
<ToolBarContent>
|
||||
<MudToolBar DisableGutters="true" Dense="true">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.DriveFileRenameOutline" OnClick="RenameFile" Title="Rename" />
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.ViewColumn" Color="Color.Inherit" OnClick="ColumnOptions" Title="Choose Columns" />
|
||||
<MudDivider Vertical="true" />
|
||||
<MudMenu Icon="@Icons.Material.Outlined.FileDownloadOff" Label="Do Not Download" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft">
|
||||
<MudMenuItem OnClick="DoNotDownloadLessThan100PercentAvailability" OnTouch="DoNotDownloadLessThan100PercentAvailability">Less Than 100% Availability</MudMenuItem>
|
||||
<MudMenuItem OnClick="DoNotDownloadLessThan80PercentAvailability" OnTouch="DoNotDownloadLessThan80PercentAvailability">Less than 80% Availability</MudMenuItem>
|
||||
<MudMenuItem OnClick="DoNotDownloadCurrentlyFilteredFiles" OnTouch="NormalPriorityCurrentlyFilteredFiles">Currently Filtered Files</MudMenuItem>
|
||||
</MudMenu>
|
||||
<MudMenu Icon="@Icons.Material.Outlined.FileDownload" Label="Normal Priority" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft">
|
||||
<MudMenuItem OnClick="NormalPriorityLessThan100PercentAvailability" OnTouch="NormalPriorityLessThan100PercentAvailability">Less Than 100% Availability</MudMenuItem>
|
||||
<MudMenuItem OnClick="NormalPriorityLessThan80PercentAvailability" OnTouch="NormalPriorityLessThan80PercentAvailability">Less than 80% Availability</MudMenuItem>
|
||||
<MudMenuItem OnClick="NormalPriorityCurrentlyFilteredFiles" OnTouch="NormalPriorityCurrentlyFilteredFiles">Currently Filtered Files</MudMenuItem>
|
||||
</MudMenu>
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.FilterList" OnClick="ShowFilterDialog" Title="Filter" />
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.FilterListOff" OnClick="RemoveFilter" Title="Remove Filter" />
|
||||
</MudToolBar>
|
||||
<MudSpacer />
|
||||
<MudTextField T="string" Value="SearchText" ValueChanged="SearchTextChanged" Immediate="true" DebounceInterval="500" Placeholder="Filter file list" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"></MudTextField>
|
||||
</ToolBarContent>
|
||||
<ColGroup>
|
||||
<col style="width: 30px" />
|
||||
@foreach (var column in GetColumns())
|
||||
{
|
||||
var style = column.Width.HasValue ? $"width: {column.Width.Value}px" : null;
|
||||
<col style="@style" />
|
||||
}
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
@foreach (var column in GetColumns())
|
||||
{
|
||||
<MudTh>
|
||||
@if (column.SortSelector is not null)
|
||||
{
|
||||
<MudTableSortLabel T="Torrent" SortDirectionChanged="@(c => SetSort(column.SortSelector, c))" InitialDirection="column.InitialDirection">@column.Header</MudTableSortLabel>
|
||||
}
|
||||
else
|
||||
{
|
||||
@column.Header
|
||||
}
|
||||
</MudTh>
|
||||
}
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
@foreach (var column in GetColumns())
|
||||
{
|
||||
<MudTd DataLabel="@column.Header" Class="@column.Class">
|
||||
@column.RowTemplate(column.GetRowContext(context))
|
||||
</MudTd>
|
||||
}
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
|
||||
@code {
|
||||
private RenderFragment<RowContext<ContentItem>> NameColumn
|
||||
{
|
||||
get
|
||||
{
|
||||
return context => __builder =>
|
||||
{
|
||||
<div style="@($"margin-left: {context.Data.Level * 56}px")">
|
||||
@if (context.Data.IsFolder)
|
||||
{
|
||||
<MudIconButton ButtonType="ButtonType.Button" Icon="@Icons.Material.Filled.ExpandLess" Class="@("pa-0 " + (ExpandedNodes.Contains(context.Data.Name) ? "rotate-180" : "rotate-90"))" OnClick="@(c => ToggleNode(context.Data, c))"></MudIconButton>
|
||||
<MudIcon Icon="@Icons.Material.Filled.Folder" Class="pt-2" Style="margin-right: 2px" />
|
||||
}
|
||||
@context.Data.DisplayName
|
||||
</div>;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private RenderFragment<RowContext<ContentItem>> PriorityColumn
|
||||
{
|
||||
get
|
||||
{
|
||||
return context => __builder =>
|
||||
{
|
||||
<MudSelect T="Priority" Dense="true" Value="@context.Data.Priority" ValueChanged="@(priority => PriorityValueChanged(context.Data, priority))" Class="mt-0">
|
||||
<MudSelectItem T="Priority" Value="Priority.DoNotDownload">Do not download</MudSelectItem>
|
||||
<MudSelectItem T="Priority" Value="Priority.Normal">Normal</MudSelectItem>
|
||||
<MudSelectItem T="Priority" Value="Priority.High">High</MudSelectItem>
|
||||
<MudSelectItem T="Priority" Value="Priority.Maximum">Maximum</MudSelectItem>
|
||||
</MudSelect>
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
561
Lantean.QBTMudBlade/Components/FilesTab.razor.cs
Normal file
561
Lantean.QBTMudBlade/Components/FilesTab.razor.cs
Normal file
@@ -0,0 +1,561 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMudBlade.Components.Dialogs;
|
||||
using Lantean.QBTMudBlade.Filter;
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Lantean.QBTMudBlade.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Net;
|
||||
using static MudBlazor.CategoryTypes;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
public partial class FilesTab : IAsyncDisposable
|
||||
{
|
||||
private readonly CancellationTokenSource _timerCancellationToken = new();
|
||||
private bool _disposedValue;
|
||||
|
||||
private Func<ContentItem, object?> SortSelector { get; set; } = c => c.Name;
|
||||
|
||||
private SortDirection SortDirection { get; set; } = SortDirection.Ascending;
|
||||
|
||||
[Parameter]
|
||||
public bool Active { get; set; }
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public string? Hash { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public int RefreshInterval { get; set; }
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDataManager DataManager { get; set; } = default!;
|
||||
|
||||
protected HashSet<string> ExpandedNodes { get; set; } = [];
|
||||
|
||||
protected Dictionary<string, ContentItem>? FileList { get; set; }
|
||||
|
||||
protected IEnumerable<ContentItem> Files => GetFiles();
|
||||
|
||||
protected HashSet<ContentItem> SelectedItems { get; set; } = [];
|
||||
|
||||
protected List<ColumnDefinition<ContentItem>> _columns = [];
|
||||
|
||||
protected ContentItem? SelectedItem { get; set; }
|
||||
|
||||
protected string? SearchText { get; set; }
|
||||
|
||||
protected int? _selectedIndex { get; set; }
|
||||
|
||||
protected HashSet<string> SelectedColumns { get; set; }
|
||||
|
||||
public IEnumerable<Func<ContentItem, bool>>? Filters { get; set; }
|
||||
|
||||
public FilesTab()
|
||||
{
|
||||
_columns.Add(CreateColumnDefinition("Name", c => c.Name, NameColumn, width: 200, initialDirection: SortDirection.Ascending));
|
||||
_columns.Add(CreateColumnDefinition("Total Size", c => c.Size, c => DisplayHelpers.Size(c.Size)));
|
||||
_columns.Add(CreateColumnDefinition("Progress", c => c.Progress, c => DisplayHelpers.Percentage(c.Progress)));
|
||||
_columns.Add(CreateColumnDefinition("Priority", c => c.Priority, PriorityColumn));
|
||||
_columns.Add(CreateColumnDefinition("Remaining", c => c.Remaining, c => DisplayHelpers.Size(c.Remaining)));
|
||||
_columns.Add(CreateColumnDefinition("Availability", c => c.Availability, c => c.Availability.ToString("0.00")));
|
||||
|
||||
SelectedColumns = _columns.Where(c => c.Enabled).Select(c => c.Id).ToHashSet();
|
||||
}
|
||||
|
||||
protected IEnumerable<ColumnDefinition<ContentItem>> GetColumns()
|
||||
{
|
||||
return _columns.Where(c => SelectedColumns.Contains(c.Id));
|
||||
}
|
||||
|
||||
protected async Task ColumnOptions()
|
||||
{
|
||||
DialogParameters parameters = new DialogParameters
|
||||
{
|
||||
{ "Columns", _columns }
|
||||
};
|
||||
|
||||
var reference = await DialogService.ShowAsync<ColumnOptionsDialog<ContentItem>>("ColumnOptions", parameters, DialogHelper.FormDialogOptions);
|
||||
|
||||
var result = await reference.Result;
|
||||
if (result.Canceled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SelectedColumns = (HashSet<string>)result.Data;
|
||||
}
|
||||
|
||||
protected async Task ShowFilterDialog()
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ nameof(FilterOptionsDialog<ContentItem>.FilterDefinitions), Filters },
|
||||
};
|
||||
|
||||
var result = await DialogService.ShowAsync<FilterOptionsDialog<ContentItem>>("Filters", parameters, DialogHelper.FormDialogOptions);
|
||||
|
||||
var dialogResult = await result.Result;
|
||||
if (dialogResult.Canceled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var filterDefinitions = (List<PropertyFilterDefinition<ContentItem>>?)dialogResult.Data;
|
||||
if (filterDefinitions is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var filters = new List<Func<ContentItem, bool>>();
|
||||
foreach (var filterDefinition in filterDefinitions)
|
||||
{
|
||||
var expression = Filter.FilterExpressionGenerator.GenerateExpression(filterDefinition, false);
|
||||
filters.Add(expression.Compile());
|
||||
}
|
||||
|
||||
Filters = filters;
|
||||
}
|
||||
|
||||
protected async Task RemoveFilter()
|
||||
{
|
||||
Filters = null;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
await DisposeAsync(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected static float CalculateProgress(IEnumerable<ContentItem> items)
|
||||
{
|
||||
return (float)items.Sum(i => i.Downloaded) / items.Sum(i => i.Size);
|
||||
}
|
||||
|
||||
protected static Priority GetPriority(IEnumerable<ContentItem> items)
|
||||
{
|
||||
var distinctPriorities = items.Select(i => i.Priority).Distinct();
|
||||
if (distinctPriorities.Count() == 1)
|
||||
{
|
||||
return distinctPriorities.First();
|
||||
}
|
||||
|
||||
return Priority.Mixed;
|
||||
}
|
||||
|
||||
protected virtual async Task DisposeAsync(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing && Files is not null)
|
||||
{
|
||||
_timerCancellationToken.Cancel();
|
||||
_timerCancellationToken.Dispose();
|
||||
|
||||
await Task.Delay(0);
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task SearchTextChanged(string value)
|
||||
{
|
||||
SearchText = value;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
if (FileList is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
SelectedItems = FileList.Values.Where(f => f.Priority != Priority.DoNotDownload).ToHashSet();
|
||||
}
|
||||
|
||||
protected async Task EnabledValueChanged(ContentItem contentItem, bool value)
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ApiClient.SetFilePriority(Hash, [contentItem.Index], MapPriority(value ? Priority.Normal : Priority.DoNotDownload));
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (!firstRender)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(RefreshInterval)))
|
||||
{
|
||||
while (!_timerCancellationToken.IsCancellationRequested && await timer.WaitForNextTickAsync())
|
||||
{
|
||||
if (Active && Hash is not null)
|
||||
{
|
||||
IReadOnlyList<QBitTorrentClient.Models.FileData> files;
|
||||
try
|
||||
{
|
||||
files = await ApiClient.GetTorrentContents(Hash);
|
||||
}
|
||||
catch (HttpRequestException exception) when (exception.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
_timerCancellationToken.CancelIfNotDisposed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (FileList is null)
|
||||
{
|
||||
FileList = DataManager.CreateContentsList(files);
|
||||
}
|
||||
else
|
||||
{
|
||||
DataManager.MergeContentsList(files, FileList);
|
||||
}
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var contents = await ApiClient.GetTorrentContents(Hash);
|
||||
FileList = DataManager.CreateContentsList(contents);
|
||||
|
||||
SelectedItems = FileList.Values.Where(f => f.Priority != Priority.DoNotDownload).ToHashSet();
|
||||
}
|
||||
|
||||
protected async Task PriorityValueChanged(ContentItem contentItem, Priority priority)
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IEnumerable<int> fileIndexes;
|
||||
if (contentItem.IsFolder)
|
||||
{
|
||||
fileIndexes = GetChildren(contentItem).Where(c => !c.IsFolder).Select(c => c.Index);
|
||||
}
|
||||
else
|
||||
{
|
||||
fileIndexes = [contentItem.Index];
|
||||
}
|
||||
|
||||
await ApiClient.SetFilePriority(Hash, fileIndexes, MapPriority(priority));
|
||||
}
|
||||
|
||||
protected string RowClass(ContentItem contentItem, int index)
|
||||
{
|
||||
if (contentItem.Level == 0)
|
||||
{
|
||||
return "d-table-row";
|
||||
}
|
||||
if (ExpandedNodes.Contains(contentItem.Path))
|
||||
{
|
||||
return "d-table-row";
|
||||
}
|
||||
return "d-none";
|
||||
}
|
||||
|
||||
protected async Task RenameFile()
|
||||
{
|
||||
if (Hash is null || FileList is null || _selectedIndex is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var contentItem = FileList.Values.FirstOrDefault(c => c.Index == _selectedIndex.Value);
|
||||
if (contentItem is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var name = contentItem.GetFileName();
|
||||
await DialogService.ShowSingleFieldDialog("Rename", "New name", name, async v => await ApiClient.RenameFile(Hash, contentItem.Name, contentItem.Path + v));
|
||||
}
|
||||
|
||||
protected void RowClick(TableRowClickEventArgs<ContentItem> eventArgs)
|
||||
{
|
||||
_selectedIndex = eventArgs.Item.Index;
|
||||
}
|
||||
|
||||
protected string RowStyle(ContentItem item, int index)
|
||||
{
|
||||
var style = "user-select: none; cursor: pointer;";
|
||||
if (_selectedIndex == item.Index)
|
||||
{
|
||||
style += " background: #D3D3D3";
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
protected async Task SelectedItemsChanged(HashSet<ContentItem> selectedItems)
|
||||
{
|
||||
if (Hash is null || Files is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var unselectedItems = Files.Except(SelectedItems);
|
||||
|
||||
if (unselectedItems.Any())
|
||||
{
|
||||
await ApiClient.SetFilePriority(Hash, unselectedItems.Select(c => c.Index), QBitTorrentClient.Models.Priority.DoNotDownload);
|
||||
|
||||
foreach (var item in unselectedItems)
|
||||
{
|
||||
Files.First(f => f == item).Priority = Priority.DoNotDownload;
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
var existingDoNotDownloads = Files.Where(f => f.Priority == Priority.DoNotDownload);
|
||||
var newlySelectedFiles = selectedItems.Where(f => existingDoNotDownloads.Contains(f));
|
||||
|
||||
if (newlySelectedFiles.Any())
|
||||
{
|
||||
await ApiClient.SetFilePriority(Hash, newlySelectedFiles.Select(c => c.Index), QBitTorrentClient.Models.Priority.Normal);
|
||||
|
||||
foreach (var item in newlySelectedFiles)
|
||||
{
|
||||
Files.First(f => f == item).Priority = Priority.Normal;
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetSort(Func<ContentItem, object?> sortSelector, SortDirection sortDirection)
|
||||
{
|
||||
SortSelector = sortSelector;
|
||||
SortDirection = sortDirection;
|
||||
}
|
||||
|
||||
protected void ToggleNode(ContentItem contentItem, MouseEventArgs args)
|
||||
{
|
||||
if (ExpandedNodes.Contains(contentItem.Name))
|
||||
{
|
||||
ExpandedNodes.Remove(contentItem.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
ExpandedNodes.Add(contentItem.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private static QBitTorrentClient.Models.Priority MapPriority(Priority priority)
|
||||
{
|
||||
return (QBitTorrentClient.Models.Priority)(int)priority;
|
||||
}
|
||||
|
||||
private IEnumerable<ContentItem> GetChildren(ContentItem contentItem)
|
||||
{
|
||||
if (!contentItem.IsFolder || Files is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return Files.Where(f => f.Name.StartsWith(contentItem.Name + Extensions.DirectorySeparator) && !f.IsFolder);
|
||||
}
|
||||
|
||||
private IEnumerable<ContentItem> GetDescendants(ContentItem folder, int level)
|
||||
{
|
||||
level++;
|
||||
var descendantsKey = folder.GetDescendantsKey(level);
|
||||
foreach (var item in FileList!.Values.Where(f => f.Name.StartsWith(descendantsKey)).OrderByDirection(SortDirection, SortSelector))
|
||||
{
|
||||
if (item.IsFolder)
|
||||
{
|
||||
var descendants = GetDescendants(item, level);
|
||||
// if the filter returns some resutls then show folder item
|
||||
if (descendants.Any())
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
// 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)
|
||||
{
|
||||
foreach (var filter in Filters)
|
||||
{
|
||||
var result = filter(item);
|
||||
if (!result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!FilterHelper.FilterTerms(item.Name, SearchText))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private ReadOnlyCollection<ContentItem> GetFiles()
|
||||
{
|
||||
if (FileList is null)
|
||||
{
|
||||
return new ReadOnlyCollection<ContentItem>([]);
|
||||
}
|
||||
|
||||
var maxLevel = FileList.Values.Max(f => f.Level);
|
||||
// this is a flat file structure
|
||||
if (maxLevel == 0)
|
||||
{
|
||||
return FileList.Values.Where(FilterContentItem).OrderByDirection(SortDirection, SortSelector).ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
var list = new List<ContentItem>();
|
||||
|
||||
var folders = FileList.Values.Where(c => c.IsFolder && c.Level == 0).OrderByDirection(SortDirection, SortSelector).ToList();
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
list.Add(folder);
|
||||
var level = 0;
|
||||
var descendants = GetDescendants(folder, level);
|
||||
foreach (var descendant in descendants)
|
||||
{
|
||||
list.Add(descendant);
|
||||
}
|
||||
}
|
||||
|
||||
return list.AsReadOnly();
|
||||
}
|
||||
|
||||
protected async Task DoNotDownloadLessThan100PercentAvailability()
|
||||
{
|
||||
await LessThanXAvailability(1f, QBitTorrentClient.Models.Priority.DoNotDownload);
|
||||
}
|
||||
|
||||
protected async Task DoNotDownloadLessThan80PercentAvailability()
|
||||
{
|
||||
await LessThanXAvailability(0.8f, QBitTorrentClient.Models.Priority.DoNotDownload);
|
||||
}
|
||||
|
||||
protected async Task DoNotDownloadCurrentlyFilteredFiles()
|
||||
{
|
||||
await CurrentlyFilteredFiles(QBitTorrentClient.Models.Priority.DoNotDownload);
|
||||
}
|
||||
|
||||
protected async Task NormalPriorityLessThan100PercentAvailability()
|
||||
{
|
||||
await LessThanXAvailability(1f, QBitTorrentClient.Models.Priority.Normal);
|
||||
}
|
||||
|
||||
protected async Task NormalPriorityLessThan80PercentAvailability()
|
||||
{
|
||||
await LessThanXAvailability(0.8f, QBitTorrentClient.Models.Priority.Normal);
|
||||
}
|
||||
|
||||
protected async Task NormalPriorityCurrentlyFilteredFiles()
|
||||
{
|
||||
await CurrentlyFilteredFiles(QBitTorrentClient.Models.Priority.Normal);
|
||||
}
|
||||
|
||||
private async Task LessThanXAvailability(float value, QBitTorrentClient.Models.Priority priority)
|
||||
{
|
||||
if (Hash is null || FileList is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var files = FileList.Values.Where(f => f.Availability < value).Select(f => f.Index);
|
||||
|
||||
if (!files.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ApiClient.SetFilePriority(Hash, files, priority);
|
||||
}
|
||||
|
||||
protected async Task CurrentlyFilteredFiles(QBitTorrentClient.Models.Priority priority)
|
||||
{
|
||||
if (Hash is null || FileList is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var files = GetFiles().Select(f => f.Index);
|
||||
|
||||
if (!files.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ApiClient.SetFilePriority(Hash, files, priority);
|
||||
}
|
||||
|
||||
private static ColumnDefinition<ContentItem> CreateColumnDefinition(string name, Func<ContentItem, object?> selector, RenderFragment<RowContext<ContentItem>> rowTemplate, int? width = null, string? tdClass = null, bool enabled = true, SortDirection initialDirection = SortDirection.None)
|
||||
{
|
||||
var cd = new ColumnDefinition<ContentItem>(name, selector, rowTemplate);
|
||||
cd.Class = "no-wrap";
|
||||
if (tdClass is not null)
|
||||
{
|
||||
cd.Class += " " + tdClass;
|
||||
}
|
||||
cd.Width = width;
|
||||
cd.Enabled = enabled;
|
||||
cd.InitialDirection = initialDirection;
|
||||
|
||||
return cd;
|
||||
}
|
||||
|
||||
private static ColumnDefinition<ContentItem> CreateColumnDefinition(string name, Func<ContentItem, object?> selector, Func<ContentItem, string>? formatter = null, int? width = null, string? tdClass = null, bool enabled = true, SortDirection initialDirection = SortDirection.None)
|
||||
{
|
||||
var cd = new ColumnDefinition<ContentItem>(name, selector, formatter);
|
||||
cd.Class = "no-wrap";
|
||||
if (tdClass is not null)
|
||||
{
|
||||
cd.Class += " " + tdClass;
|
||||
}
|
||||
cd.Width = width;
|
||||
cd.Enabled = enabled;
|
||||
cd.InitialDirection = initialDirection;
|
||||
|
||||
return cd;
|
||||
}
|
||||
}
|
||||
}
|
27
Lantean.QBTMudBlade/Components/FiltersNav.razor
Normal file
27
Lantean.QBTMudBlade/Components/FiltersNav.razor
Normal file
@@ -0,0 +1,27 @@
|
||||
<MudNavMenu Dense="true">
|
||||
<MudNavGroup Title="Status" @bind-Expanded="_statusExpanded">
|
||||
@foreach (var (status, count) in Statuses)
|
||||
{
|
||||
var (icon, color) = DisplayHelpers.GetStatusIcon(status);
|
||||
<FakeNavLink Active="@(Status == status)" Icon="@icon" IconColor="@color" OnClick="@(v => StatusValueChanged(status))">@($"{status.GetStatusName()} ({count})")</FakeNavLink>
|
||||
}
|
||||
</MudNavGroup>
|
||||
<MudNavGroup Title="Categories" @bind-Expanded="_categoriesExpanded">
|
||||
@foreach (var (category, count) in Categories)
|
||||
{
|
||||
<FakeNavLink Active="@(Category == category)" Icon="@Icons.Material.Filled.List" IconColor="Color.Info" OnClick="@(v => CategoryValueChanged(category))">@($"{category} ({count})")</FakeNavLink>
|
||||
}
|
||||
</MudNavGroup>
|
||||
<MudNavGroup Title="Tags" @bind-Expanded="_tagsExpanded">
|
||||
@foreach (var (tag, count) in Tags)
|
||||
{
|
||||
<FakeNavLink Active="@(Tag == tag)" Icon="@Icons.Material.Filled.Label" IconColor="Color.Info" OnClick="@(v => TagValueChanged(tag))">@($"{tag} ({count})")</FakeNavLink>
|
||||
}
|
||||
</MudNavGroup>
|
||||
<MudNavGroup Title="Trackers" @bind-Expanded="_trackersExpanded">
|
||||
@foreach (var (tracker, count) in Trackers)
|
||||
{
|
||||
<FakeNavLink Active="@(Tracker == tracker)" Icon="@Icons.Material.Filled.PinDrop" IconColor="Color.Info" OnClick="@(v => TrackerValueChanged(tracker))">@($"{GetHostName(tracker)} ({count})")</FakeNavLink>
|
||||
}
|
||||
</MudNavGroup>
|
||||
</MudNavMenu>
|
84
Lantean.QBTMudBlade/Components/FiltersNav.razor.cs
Normal file
84
Lantean.QBTMudBlade/Components/FiltersNav.razor.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
public partial class FiltersNav
|
||||
{
|
||||
private bool _statusExpanded = true;
|
||||
private bool _categoriesExpanded = true;
|
||||
private bool _tagsExpanded = true;
|
||||
private bool _trackersExpanded = true;
|
||||
|
||||
protected string Status { get; set; } = Models.Status.All.ToString();
|
||||
|
||||
protected string Category { get; set; } = FilterHelper.CATEGORY_ALL;
|
||||
|
||||
protected string Tag { get; set; } = FilterHelper.TAG_ALL;
|
||||
|
||||
protected string Tracker { get; set; } = FilterHelper.TRACKER_ALL;
|
||||
|
||||
[CascadingParameter]
|
||||
public MainData? MainData { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<string> CategoryChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<Status> StatusChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<string> TagChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<string> TrackerChanged { get; set; }
|
||||
|
||||
public Dictionary<string, int> Tags => MainData?.TagState.ToDictionary(d => d.Key, d => d.Value.Count) ?? [];
|
||||
|
||||
public Dictionary<string, int> Categories => MainData?.CategoriesState.ToDictionary(d => d.Key, d => d.Value.Count) ?? [];
|
||||
|
||||
public Dictionary<string, int> Trackers => MainData?.TrackersState.ToDictionary(d => d.Key, d => d.Value.Count) ?? [];
|
||||
|
||||
public Dictionary<string, int> Statuses => MainData?.StatusState.ToDictionary(d => d.Key, d => d.Value.Count) ?? [];
|
||||
|
||||
protected async Task StatusValueChanged(string value)
|
||||
{
|
||||
Status = value;
|
||||
await StatusChanged.InvokeAsync(Enum.Parse<Status>(value));
|
||||
}
|
||||
|
||||
protected async Task CategoryValueChanged(string value)
|
||||
{
|
||||
Category = value;
|
||||
await CategoryChanged.InvokeAsync(value);
|
||||
}
|
||||
|
||||
protected async Task TagValueChanged(string value)
|
||||
{
|
||||
Tag = value;
|
||||
await TagChanged.InvokeAsync(value);
|
||||
}
|
||||
|
||||
protected async Task TrackerValueChanged(string value)
|
||||
{
|
||||
Tracker = value;
|
||||
await TrackerChanged.InvokeAsync(value);
|
||||
}
|
||||
|
||||
protected static string GetHostName(string tracker)
|
||||
{
|
||||
try
|
||||
{
|
||||
var uri = new Uri(tracker);
|
||||
return uri.Host;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return tracker;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
97
Lantean.QBTMudBlade/Components/GeneralTab.razor
Normal file
97
Lantean.QBTMudBlade/Components/GeneralTab.razor
Normal file
@@ -0,0 +1,97 @@
|
||||
<div class="pl-6 pt-6"><MudText Typo="Typo.h6">Transfer</MudText></div>
|
||||
<MudGrid Class="pl-6 pr-6 pb-6">
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Time Active">@DisplayHelpers.Duration(Properties?.TimeElapsed)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="ETA">@DisplayHelpers.Duration(Properties?.EstimatedTimeOfArrival)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Connections">@DisplayHelpers.Duration(Properties?.Connections) @DisplayHelpers.EmptyIfNull(Properties?.ConnectionsLimit, "(", " max)")</MudField>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Downloaded">@DisplayHelpers.Size(Properties?.TotalDownloaded) @DisplayHelpers.Size(Properties?.TotalDownloadedSession, "(", " this session)")</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Uploaded">@DisplayHelpers.Size(Properties?.TotalUploaded) @DisplayHelpers.Size(Properties?.TotalUploaded, "(", " this session)")</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Seeds">@DisplayHelpers.Size(Properties?.Seeds) @DisplayHelpers.EmptyIfNull(Properties?.Seeds, "(", " total)")</MudField>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Download Speed">@DisplayHelpers.Speed(Properties?.DownloadSpeed) @DisplayHelpers.Speed(Properties?.DownloadSpeedAverage, "(", " avg.)")</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Upload Speed">@DisplayHelpers.Speed(Properties?.UploadSpeed) @DisplayHelpers.Speed(Properties?.UploadSpeedAverage, "(", " avg.)")</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Peers">@DisplayHelpers.Size(Properties?.Peers) @DisplayHelpers.EmptyIfNull(Properties?.Peers, "(", " total)")</MudField>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Download Limit">@DisplayHelpers.Speed(Properties?.DownloadLimit)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Upload Limit">@DisplayHelpers.Speed(Properties?.UploadLimit)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Wasted">@DisplayHelpers.Size(Properties?.TotalWasted)</MudField>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Share Ratio">@Properties?.ShareRatio.ToString("0.00")</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Reannounce In">@DisplayHelpers.Duration(Properties?.Reannounce)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Last Seen Complete">@DisplayHelpers.DateTime(Properties?.LastSeen, "Never")</MudField>
|
||||
</MudItem>
|
||||
|
||||
</MudGrid>
|
||||
|
||||
<div class="pl-6 pt-6"><MudText Typo="Typo.h6">Information</MudText></div>
|
||||
<MudGrid Class="pl-6 pr-6 pb-6">
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Total Size">@DisplayHelpers.Size(Properties?.TotalSize)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Pieces">
|
||||
@if (Properties is not null)
|
||||
{
|
||||
<text>@Properties.PiecesNum x @DisplayHelpers.Size(Properties.PieceSize) (have @Properties.PiecesHave)</text>
|
||||
}
|
||||
</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Created By">@Properties?.CreatedBy</MudField>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Added On">@DisplayHelpers.DateTime(Properties?.AdditionDate)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Completed On">@DisplayHelpers.DateTime(Properties?.CompletionDate)</MudField>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudField Label="Created On">@DisplayHelpers.DateTime(Properties?.CreationDate)</MudField>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Info Hash v1">@Properties?.InfoHashV1</MudField>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Info Hash v2">@Properties?.InfoHashV2</MudField>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Save Path">@Properties?.SavePath</MudField>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudField Label="Commenet">@Properties?.Comment</MudField>
|
||||
</MudItem>
|
||||
</MudGrid>
|
101
Lantean.QBTMudBlade/Components/GeneralTab.razor.cs
Normal file
101
Lantean.QBTMudBlade/Components/GeneralTab.razor.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using Lantean.QBTMudBlade.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using System.Net;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
public partial class GeneralTab : IAsyncDisposable
|
||||
{
|
||||
private readonly CancellationTokenSource _timerCancellationToken = new();
|
||||
private bool _disposedValue;
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public string? Hash { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Active { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public int RefreshInterval { get; set; }
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDataManager DataManager { get; set; } = default!;
|
||||
|
||||
protected IReadOnlyList<PieceState> Pieces { get; set; } = [];
|
||||
|
||||
protected TorrentProperties Properties { get; set; } = default!;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Pieces = await ApiClient.GetTorrentPieceStates(Hash);
|
||||
Properties = await ApiClient.GetTorrentProperties(Hash);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
using (var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(RefreshInterval)))
|
||||
{
|
||||
while (!_timerCancellationToken.IsCancellationRequested && await timer.WaitForNextTickAsync())
|
||||
{
|
||||
if (Active && Hash is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Pieces = await ApiClient.GetTorrentPieceStates(Hash);
|
||||
Properties = await ApiClient.GetTorrentProperties(Hash);
|
||||
}
|
||||
catch (HttpRequestException exception) when (exception.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
_timerCancellationToken.CancelIfNotDisposed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual async Task DisposeAsync(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_timerCancellationToken.Cancel();
|
||||
_timerCancellationToken.Dispose();
|
||||
await Task.Delay(0);
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
await DisposeAsync(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
9
Lantean.QBTMudBlade/Components/Menu.razor
Normal file
9
Lantean.QBTMudBlade/Components/Menu.razor
Normal file
@@ -0,0 +1,9 @@
|
||||
<MudMenu Icon="@Icons.Material.Filled.MoreVert" Color="Color.Inherit" Dense="true" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft">
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.PieChart" OnClick="Statistics" OnTouch="Statistics">Statistics</MudMenuItem>
|
||||
<MudDivider />
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Settings" OnClick="Settings" OnTouch="Settings">Settings</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Undo" OnClick="ResetWebUI" OnTouch="ResetWebUI">Reset Web UI</MudMenuItem>
|
||||
<MudDivider />
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Logout" OnClick="Logout" OnTouch="Logout">Logout</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.ExitToApp" OnClick="Exit" OnTouch="Exit">Exit qBittorrent</MudMenuItem>
|
||||
</MudMenu>
|
58
Lantean.QBTMudBlade/Components/Menu.razor.cs
Normal file
58
Lantean.QBTMudBlade/Components/Menu.razor.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
public partial class Menu
|
||||
{
|
||||
[Inject]
|
||||
protected NavigationManager NavigationManager { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
protected async Task ResetWebUI()
|
||||
{
|
||||
var preferences = new UpdatePreferences
|
||||
{
|
||||
AlternativeWebuiPath = null,
|
||||
AlternativeWebuiEnabled = false,
|
||||
};
|
||||
|
||||
await ApiClient.SetApplicationPreferences(preferences);
|
||||
|
||||
NavigationManager.NavigateTo("/", true);
|
||||
}
|
||||
|
||||
protected void Settings()
|
||||
{
|
||||
NavigationManager.NavigateTo("/options");
|
||||
}
|
||||
|
||||
protected void Statistics()
|
||||
{
|
||||
NavigationManager.NavigateTo("/statistics");
|
||||
}
|
||||
|
||||
protected async Task Logout()
|
||||
{
|
||||
await DialogService.ShowConfirmDialog("Logout?", "Are you sure you want to logout?", async () =>
|
||||
{
|
||||
await ApiClient.Logout();
|
||||
|
||||
NavigationManager.NavigateTo("/login", true);
|
||||
});
|
||||
}
|
||||
|
||||
protected async Task Exit()
|
||||
{
|
||||
await DialogService.ShowConfirmDialog("Quit?", "Are you sure you want to exit qBittorrent?", ApiClient.Shutdown);
|
||||
}
|
||||
}
|
||||
}
|
1
Lantean.QBTMudBlade/Components/NonRendering.razor
Normal file
1
Lantean.QBTMudBlade/Components/NonRendering.razor
Normal file
@@ -0,0 +1 @@
|
||||
@ChildContent
|
16
Lantean.QBTMudBlade/Components/NonRendering.razor.cs
Normal file
16
Lantean.QBTMudBlade/Components/NonRendering.razor.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple razor wrapper that only renders the child content without any additonal html markup
|
||||
/// </summary>
|
||||
public partial class NonRendering
|
||||
{
|
||||
/// <summary>
|
||||
/// The child content to be rendered
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
}
|
||||
}
|
14
Lantean.QBTMudBlade/Components/Options/AdvancedOptions.razor
Normal file
14
Lantean.QBTMudBlade/Components/Options/AdvancedOptions.razor
Normal file
@@ -0,0 +1,14 @@
|
||||
@inherits Options
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6"></MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
@@ -0,0 +1,10 @@
|
||||
namespace Lantean.QBTMudBlade.Components.Options
|
||||
{
|
||||
public partial class AdvancedOptions : Options
|
||||
{
|
||||
protected override bool SetOptions()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,69 @@
|
||||
@inherits Options
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6">Language</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string" Label="User Interface Language" Value="@("en-US")">
|
||||
<MudSelectItem Value="@("en-US")">English</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6">Log File</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox T="bool" Label="Log file" Value="FileLogEnabled" ValueChanged="FileLogEnabledChanged" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Save Path" Value="FileLogPath" ValueChanged="FileLogPathChanged" Disabled="@(!FileLogEnabled)" />
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<MudCheckBox T="bool" Label="Backup the log after" Value="FileLogBackupEnabled" ValueChanged="FileLogBackupEnabledChanged" Disabled="@(!FileLogEnabled)" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="9">
|
||||
<MudNumericField T="int" Label="KiB" Value="FileLogMaxSize" ValueChanged="FileLogMaxSizeChanged" Disabled="@(!FileLogEnabled)" ShrinkLabel Min="1" />
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<MudCheckBox T="bool" Label="Delete backups older than" Value="FileLogDeleteOld" ValueChanged="FileLogDeleteOldChanged" Disabled="@(!FileLogEnabled)" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="9">
|
||||
<MudGrid>
|
||||
<MudItem xs="9">
|
||||
<MudNumericField T="int" Value="FileLogAge" ValueChanged="FileLogAgeChanged" Disabled="@(!FileLogEnabled)" ShrinkLabel Min="1" />
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<MudSelect T="int" Value="FileLogAgeType" ValueChanged="FileLogAgeTypeChanged" Disabled="@(!FileLogEnabled)" ShrinkLabel>
|
||||
<MudSelectItem Value="0">days</MudSelectItem>
|
||||
<MudSelectItem Value="1">months</MudSelectItem>
|
||||
<MudSelectItem Value="2">years</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox T="bool" Label="Log performance warnings" Value="PerformanceWarning" ValueChanged="PerformanceWarningChanged" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
@@ -0,0 +1,99 @@
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components.Options
|
||||
{
|
||||
public partial class BehaviourOptions : Options
|
||||
{
|
||||
protected bool FileLogEnabled { get; set; }
|
||||
|
||||
protected string? FileLogPath { get; set; }
|
||||
|
||||
protected bool FileLogBackupEnabled { get; set; }
|
||||
|
||||
protected int FileLogMaxSize { get; set; }
|
||||
|
||||
protected bool FileLogDeleteOld { get; set; }
|
||||
|
||||
protected int FileLogAge { get; set; }
|
||||
|
||||
protected int FileLogAgeType { get; set; }
|
||||
|
||||
protected bool PerformanceWarning { get; set; }
|
||||
|
||||
protected override bool SetOptions()
|
||||
{
|
||||
if (Preferences is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FileLogEnabled = Preferences.FileLogEnabled;
|
||||
FileLogPath = Preferences.FileLogPath;
|
||||
FileLogBackupEnabled = Preferences.FileLogBackupEnabled;
|
||||
FileLogMaxSize = Preferences.FileLogMaxSize;
|
||||
FileLogDeleteOld = Preferences.FileLogDeleteOld;
|
||||
FileLogAge = Preferences.FileLogAge;
|
||||
FileLogAgeType = Preferences.FileLogAgeType;
|
||||
PerformanceWarning = Preferences.PerformanceWarning;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async Task FileLogEnabledChanged(bool value)
|
||||
{
|
||||
FileLogEnabled = value;
|
||||
UpdatePreferences.FileLogEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected async Task FileLogPathChanged(string value)
|
||||
{
|
||||
FileLogPath = value;
|
||||
UpdatePreferences.FileLogPath = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task FileLogBackupEnabledChanged(bool value)
|
||||
{
|
||||
FileLogBackupEnabled = value;
|
||||
UpdatePreferences.FileLogBackupEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task FileLogMaxSizeChanged(int value)
|
||||
{
|
||||
FileLogMaxSize = value;
|
||||
UpdatePreferences.FileLogMaxSize = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task FileLogDeleteOldChanged(bool value)
|
||||
{
|
||||
FileLogDeleteOld = value;
|
||||
UpdatePreferences.FileLogDeleteOld = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task FileLogAgeChanged(int value)
|
||||
{
|
||||
FileLogAge = value;
|
||||
UpdatePreferences.FileLogAge = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task FileLogAgeTypeChanged(int value)
|
||||
{
|
||||
FileLogAgeType = value;
|
||||
UpdatePreferences.FileLogAgeType = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task PerformanceWarningChanged(bool value)
|
||||
{
|
||||
PerformanceWarning = value;
|
||||
UpdatePreferences.PerformanceWarning = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
@inherits Options
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6"></MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
@@ -0,0 +1,10 @@
|
||||
namespace Lantean.QBTMudBlade.Components.Options
|
||||
{
|
||||
public partial class BitTorrentOptions : Options
|
||||
{
|
||||
protected override bool SetOptions()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
@inherits Options
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
|
||||
<MudCardContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="int" Label="Peer connection protocol" Value="BittorrentProtocol" ValueChanged="BittorrentProtocolChanged">
|
||||
<MudSelectItem T="int" Value="0">TCP and μTP</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="1">TCP</MudSelectItem>
|
||||
<MudSelectItem T="int" Value="2">μTP</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6">Listening Port</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="11">
|
||||
<MudNumericField T="int" Label="Port used for incoming connections" Value="ListenPort" ValueChanged="ListenPortChanged" Min="@MinPortValue" Max="@MaxPortValue" />
|
||||
</MudItem>
|
||||
<MudItem xs="1">
|
||||
<MudButton OnClick="GenerateRandomPort">Random</MudButton>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox T="bool" Label="Use UPnp / NAT-PMP port forwarding from my router" Value="Upnp" ValueChanged="UpnpChanged" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6">Connections Limits</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudCheckBox T="bool" Label="Global maximum number of connections" Value="MaxConnecEnabled" ValueChanged="MaxConnecEnabledChanged" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudNumericField T="int" Label="Connections" Value="MaxConnec" ValueChanged="MaxConnecChanged" ShrinkLabel Min="1" Disabled="@(!MaxConnecEnabled)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudCheckBox T="bool" Label="Maximum number of connections per torrent" Value="MaxConnecPerTorrentEnabled" ValueChanged="MaxConnecPerTorrentEnabledChanged" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudNumericField T="int" Label="Connections" Value="MaxConnecPerTorrent" ValueChanged="MaxConnecPerTorrentChanged" ShrinkLabel Min="1" Disabled="@(!MaxConnecPerTorrentEnabled)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudCheckBox T="bool" Label="Global maximum number of upload slots" Value="MaxUploadsEnabled" ValueChanged="MaxUploadsEnabledChanged" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudNumericField T="int" Label="Slots" Value="MaxUploads" ValueChanged="MaxUploadsChanged" ShrinkLabel Min="1" Disabled="@(!MaxUploadsEnabled)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudCheckBox T="bool" Label="Maximum number of upload slots per torrent" Value="MaxUploadsPerTorrentEnabled" ValueChanged="MaxUploadsPerTorrentEnabledChanged" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudNumericField T="int" Label="Slots" Value="MaxUploadsPerTorrent" ValueChanged="MaxUploadsPerTorrentChanged" ShrinkLabel Min="1" Disabled="@(!MaxUploadsPerTorrentEnabled)" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
@@ -0,0 +1,330 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components.Options
|
||||
{
|
||||
public partial class ConnectionOptions : Options
|
||||
{
|
||||
protected int BittorrentProtocol { get; private set; }
|
||||
protected int ListenPort { get; private set; }
|
||||
protected bool Upnp { get; private set; }
|
||||
protected bool MaxConnecEnabled { get; private set; }
|
||||
protected int MaxConnec { get; private set; }
|
||||
protected bool MaxConnecPerTorrentEnabled { get; private set; }
|
||||
protected int MaxConnecPerTorrent { get; private set; }
|
||||
protected bool MaxUploadsEnabled { get; private set; }
|
||||
protected int MaxUploads { get; private set; }
|
||||
protected bool MaxUploadsPerTorrentEnabled { get; private set; }
|
||||
protected int MaxUploadsPerTorrent { get; private set; }
|
||||
protected bool I2pEnabled { get; private set; }
|
||||
protected string? I2pAddress { get; private set; }
|
||||
protected int I2pPort { get; private set; }
|
||||
protected bool I2pMixedMode { get; private set; }
|
||||
protected string? ProxyType { get; private set; }
|
||||
protected string? ProxyIp { get; private set; }
|
||||
protected int ProxyPort { get; private set; }
|
||||
protected bool ProxyAuthEnabled { get; private set; }
|
||||
protected string? ProxyUsername { get; private set; }
|
||||
protected string? ProxyPassword { get; private set; }
|
||||
protected bool ProxyHostnameLookup { get; private set; }
|
||||
protected bool ProxyBittorrent { get; private set; }
|
||||
protected bool ProxyPeerConnections { get; private set; }
|
||||
protected bool ProxyRss { get; private set; }
|
||||
protected bool ProxyMisc { get; private set; }
|
||||
protected bool IpFilterEnabled { get; private set; }
|
||||
protected string? IpFilterPath { get; private set; }
|
||||
protected bool IpFilterTrackers { get; private set; }
|
||||
protected string? BannedIPs { get; private set; }
|
||||
|
||||
protected override bool SetOptions()
|
||||
{
|
||||
if (Preferences is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
BittorrentProtocol = Preferences.BittorrentProtocol;
|
||||
ListenPort = Preferences.ListenPort;
|
||||
Upnp = Preferences.Upnp;
|
||||
if (Preferences.MaxConnec > 0)
|
||||
{
|
||||
MaxConnecEnabled = true;
|
||||
MaxConnec = Preferences.MaxConnec;
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxConnecEnabled = false;
|
||||
MaxConnec = 500;
|
||||
}
|
||||
|
||||
if (Preferences.MaxConnecPerTorrent > 0)
|
||||
{
|
||||
MaxConnecPerTorrentEnabled = true;
|
||||
MaxConnecPerTorrent = Preferences.MaxConnecPerTorrent;
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxConnecPerTorrentEnabled = false;
|
||||
MaxConnecPerTorrent = 100;
|
||||
}
|
||||
|
||||
if (Preferences.MaxUploads > 0)
|
||||
{
|
||||
MaxUploadsEnabled = true;
|
||||
MaxUploads = Preferences.MaxUploads;
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxUploadsEnabled = false;
|
||||
MaxUploads = 20;
|
||||
}
|
||||
|
||||
if (Preferences.MaxUploadsPerTorrent > 0)
|
||||
{
|
||||
MaxUploadsPerTorrentEnabled = true;
|
||||
MaxUploadsPerTorrent = Preferences.MaxUploadsPerTorrent;
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxUploadsPerTorrentEnabled = false;
|
||||
MaxUploadsPerTorrent = 4;
|
||||
}
|
||||
|
||||
I2pEnabled = Preferences.I2pEnabled;
|
||||
I2pAddress = Preferences.I2pAddress;
|
||||
I2pPort = Preferences.I2pPort;
|
||||
I2pMixedMode = Preferences.I2pMixedMode;
|
||||
|
||||
ProxyType = Preferences.ProxyType;
|
||||
ProxyIp = Preferences.ProxyIp;
|
||||
ProxyPort = Preferences.ProxyPort;
|
||||
ProxyAuthEnabled = Preferences.ProxyAuthEnabled;
|
||||
ProxyUsername = Preferences.ProxyUsername;
|
||||
ProxyPassword = Preferences.ProxyPassword;
|
||||
ProxyHostnameLookup = Preferences.ProxyHostnameLookup;
|
||||
ProxyBittorrent = Preferences.ProxyBittorrent;
|
||||
ProxyPeerConnections = Preferences.ProxyPeerConnections;
|
||||
ProxyRss = Preferences.ProxyRss;
|
||||
ProxyMisc = Preferences.ProxyMisc;
|
||||
|
||||
IpFilterEnabled = Preferences.IpFilterEnabled;
|
||||
IpFilterPath = Preferences.IpFilterPath;
|
||||
IpFilterTrackers = Preferences.IpFilterTrackers;
|
||||
BannedIPs = Preferences.BannedIPs;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async Task BittorrentProtocolChanged(int value)
|
||||
{
|
||||
BittorrentProtocol = value;
|
||||
UpdatePreferences.BittorrentProtocol = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ListenPortChanged(int value)
|
||||
{
|
||||
ListenPort = value;
|
||||
UpdatePreferences.ListenPort = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task UpnpChanged(bool value)
|
||||
{
|
||||
Upnp = value;
|
||||
UpdatePreferences.Upnp = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected void MaxConnecEnabledChanged(bool value)
|
||||
{
|
||||
MaxConnecEnabled = value;
|
||||
}
|
||||
|
||||
protected async Task MaxConnecChanged(int value)
|
||||
{
|
||||
MaxConnec = value;
|
||||
UpdatePreferences.MaxConnec = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected void MaxConnecPerTorrentEnabledChanged(bool value)
|
||||
{
|
||||
MaxConnecPerTorrentEnabled = value;
|
||||
}
|
||||
|
||||
protected async Task MaxConnecPerTorrentChanged(int value)
|
||||
{
|
||||
MaxConnecPerTorrent = value;
|
||||
UpdatePreferences.MaxConnecPerTorrent = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected void MaxUploadsEnabledChanged(bool value)
|
||||
{
|
||||
MaxUploadsEnabled = value;
|
||||
}
|
||||
|
||||
protected async Task MaxUploadsChanged(int value)
|
||||
{
|
||||
MaxUploads = value;
|
||||
UpdatePreferences.MaxUploads = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected void MaxUploadsPerTorrentEnabledChanged(bool value)
|
||||
{
|
||||
MaxUploadsPerTorrentEnabled = value;
|
||||
}
|
||||
|
||||
protected async Task MaxUploadsPerTorrentChanged(int value)
|
||||
{
|
||||
MaxUploadsPerTorrent = value;
|
||||
UpdatePreferences.MaxUploadsPerTorrent = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task I2pEnabledChanged(bool value)
|
||||
{
|
||||
I2pEnabled = value;
|
||||
UpdatePreferences.I2pEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task I2pAddressChanged(string value)
|
||||
{
|
||||
I2pAddress = value;
|
||||
UpdatePreferences.I2pAddress = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task I2pPortChanged(int value)
|
||||
{
|
||||
I2pPort = value;
|
||||
UpdatePreferences.I2pPort = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task I2pMixedModeChanged(bool value)
|
||||
{
|
||||
I2pMixedMode = value;
|
||||
UpdatePreferences.I2pMixedMode = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyTypeChanged(string value)
|
||||
{
|
||||
ProxyType = value;
|
||||
UpdatePreferences.ProxyType = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyIpChanged(string value)
|
||||
{
|
||||
ProxyIp = value;
|
||||
UpdatePreferences.ProxyIp = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyPortChanged(int value)
|
||||
{
|
||||
ProxyPort = value;
|
||||
UpdatePreferences.ProxyPort = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyAuthEnabledChanged(bool value)
|
||||
{
|
||||
ProxyAuthEnabled = value;
|
||||
UpdatePreferences.ProxyAuthEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyUsernameChanged(string value)
|
||||
{
|
||||
ProxyUsername = value;
|
||||
UpdatePreferences.ProxyUsername = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyPasswordChanged(string value)
|
||||
{
|
||||
ProxyPassword = value;
|
||||
UpdatePreferences.ProxyPassword = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyHostnameLookupChanged(bool value)
|
||||
{
|
||||
ProxyHostnameLookup = value;
|
||||
UpdatePreferences.ProxyHostnameLookup = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyBittorrentChanged(bool value)
|
||||
{
|
||||
ProxyBittorrent = value;
|
||||
UpdatePreferences.ProxyBittorrent = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyPeerConnectionsChanged(bool value)
|
||||
{
|
||||
ProxyPeerConnections = value;
|
||||
UpdatePreferences.ProxyPeerConnections = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyRssChanged(bool value)
|
||||
{
|
||||
ProxyRss = value;
|
||||
UpdatePreferences.ProxyRss = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ProxyMiscChanged(bool value)
|
||||
{
|
||||
ProxyMisc = value;
|
||||
UpdatePreferences.ProxyMisc = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task IpFilterEnabledChanged(bool value)
|
||||
{
|
||||
IpFilterEnabled = value;
|
||||
UpdatePreferences.IpFilterEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task IpFilterPathChanged(string value)
|
||||
{
|
||||
IpFilterPath = value;
|
||||
UpdatePreferences.IpFilterPath = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task IpFilterTrackersChanged(bool value)
|
||||
{
|
||||
IpFilterTrackers = value;
|
||||
UpdatePreferences.IpFilterTrackers = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task BannedIPsChanged(string value)
|
||||
{
|
||||
BannedIPs = value;
|
||||
UpdatePreferences.BannedIPs = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected const int MinPortValue = 1024;
|
||||
protected const int MaxPortValue = 65535;
|
||||
|
||||
protected async Task GenerateRandomPort()
|
||||
{
|
||||
var random = new Random();
|
||||
var port = random.Next(MinPortValue, MaxPortValue);
|
||||
|
||||
await ListenPortChanged(port);
|
||||
}
|
||||
}
|
||||
}
|
287
Lantean.QBTMudBlade/Components/Options/DownloadsOptions.razor
Normal file
287
Lantean.QBTMudBlade/Components/Options/DownloadsOptions.razor
Normal file
@@ -0,0 +1,287 @@
|
||||
@inherits Options
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6">When adding a torrent</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string" Label="Torrent content layout" Value="TorrentContentLayout" ValueChanged="TorrentContentLayoutChanged" ShrinkLabel>
|
||||
<MudSelectItem Value="@("Original")">Original</MudSelectItem>
|
||||
<MudSelectItem Value="@("Subfolder")">Create subfolder</MudSelectItem>
|
||||
<MudSelectItem Value="@("NoSubfolder")">Don't create subfolder</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox T="bool" Label="Add to top of queue" Value="AddToTopOfQueue" ValueChanged="AddToTopOfQueueChanged" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox T="bool" Label="Do not start the download automatically" Value="StartPausedEnabled" ValueChanged="StartPausedEnabledChanged" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string" Label="Torrent stop condition" Value="TorrentStopCondition" ValueChanged="TorrentStopConditionChanged" ShrinkLabel>
|
||||
<MudSelectItem Value="@("None")">None</MudSelectItem>
|
||||
<MudSelectItem Value="@("MetadataReceived")">Metadata received</MudSelectItem>
|
||||
<MudSelectItem Value="@("FilesChecked")">Files Checked</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox T="bool" Label="Delete .torrent files afterwards" Value="AutoDeleteMode" ValueChanged="AutoDeleteModeChanged" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox T="bool" Label="Pre-allocate disk space for all files" Value="PreallocateAll" ValueChanged="PreallocateAllChanged" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox T="bool" Label="Append .!qB extension to incomplete files" Value="IncompleteFilesExt" ValueChanged="IncompleteFilesExtChanged" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6">Saving Management</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="bool" Label="Default Torrent Management Mode" Value="AutoTmmEnabled" ValueChanged="AutoDeleteModeChanged" ShrinkLabel>
|
||||
<MudSelectItem Value="false">Manual</MudSelectItem>
|
||||
<MudSelectItem Value="true">Automatic</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="bool" Label="When Torrent Category changed" Value="TorrentChangedTmmEnabled" ValueChanged="TorrentChangedTmmEnabledChanged" ShrinkLabel>
|
||||
<MudSelectItem Value="true">Relocate torrent</MudSelectItem>
|
||||
<MudSelectItem Value="false">Switch torrent to Manual Mode</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="bool" Label="When Default Save Path changed" Value="SavePathChangedTmmEnabled" ValueChanged="SavePathChangedTmmEnabledChanged" ShrinkLabel>
|
||||
<MudSelectItem Value="true">Relocate affected torrents</MudSelectItem>
|
||||
<MudSelectItem Value="false">Switch affected torrents to Manual Mode</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="bool" Label="When Category Save Path changed" Value="CategoryChangedTmmEnabled" ValueChanged="CategoryChangedTmmEnabledChanged" ShrinkLabel>
|
||||
<MudSelectItem Value="true">Relocate affected torrents</MudSelectItem>
|
||||
<MudSelectItem Value="false">Switch affected torrents to Manual Mode</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox T="bool" Label="Use Subcategories" Value="UseSubcategories" ValueChanged="UseSubcategoriesChanged" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Default Save Path" Value="SavePath" ValueChanged="SavePathChanged" ShrinkLabel />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudCheckBox T="bool" Label="Keep incomplete torrents in" Value="TempPathEnabled" ValueChanged="TempPathEnabledChanged" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="9">
|
||||
<MudTextField T="string" Label="Path" Value="TempPath" ValueChanged="TempPathChanged" ShrinkLabel Disabled="@(!TempPathEnabled)" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudCheckBox T="bool" Label="Copy .torrent files to" Value="ExportDirEnabled" ValueChanged="ExportDirEnabledChanged" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="9">
|
||||
<MudTextField T="string" Label="Path" Value="ExportDir" ValueChanged="ExportDirChanged" ShrinkLabel Disabled="@(!TempPathEnabled)" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudCheckBox T="bool" Label="Copy .torrent files for finished downloads to" Value="ExportDirFinEnabled" ValueChanged="ExportDirFinEnabledChanged" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="9">
|
||||
<MudTextField T="string" Label="Path" Value="ExportDirFin" ValueChanged="ExportDirFinChanged" ShrinkLabel Disabled="@(!TempPathEnabled)" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6">Automatically add torrents from</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudSimpleTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Monitored Folder</th>
|
||||
<th>Override Save Location</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in ScanDirs)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<MudTextField T="string" Label="Path" Value="@item.Key" ValueChanged="@(v => ScanDirsKeyChanged(item.Key, v))" />
|
||||
</td>
|
||||
<td>
|
||||
<MudGrid>
|
||||
<MudItem xs="@(item.Value.SavePath is null ? 12 : 3)">
|
||||
<MudSelect T="string" Value="@item.Value.ToString()" ValueChanged="@(v => ScanDirsValueChanged(item.Key, v))">
|
||||
<MudSelectItem T="string" Value="@("0")">Monitored folder</MudSelectItem>
|
||||
<MudSelectItem T="string" Value="@("1")">Default save location</MudSelectItem>
|
||||
<MudSelectItem T="string" Value="@(item.Value.SavePath is not null ? item.Value.SavePath : "")">Other...</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
@if (item.Value.SavePath is not null)
|
||||
{
|
||||
<MudItem xs="9">
|
||||
<MudTextField T="string" Label="Path" Value="@item.Value.SavePath" ValueChanged="@(v => ScanDirsValueChanged(item.Key, v))" />
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@for (int i = 0; i < AddedScanDirs.Count; i++)
|
||||
{
|
||||
var item = AddedScanDirs[i];
|
||||
var index = i;
|
||||
var isLast = i == AddedScanDirs.Count - 1;
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<MudTextField T="string" Label="Path" Value="@item.Key" ValueChanged="@(v => AddedScanDirsKeyChanged(index, v))" VaMudListItemdation="IsVaMudListItemdNewKey" />
|
||||
</td>
|
||||
<td>
|
||||
<MudGrid>
|
||||
<MudItem xs="@(item.Value.SavePath is null ? (isLast ? 11 : 12) : 3)">
|
||||
<MudSelect T="string" Value="@item.Value.ToString()" ValueChanged="@(v => AddedScanDirsValueChanged(index, v))">
|
||||
<MudSelectItem T="string" Value="@("0")">Monitored folder</MudSelectItem>
|
||||
<MudSelectItem T="string" Value="@("1")">Default save location</MudSelectItem>
|
||||
<MudSelectItem T="string" Value="@(item.Value.SavePath is not null ? item.Value.SavePath : "")">Other...</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
@if (item.Value.SavePath is not null)
|
||||
{
|
||||
<MudItem xs="@(isLast ? 8 : 9)">
|
||||
<MudTextField T="string" Label="Path" Value="@item.Value.SavePath" ValueChanged="@(v => AddedScanDirsValueChanged(index, v))" />
|
||||
</MudItem>
|
||||
}
|
||||
@if (isLast)
|
||||
{
|
||||
<MudItem xs="1">
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.Add" OnCMudListItemck="AddNewScanDir" />
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</MudSimpleTable>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox T="bool" Label="Excluded file names" Value="ExcludedFileNamesEnabled" ValueChanged="ExcludedFileNamesEnabledChanged" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Excluded files names" Value="ExcludedFileNames" ValueChanged="ExcludedFileNamesChanged" MudListItemnes="5" ShrinkLabel Disabled="@(!ExcludedFileNamesEnabled)" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardContent>
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox T="bool" Label="Email notification upon download completion" Value="MailNotificationEnabled" ValueChanged="MailNotificationEnabledChanged" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="From" Value="MailNotificationSender" ValueChanged="MailNotificationSenderChanged" ShrinkLabel Disabled="@(!MailNotificationEnabled)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="To" Value="MailNotificationEmail" ValueChanged="MailNotificationEmailChanged" ShrinkLabel Disabled="@(!MailNotificationEnabled)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="SMTP server" Value="MailNotificationSmtp" ValueChanged="MailNotificationSmtpChanged" ShrinkLabel Disabled="@(!MailNotificationEnabled)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox T="bool" Label="This server requires a secure connection (SSL)" Value="MailNotificationSslEnabled" ValueChanged="MailNotificationSslEnabledChanged" LabelPosition="LabelPosition.End" Disabled="@(!MailNotificationEnabled)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox T="bool" Label="Authentication" Value="MailNotificationAuthEnabled" ValueChanged="MailNotificationAuthEnabledChanged" LabelPosition="LabelPosition.End" Disabled="@(!MailNotificationEnabled)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Username" Value="MailNotificationUsername" ValueChanged="MailNotificationUsernameChanged" ShrinkLabel Disabled="@(!(MailNotificationEnabled && MailNotificationAuthEnabled))" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="Password" Value="MailNotificationPassword" ValueChanged="MailNotificationPasswordChanged" ShrinkLabel Disabled="@(!(MailNotificationEnabled && MailNotificationAuthEnabled))" InputType="InputType.Password" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6">Run exernal program</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox T="bool" Label="Run external program on torrent added" Value="AutorunOnTorrentAddedEnabled" ValueChanged="AutorunOnTorrentAddedEnabledChanged" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="External program" Value="AutorunOnTorrentAddedProgram" ValueChanged="AutorunOnTorrentAddedProgramChanged" ShrinkLabel Disabled="@(!AutorunOnTorrentAddedEnabled)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudCheckBox T="bool" Label="Run external program on torrent finished" Value="AutorunEnabled" ValueChanged="AutorunEnabledChanged" LabelPosition="LabelPosition.End" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField T="string" Label="External program" Value="AutorunProgram" ValueChanged="AutorunProgramChanged" ShrinkLabel Disabled="@(!AutorunEnabled)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudText>Supported parameters (case sensitive):</MudText>
|
||||
<MudList>
|
||||
<MudListItem>%N: Torrent name</MudListItem>
|
||||
<MudListItem>%L: Category</MudListItem>
|
||||
<MudListItem>%G: Tags (separated by comma)</MudListItem>
|
||||
<MudListItem>%F: Content path (same as root path for multifile torrent)</MudListItem>
|
||||
<MudListItem>%R: Root path (first torrent subdirectory path)</MudListItem>
|
||||
<MudListItem>%D: Save path</MudListItem>
|
||||
<MudListItem>%C: Number of files</MudListItem>
|
||||
<MudListItem>%Z: Torrent size (bytes)</MudListItem>
|
||||
<MudListItem>%T: Current tracker</MudListItem>
|
||||
<MudListItem>%I: Info hash v1</MudListItem>
|
||||
<MudListItem>%J: Info hash v2</MudListItem>
|
||||
<MudListItem>%K: Torrent ID</MudListItem>
|
||||
</MudList>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
399
Lantean.QBTMudBlade/Components/Options/DownloadsOptions.razor.cs
Normal file
399
Lantean.QBTMudBlade/Components/Options/DownloadsOptions.razor.cs
Normal file
@@ -0,0 +1,399 @@
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components.Options
|
||||
{
|
||||
public partial class DownloadsOptions : Options
|
||||
{
|
||||
protected string? TorrentContentLayout { get; set; }
|
||||
protected bool AddToTopOfQueue { get; set; }
|
||||
protected bool StartPausedEnabled { get; set; }
|
||||
protected string? TorrentStopCondition { get; set; }
|
||||
protected bool AutoDeleteMode { get; set; }
|
||||
protected bool PreallocateAll { get; set; }
|
||||
protected bool IncompleteFilesExt { get; set; }
|
||||
protected bool AutoTmmEnabled { get; set; }
|
||||
protected bool TorrentChangedTmmEnabled { get; set; }
|
||||
protected bool SavePathChangedTmmEnabled { get; set; }
|
||||
protected bool CategoryChangedTmmEnabled { get; set; }
|
||||
protected bool UseSubcategories { get; set; }
|
||||
protected string? SavePath { get; set; }
|
||||
protected bool TempPathEnabled { get; set; }
|
||||
protected string? TempPath { get; set; }
|
||||
protected bool ExportDirEnabled { get; set; }
|
||||
protected string? ExportDir { get; set; }
|
||||
protected bool ExportDirFinEnabled { get; set; }
|
||||
protected string? ExportDirFin { get; set; }
|
||||
protected Dictionary<string, SaveLocation> ScanDirs { get; set; } = [];
|
||||
protected bool ExcludedFileNamesEnabled { get; set; }
|
||||
protected string? ExcludedFileNames { get; set; }
|
||||
protected bool MailNotificationEnabled { get; set; }
|
||||
protected string? MailNotificationSender { get; set; }
|
||||
protected string? MailNotificationEmail { get; set; }
|
||||
protected string? MailNotificationSmtp { get; set; }
|
||||
protected bool MailNotificationSslEnabled { get; set; }
|
||||
protected bool MailNotificationAuthEnabled { get; set; }
|
||||
protected string? MailNotificationUsername { get; set; }
|
||||
protected string? MailNotificationPassword { get; set; }
|
||||
protected bool AutorunOnTorrentAddedEnabled { get; set; }
|
||||
protected string? AutorunOnTorrentAddedProgram { get; set; }
|
||||
protected bool AutorunEnabled { get; set; }
|
||||
protected string? AutorunProgram { get; set; }
|
||||
|
||||
protected List<KeyValuePair<string, SaveLocation>> AddedScanDirs { get; set; } = [];
|
||||
|
||||
protected override bool SetOptions()
|
||||
{
|
||||
if (Preferences is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// when adding a torrent
|
||||
TorrentContentLayout = Preferences.TorrentContentLayout;
|
||||
AddToTopOfQueue = Preferences.AddToTopOfQueue;
|
||||
StartPausedEnabled = Preferences.StartPausedEnabled;
|
||||
TorrentStopCondition = Preferences.TorrentStopCondition;
|
||||
AutoDeleteMode = Preferences.AutoDeleteMode == 1;
|
||||
PreallocateAll = Preferences.PreallocateAll;
|
||||
IncompleteFilesExt = Preferences.IncompleteFilesExt;
|
||||
|
||||
// saving management
|
||||
AutoTmmEnabled = Preferences.AutoTmmEnabled;
|
||||
TorrentChangedTmmEnabled = Preferences.TorrentChangedTmmEnabled;
|
||||
SavePathChangedTmmEnabled = Preferences.SavePathChangedTmmEnabled;
|
||||
CategoryChangedTmmEnabled = Preferences.CategoryChangedTmmEnabled;
|
||||
UseSubcategories = Preferences.UseSubcategories;
|
||||
SavePath = Preferences.SavePath;
|
||||
TempPathEnabled = Preferences.TempPathEnabled;
|
||||
TempPath = Preferences.TempPath;
|
||||
ExportDir = Preferences.ExportDir;
|
||||
ExportDirEnabled = !string.IsNullOrEmpty(Preferences.ExportDir);
|
||||
ExportDirFin = Preferences.ExportDirFin;
|
||||
ExportDirFinEnabled = !string.IsNullOrEmpty(Preferences.ExportDirFin);
|
||||
|
||||
ScanDirs.Clear();
|
||||
foreach (var dir in Preferences.ScanDirs)
|
||||
{
|
||||
ScanDirs.Add(dir.Key, dir.Value);
|
||||
}
|
||||
|
||||
ExcludedFileNamesEnabled = Preferences.ExcludedFileNamesEnabled;
|
||||
ExcludedFileNames = Preferences.ExcludedFileNames;
|
||||
|
||||
// email notification
|
||||
MailNotificationEnabled = Preferences.MailNotificationEnabled;
|
||||
MailNotificationSender = Preferences.MailNotificationSender;
|
||||
MailNotificationEmail = Preferences.MailNotificationEmail;
|
||||
MailNotificationSmtp = Preferences.MailNotificationSmtp;
|
||||
MailNotificationSslEnabled = Preferences.MailNotificationSslEnabled;
|
||||
MailNotificationAuthEnabled = Preferences.MailNotificationAuthEnabled;
|
||||
MailNotificationUsername = Preferences.MailNotificationUsername;
|
||||
MailNotificationPassword = Preferences.MailNotificationPassword;
|
||||
|
||||
// autorun
|
||||
AutorunOnTorrentAddedEnabled = Preferences.AutorunOnTorrentAddedEnabled;
|
||||
AutorunOnTorrentAddedProgram = Preferences.AutorunOnTorrentAddedProgram;
|
||||
AutorunEnabled = Preferences.AutorunEnabled;
|
||||
AutorunProgram = Preferences.AutorunProgram;
|
||||
|
||||
AddedScanDirs.Clear();
|
||||
AddDefaultScanDir();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async Task TorrentContentLayoutChanged(string value)
|
||||
{
|
||||
TorrentContentLayout = value;
|
||||
UpdatePreferences.TorrentContentLayout = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AddToTopOfQueueChanged(bool value)
|
||||
{
|
||||
AddToTopOfQueue = value;
|
||||
UpdatePreferences.AddToTopOfQueue = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task StartPausedEnabledChanged(bool value)
|
||||
{
|
||||
StartPausedEnabled = value;
|
||||
UpdatePreferences.StartPausedEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task TorrentStopConditionChanged(string value)
|
||||
{
|
||||
TorrentStopCondition = value;
|
||||
UpdatePreferences.TorrentStopCondition = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AutoDeleteModeChanged(bool value)
|
||||
{
|
||||
AutoDeleteMode = value;
|
||||
UpdatePreferences.AutoDeleteMode = value ? 1 : 0;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task PreallocateAllChanged(bool value)
|
||||
{
|
||||
PreallocateAll = value;
|
||||
UpdatePreferences.PreallocateAll = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task IncompleteFilesExtChanged(bool value)
|
||||
{
|
||||
IncompleteFilesExt = value;
|
||||
UpdatePreferences.IncompleteFilesExt = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AutoTmmEnabledChanged(bool value)
|
||||
{
|
||||
AutoTmmEnabled = value;
|
||||
UpdatePreferences.AutoTmmEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task TorrentChangedTmmEnabledChanged(bool value)
|
||||
{
|
||||
TorrentChangedTmmEnabled = value;
|
||||
UpdatePreferences.TorrentChangedTmmEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task SavePathChangedTmmEnabledChanged(bool value)
|
||||
{
|
||||
SavePathChangedTmmEnabled = value;
|
||||
UpdatePreferences.SavePathChangedTmmEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task CategoryChangedTmmEnabledChanged(bool value)
|
||||
{
|
||||
CategoryChangedTmmEnabled = value;
|
||||
UpdatePreferences.CategoryChangedTmmEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task UseSubcategoriesChanged(bool value)
|
||||
{
|
||||
UseSubcategories = value;
|
||||
UpdatePreferences.UseSubcategories = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task SavePathChanged(string value)
|
||||
{
|
||||
SavePath = value;
|
||||
UpdatePreferences.SavePath = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task TempPathEnabledChanged(bool value)
|
||||
{
|
||||
TempPathEnabled = value;
|
||||
UpdatePreferences.TempPathEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task TempPathChanged(string value)
|
||||
{
|
||||
TempPath = value;
|
||||
UpdatePreferences.TempPath = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected void ExportDirEnabledChanged(bool value)
|
||||
{
|
||||
ExportDirEnabled = value;
|
||||
}
|
||||
|
||||
protected async Task ExportDirChanged(string value)
|
||||
{
|
||||
ExportDir = value;
|
||||
UpdatePreferences.ExportDir = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected void ExportDirFinEnabledChanged(bool value)
|
||||
{
|
||||
ExportDirFinEnabled = value;
|
||||
}
|
||||
|
||||
protected async Task ExportDirFinChanged(string value)
|
||||
{
|
||||
ExportDirFin = value;
|
||||
UpdatePreferences.ExportDirFin = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ScanDirsKeyChanged(string key, string value)
|
||||
{
|
||||
if (ScanDirs.Remove(key, out var location))
|
||||
{
|
||||
ScanDirs[value] = location;
|
||||
}
|
||||
UpdatePreferences.ScanDirs = ScanDirs;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ScanDirsValueChanged(string key, string value)
|
||||
{
|
||||
ScanDirs[key] = SaveLocation.Create(value);
|
||||
UpdatePreferences.ScanDirs = ScanDirs;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AddedScanDirsKeyChanged(int index, string key)
|
||||
{
|
||||
if (key == "")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ScanDirs.Add(key, AddedScanDirs[index].Value);
|
||||
AddedScanDirs.RemoveAt(index);
|
||||
UpdatePreferences.ScanDirs = ScanDirs;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
|
||||
if (AddedScanDirs.Count == 0)
|
||||
{
|
||||
AddDefaultScanDir();
|
||||
}
|
||||
}
|
||||
|
||||
protected void AddedScanDirsValueChanged(int index, string value)
|
||||
{
|
||||
var existing = AddedScanDirs[index];
|
||||
AddedScanDirs[index] = new KeyValuePair<string, SaveLocation>(existing.Key, SaveLocation.Create(value));
|
||||
}
|
||||
|
||||
protected void AddNewScanDir()
|
||||
{
|
||||
AddDefaultScanDir();
|
||||
}
|
||||
|
||||
protected async Task ExcludedFileNamesEnabledChanged(bool value)
|
||||
{
|
||||
ExcludedFileNamesEnabled = value;
|
||||
UpdatePreferences.ExcludedFileNamesEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ExcludedFileNamesChanged(string value)
|
||||
{
|
||||
ExcludedFileNames = value;
|
||||
UpdatePreferences.ExcludedFileNames = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MailNotificationEnabledChanged(bool value)
|
||||
{
|
||||
MailNotificationEnabled = value;
|
||||
UpdatePreferences.MailNotificationEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MailNotificationSenderChanged(string value)
|
||||
{
|
||||
MailNotificationSender = value;
|
||||
UpdatePreferences.MailNotificationSender = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MailNotificationEmailChanged(string value)
|
||||
{
|
||||
MailNotificationEmail = value;
|
||||
UpdatePreferences.MailNotificationEmail = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MailNotificationSmtpChanged(string value)
|
||||
{
|
||||
MailNotificationSmtp = value;
|
||||
UpdatePreferences.MailNotificationSmtp = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MailNotificationSslEnabledChanged(bool value)
|
||||
{
|
||||
MailNotificationSslEnabled = value;
|
||||
UpdatePreferences.MailNotificationSslEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MailNotificationAuthEnabledChanged(bool value)
|
||||
{
|
||||
MailNotificationAuthEnabled = value;
|
||||
UpdatePreferences.MailNotificationAuthEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MailNotificationUsernameChanged(string value)
|
||||
{
|
||||
MailNotificationUsername = value;
|
||||
UpdatePreferences.MailNotificationUsername = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task MailNotificationPasswordChanged(string value)
|
||||
{
|
||||
MailNotificationPassword = value;
|
||||
UpdatePreferences.MailNotificationPassword = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AutorunOnTorrentAddedEnabledChanged(bool value)
|
||||
{
|
||||
AutorunOnTorrentAddedEnabled = value;
|
||||
UpdatePreferences.AutorunOnTorrentAddedEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AutorunOnTorrentAddedProgramChanged(string value)
|
||||
{
|
||||
AutorunOnTorrentAddedProgram = value;
|
||||
UpdatePreferences.AutorunOnTorrentAddedProgram = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AutorunEnabledChanged(bool value)
|
||||
{
|
||||
AutorunEnabled = value;
|
||||
UpdatePreferences.AutorunEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AutorunProgramChanged(string value)
|
||||
{
|
||||
AutorunProgram = value;
|
||||
UpdatePreferences.AutorunProgram = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
private void AddDefaultScanDir()
|
||||
{
|
||||
AddedScanDirs.Add(new KeyValuePair<string, SaveLocation>("", SaveLocation.Create(1)));
|
||||
}
|
||||
|
||||
protected Func<string, string?> IsValidNewKey => IsValidNewKeyFunc;
|
||||
|
||||
private string? IsValidNewKeyFunc(string? key)
|
||||
{
|
||||
if (key is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (ScanDirs.ContainsKey(key))
|
||||
{
|
||||
return "A folder with this path already exists";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
38
Lantean.QBTMudBlade/Components/Options/Options.cs
Normal file
38
Lantean.QBTMudBlade/Components/Options/Options.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components.Options
|
||||
{
|
||||
public abstract class Options : ComponentBase
|
||||
{
|
||||
private bool _preferencesRead;
|
||||
protected UpdatePreferences UpdatePreferences { get; set; } = new UpdatePreferences();
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public Preferences? Preferences { get; set; }
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public EventCallback<UpdatePreferences> PreferencesChanged { get; set; }
|
||||
|
||||
public async Task ResetAsync()
|
||||
{
|
||||
SetOptions();
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (_preferencesRead)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_preferencesRead = SetOptions();
|
||||
}
|
||||
|
||||
protected abstract bool SetOptions();
|
||||
}
|
||||
}
|
14
Lantean.QBTMudBlade/Components/Options/RSSOptions.razor
Normal file
14
Lantean.QBTMudBlade/Components/Options/RSSOptions.razor
Normal file
@@ -0,0 +1,14 @@
|
||||
@inherits Options
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6"></MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
10
Lantean.QBTMudBlade/Components/Options/RSSOptions.razor.cs
Normal file
10
Lantean.QBTMudBlade/Components/Options/RSSOptions.razor.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Lantean.QBTMudBlade.Components.Options
|
||||
{
|
||||
public partial class RSSOptions : Options
|
||||
{
|
||||
protected override bool SetOptions()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
14
Lantean.QBTMudBlade/Components/Options/SpeedOptions.razor
Normal file
14
Lantean.QBTMudBlade/Components/Options/SpeedOptions.razor
Normal file
@@ -0,0 +1,14 @@
|
||||
@inherits Options
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6"></MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
10
Lantean.QBTMudBlade/Components/Options/SpeedOptions.razor.cs
Normal file
10
Lantean.QBTMudBlade/Components/Options/SpeedOptions.razor.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Lantean.QBTMudBlade.Components.Options
|
||||
{
|
||||
public partial class SpeedOptions : Options
|
||||
{
|
||||
protected override bool SetOptions()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
14
Lantean.QBTMudBlade/Components/Options/WebUIOptions.razor
Normal file
14
Lantean.QBTMudBlade/Components/Options/WebUIOptions.razor
Normal file
@@ -0,0 +1,14 @@
|
||||
@inherits Options
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h6"></MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
10
Lantean.QBTMudBlade/Components/Options/WebUIOptions.razor.cs
Normal file
10
Lantean.QBTMudBlade/Components/Options/WebUIOptions.razor.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Lantean.QBTMudBlade.Components.Options
|
||||
{
|
||||
public partial class WebUIOptions : Options
|
||||
{
|
||||
protected override bool SetOptions()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
32
Lantean.QBTMudBlade/Components/PeersTab.razor
Normal file
32
Lantean.QBTMudBlade/Components/PeersTab.razor
Normal file
@@ -0,0 +1,32 @@
|
||||
<MudTable T="Peer" Items="Peers" >
|
||||
<HeaderContent>
|
||||
<MudTh>Country/Region</MudTh>
|
||||
<MudTh>IP</MudTh>
|
||||
<MudTh>Port</MudTh>
|
||||
<MudTh>Connection</MudTh>
|
||||
<MudTh>Flags</MudTh>
|
||||
<MudTh>Client</MudTh>
|
||||
<MudTh>Progress</MudTh>
|
||||
<MudTh>Download Speed</MudTh>
|
||||
<MudTh>Upload Speed</MudTh>
|
||||
<MudTh>Downloaded</MudTh>
|
||||
<MudTh>Uploaded</MudTh>
|
||||
<MudTh>Relevance</MudTh>
|
||||
<MudTh>Files</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Country/Region"><MudImage ObjectFit="ObjectFit.Fill" Src="@($"https://flagcdn.com/20x15/{context.CountryCode}.png")"></MudImage></MudTd>
|
||||
<MudTd DataLabel="IP">@context.IPAddress</MudTd>
|
||||
<MudTd DataLabel="Port">@context.Port</MudTd>
|
||||
<MudTd DataLabel="Connection">@context.Connection</MudTd>
|
||||
<MudTd DataLabel="Flags">@context.Flags</MudTd>
|
||||
<MudTd DataLabel="Client">@context.Client</MudTd>
|
||||
<MudTd DataLabel="Progress">@DisplayHelpers.Percentage(context.Progress)</MudTd>
|
||||
<MudTd DataLabel="Download Speed">@DisplayHelpers.Speed(context.DownloadSpeed)</MudTd>
|
||||
<MudTd DataLabel="Upload Speed">@DisplayHelpers.Speed(context.UploadSpeed)</MudTd>
|
||||
<MudTd DataLabel="Downloaded">@DisplayHelpers.Size(context.Downloaded)</MudTd>
|
||||
<MudTd DataLabel="Uploaded">@DisplayHelpers.Size(context.Uploaded)</MudTd>
|
||||
<MudTd DataLabel="Relevance">@DisplayHelpers.Percentage(context.Relevance)</MudTd>
|
||||
<MudTd DataLabel="Files">@context.Files</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
119
Lantean.QBTMudBlade/Components/PeersTab.razor.cs
Normal file
119
Lantean.QBTMudBlade/Components/PeersTab.razor.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Lantean.QBTMudBlade.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using System.Net;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
public partial class PeersTab : IAsyncDisposable
|
||||
{
|
||||
private bool _disposedValue;
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public string? Hash { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Active { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public int RefreshInterval { get; set; }
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDataManager DataManager { get; set; } = default!;
|
||||
|
||||
protected PeerList? PeerList { get; set; }
|
||||
|
||||
protected IEnumerable<Peer> Peers => PeerList?.Peers.Select(p => p.Value) ?? [];
|
||||
|
||||
private int _requestId = 0;
|
||||
private readonly CancellationTokenSource _timerCancellationToken = new();
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var torrentPeers = await ApiClient.GetTorrentPeersData(Hash, _requestId);
|
||||
PeerList = DataManager.CreatePeerList(torrentPeers);
|
||||
_requestId = torrentPeers.RequestId;
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
using (var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(RefreshInterval)))
|
||||
{
|
||||
while (!_timerCancellationToken.IsCancellationRequested && await timer.WaitForNextTickAsync())
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Active)
|
||||
{
|
||||
QBitTorrentClient.Models.TorrentPeers peers;
|
||||
try
|
||||
{
|
||||
peers = await ApiClient.GetTorrentPeersData(Hash, _requestId);
|
||||
}
|
||||
catch (HttpRequestException exception) when (exception.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
_timerCancellationToken.CancelIfNotDisposed();
|
||||
return;
|
||||
}
|
||||
if (PeerList is null || peers.FullUpdate)
|
||||
{
|
||||
PeerList = DataManager.CreatePeerList(peers);
|
||||
}
|
||||
else
|
||||
{
|
||||
DataManager.MergeTorrentPeers(peers, PeerList);
|
||||
}
|
||||
|
||||
_requestId = peers.RequestId;
|
||||
}
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual async Task DisposeAsync(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_timerCancellationToken.Cancel();
|
||||
_timerCancellationToken.Dispose();
|
||||
|
||||
await Task.Delay(0);
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
await DisposeAsync(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
88
Lantean.QBTMudBlade/Components/TorrentActions.razor
Normal file
88
Lantean.QBTMudBlade/Components/TorrentActions.razor
Normal file
@@ -0,0 +1,88 @@
|
||||
@if (Type == ParentType.StandaloneToolbar)
|
||||
{
|
||||
<MudToolBar Dense="true" DisableGutters="true">
|
||||
@ToolbarContent
|
||||
</MudToolBar>
|
||||
}
|
||||
else if (Type == ParentType.Toolbar)
|
||||
{
|
||||
@ToolbarContent
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudMenu AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft">
|
||||
@foreach (var option in GetOptions())
|
||||
{
|
||||
<MudMenuItem Icon="@option.Icon" IconColor="option.Color" OnClick="option.Callback" OnTouch="option.Callback">
|
||||
@if (option is Divider)
|
||||
{
|
||||
<MudDivider />
|
||||
}
|
||||
else if (!option.Children.Any())
|
||||
{
|
||||
@option.Name
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudMenu Dense="true" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft">
|
||||
<ActivatorContent>
|
||||
<MudMenuItem>@option.Name</MudMenuItem>
|
||||
</ActivatorContent>
|
||||
|
||||
<ChildContent>
|
||||
@foreach (var childItem in option.Children)
|
||||
{
|
||||
@ChildItem(childItem)
|
||||
}
|
||||
</ChildContent>
|
||||
</MudMenu>
|
||||
}
|
||||
</MudMenuItem>
|
||||
}
|
||||
</MudMenu>
|
||||
}
|
||||
|
||||
@code {
|
||||
private RenderFragment ToolbarContent =>
|
||||
@<NonRendering>
|
||||
@foreach (var option in GetOptions())
|
||||
{
|
||||
@if (option is Divider)
|
||||
{
|
||||
<MudDivider Vertical="true" />
|
||||
}
|
||||
else if (!option.Children.Any())
|
||||
{
|
||||
if (option.Icon is null)
|
||||
{
|
||||
<MudButton Color="option.Color" OnClick="option.Callback">@option.Name</MudButton>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudIconButton Title="@option.Name" Icon="@option.Icon" Color="option.Color" OnClick="option.Callback" />
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudMenu Icon="@option.Icon" IconColor="@option.Color" Label="@option.Name" AnchorOrigin="Origin.BottomLeft" TransformOrigin="Origin.TopLeft">
|
||||
@foreach (var childItem in option.Children)
|
||||
{
|
||||
@ChildItem(childItem)
|
||||
}
|
||||
</MudMenu>
|
||||
}
|
||||
}
|
||||
</NonRendering>;
|
||||
|
||||
private RenderFragment ChildItem(Action option) =>
|
||||
@<NonRendering>
|
||||
@if (option is Divider)
|
||||
{
|
||||
<MudDivider />
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudMenuItem Icon="@option.Icon" IconColor="option.Color" OnClick="option.Callback" OnTouch="option.Callback">@option.Name</MudMenuItem>
|
||||
}
|
||||
</NonRendering>;
|
||||
}
|
325
Lantean.QBTMudBlade/Components/TorrentActions.razor.cs
Normal file
325
Lantean.QBTMudBlade/Components/TorrentActions.razor.cs
Normal file
@@ -0,0 +1,325 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Lantean.QBTMudBlade.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
public partial class TorrentActions
|
||||
{
|
||||
[Inject]
|
||||
public IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
public NavigationManager NavigationManager { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
public IDialogService DialogService { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
public ISnackbar Snackbar { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
public IDataManager DataManager { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
public IClipboardService ClipboardService { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public IEnumerable<string> Hashes { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// If true this component will render as a <see cref="MudToolBar"/> otherwise will render as a <see cref="MudMenu"/>.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public ParentType Type { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public MainData MainData { get; set; } = default!;
|
||||
|
||||
protected async Task Pause()
|
||||
{
|
||||
await ApiClient.PauseTorrents(Hashes);
|
||||
Snackbar.Add("Torrent paused.");
|
||||
}
|
||||
|
||||
protected async Task Resume()
|
||||
{
|
||||
await ApiClient.ResumeTorrents(Hashes);
|
||||
Snackbar.Add("Torrent resumed.");
|
||||
}
|
||||
|
||||
protected async Task Remove()
|
||||
{
|
||||
await DialogService.InvokeDeleteTorrentDialog(ApiClient, Hashes.ToArray());
|
||||
}
|
||||
|
||||
protected async Task SetLocation()
|
||||
{
|
||||
string? savePath = null;
|
||||
if (Hashes.Any() && MainData.Torrents.TryGetValue(Hashes.First(), out var torrent))
|
||||
{
|
||||
savePath = torrent.SavePath;
|
||||
}
|
||||
await DialogService.ShowSingleFieldDialog("Set Location", "Location", savePath, v => ApiClient.SetTorrentLocation(v, null, Hashes.ToArray()));
|
||||
}
|
||||
|
||||
protected async Task Rename()
|
||||
{
|
||||
string? name = null;
|
||||
string hash = Hashes.First();
|
||||
if (Hashes.Any() && MainData.Torrents.TryGetValue(hash, out var torrent))
|
||||
{
|
||||
name = torrent.Name;
|
||||
}
|
||||
await DialogService.ShowSingleFieldDialog("Rename", "Location", name, v => ApiClient.SetTorrentName(v, hash));
|
||||
}
|
||||
|
||||
protected async Task RenameFiles()
|
||||
{
|
||||
await DialogService.InvokeRenameFilesDialog(ApiClient, Hashes.First());
|
||||
}
|
||||
|
||||
protected async Task SetCategory(string category)
|
||||
{
|
||||
await ApiClient.SetTorrentCategory(category, null, Hashes.ToArray());
|
||||
}
|
||||
|
||||
protected async Task AddCategory()
|
||||
{
|
||||
await DialogService.InvokeAddCategoryDialog(ApiClient, Hashes);
|
||||
}
|
||||
|
||||
protected async Task ResetCategory()
|
||||
{
|
||||
await ApiClient.SetTorrentCategory("", null, Hashes.ToArray());
|
||||
}
|
||||
|
||||
protected async Task AddTag()
|
||||
{
|
||||
await DialogService.ShowSingleFieldDialog("Add Tags", "Comma-separated tags", "", v => ApiClient.AddTorrentTags(v.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries), null, Hashes.ToArray()));
|
||||
}
|
||||
|
||||
protected async Task RemoveTags()
|
||||
{
|
||||
var torrents = GetTorrents();
|
||||
|
||||
foreach (var torrent in torrents)
|
||||
{
|
||||
await ApiClient.RemoveTorrentTags(torrent.Tags, null, torrent.Hash);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task ToggleTag(string tag)
|
||||
{
|
||||
var torrents = GetTorrents();
|
||||
|
||||
await ApiClient.RemoveTorrentTag(tag, torrents.Where(t => t.Tags.Contains(tag)).Select(t => t.Hash));
|
||||
await ApiClient.AddTorrentTag(tag, torrents.Where(t => !t.Tags.Contains(tag)).Select(t => t.Hash));
|
||||
}
|
||||
|
||||
protected async Task ToggleAutoTMM()
|
||||
{
|
||||
var torrents = GetTorrents();
|
||||
|
||||
await ApiClient.SetAutomaticTorrentManagement(false, null, torrents.Where(t => t.AutomaticTorrentManagement).Select(t => t.Hash).ToArray());
|
||||
await ApiClient.SetAutomaticTorrentManagement(true, null, torrents.Where(t => !t.AutomaticTorrentManagement).Select(t => t.Hash).ToArray());
|
||||
}
|
||||
|
||||
protected async Task LimitUploadRate()
|
||||
{
|
||||
long uploadLimit = -1;
|
||||
string hash = Hashes.First();
|
||||
if (Hashes.Any() && MainData.Torrents.TryGetValue(hash, out var torrent))
|
||||
{
|
||||
uploadLimit = torrent.UploadLimit;
|
||||
}
|
||||
|
||||
await DialogService.InvokeUploadRateDialog(ApiClient, uploadLimit, Hashes);
|
||||
}
|
||||
|
||||
protected async Task LimitShareRatio()
|
||||
{
|
||||
float ratioLimit = -1;
|
||||
string hash = Hashes.First();
|
||||
if (Hashes.Any() && MainData.Torrents.TryGetValue(hash, out var torrent))
|
||||
{
|
||||
ratioLimit = torrent.RatioLimit;
|
||||
}
|
||||
|
||||
await DialogService.InvokeShareRatioDialog(ApiClient, ratioLimit, Hashes);
|
||||
}
|
||||
|
||||
protected async Task ToggleSuperSeeding()
|
||||
{
|
||||
var torrents = GetTorrents();
|
||||
|
||||
await ApiClient.SetSuperSeeding(false, null, torrents.Where(t => t.SuperSeeding).Select(t => t.Hash).ToArray());
|
||||
await ApiClient.SetSuperSeeding(true, null, torrents.Where(t => !t.SuperSeeding).Select(t => t.Hash).ToArray());
|
||||
}
|
||||
|
||||
protected async Task ForceRecheck()
|
||||
{
|
||||
await ApiClient.RecheckTorrents(null, Hashes.ToArray());
|
||||
}
|
||||
|
||||
protected async Task ForceReannounce()
|
||||
{
|
||||
await ApiClient.ReannounceTorrents(null, Hashes.ToArray());
|
||||
}
|
||||
|
||||
protected async Task MoveToTop()
|
||||
{
|
||||
await ApiClient.MaximalTorrentPriority(null, Hashes.ToArray());
|
||||
}
|
||||
|
||||
protected async Task MoveUp()
|
||||
{
|
||||
await ApiClient.IncreaseTorrentPriority(null, Hashes.ToArray());
|
||||
}
|
||||
|
||||
protected async Task MoveDown()
|
||||
{
|
||||
await ApiClient.DecreaseTorrentPriority(null, Hashes.ToArray());
|
||||
}
|
||||
|
||||
protected async Task MoveToBottom()
|
||||
{
|
||||
await ApiClient.MinimalTorrentPriority(null, Hashes.ToArray());
|
||||
}
|
||||
|
||||
protected async Task Copy(string value)
|
||||
{
|
||||
await ClipboardService.WriteToClipboard(value);
|
||||
}
|
||||
|
||||
protected async Task Export()
|
||||
{
|
||||
await Task.Delay(5);
|
||||
}
|
||||
|
||||
private IEnumerable<Torrent> GetTorrents()
|
||||
{
|
||||
foreach (var hash in Hashes)
|
||||
{
|
||||
if (MainData.Torrents.TryGetValue(hash, out var torrent))
|
||||
{
|
||||
yield return torrent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Action> GetOptions()
|
||||
{
|
||||
if (!Hashes.Any())
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var firstTorrent = MainData.Torrents[Hashes.First()];
|
||||
|
||||
var categories = new List<Action>
|
||||
{
|
||||
new Action("New", Icons.Material.Filled.Add, Color.Info, EventCallback.Factory.Create(this, AddCategory)),
|
||||
new Action("Reset", Icons.Material.Filled.Remove, Color.Error, EventCallback.Factory.Create(this, ResetCategory)),
|
||||
new Divider()
|
||||
};
|
||||
categories.AddRange(MainData.Categories.Select(c => new Action(c.Value.Name, Icons.Material.Filled.List, Color.Info, EventCallback.Factory.Create(this, () => SetCategory(c.Key)))));
|
||||
|
||||
var tags = new List<Action>
|
||||
{
|
||||
new Action("Add", Icons.Material.Filled.Add, Color.Info, EventCallback.Factory.Create(this, AddTag)),
|
||||
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)))));
|
||||
|
||||
var options = new List<Action>
|
||||
{
|
||||
new Action("Pause", Icons.Material.Filled.Pause, Color.Warning, EventCallback.Factory.Create(this, Pause)),
|
||||
new Action("Resume", Icons.Material.Filled.PlayArrow, Color.Success, EventCallback.Factory.Create(this, Resume)),
|
||||
new Divider(),
|
||||
new Action("Remove", Icons.Material.Filled.Delete, Color.Error, EventCallback.Factory.Create(this, Remove)),
|
||||
new Divider(),
|
||||
new Action("Set location", Icons.Material.Filled.MyLocation, Color.Info, EventCallback.Factory.Create(this, SetLocation)),
|
||||
new Action("Rename", Icons.Material.Filled.DriveFileRenameOutline, Color.Info, EventCallback.Factory.Create(this, Rename)),
|
||||
new Action("Category", Icons.Material.Filled.List, Color.Info, categories),
|
||||
new Action("Tags", Icons.Material.Filled.Label, Color.Info, tags),
|
||||
new Action("Automatic Torrent Management", Icons.Material.Filled.Check, firstTorrent.AutomaticTorrentManagement ? 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 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)),
|
||||
new Divider(),
|
||||
new Action("Queue", Icons.Material.Filled.Queue, Color.Transparent, new List<Action>
|
||||
{
|
||||
new Action("Move to top", Icons.Material.Filled.VerticalAlignTop, Color.Inherit, EventCallback.Factory.Create(this, MoveToTop)),
|
||||
new Action("Move up", Icons.Material.Filled.ArrowUpward, Color.Inherit, EventCallback.Factory.Create(this, MoveUp)),
|
||||
new Action("Move down", Icons.Material.Filled.ArrowDownward, Color.Inherit, EventCallback.Factory.Create(this, MoveDown)),
|
||||
new Action("Move to bottom", Icons.Material.Filled.VerticalAlignBottom, Color.Inherit, EventCallback.Factory.Create(this, MoveToBottom)),
|
||||
}),
|
||||
new Action("Copy", Icons.Material.Filled.FolderCopy, Color.Info, new List<Action>
|
||||
{
|
||||
new Action("Name", Icons.Material.Filled.TextFields, Color.Info, EventCallback.Factory.Create(this, () => Copy(firstTorrent.Name))),
|
||||
new Action("Info hash v1", Icons.Material.Filled.Tag, Color.Info, EventCallback.Factory.Create(this, () => Copy(firstTorrent.InfoHashV1))),
|
||||
new Action("Info hash v2", Icons.Material.Filled.Tag, Color.Info, EventCallback.Factory.Create(this, () => Copy(firstTorrent.InfoHashV2))),
|
||||
new Action("Magnet link", Icons.Material.Filled.TextFields, Color.Info, EventCallback.Factory.Create(this, () => Copy(firstTorrent.MagnetUri))),
|
||||
new Action("Torrent ID", Icons.Material.Filled.TextFields, Color.Info, EventCallback.Factory.Create(this, () => Copy(firstTorrent.Hash))),
|
||||
}),
|
||||
new Action("Export", Icons.Material.Filled.SaveAlt, Color.Info, EventCallback.Factory.Create(this, Export)),
|
||||
};
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
public enum ParentType
|
||||
{
|
||||
Toolbar,
|
||||
StandaloneToolbar,
|
||||
Menu,
|
||||
}
|
||||
|
||||
public class Divider : Action
|
||||
{
|
||||
public Divider() : base("-", default!, Color.Default, default(EventCallback))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class Action
|
||||
{
|
||||
public Action(string name, string icon, Color color, EventCallback callback)
|
||||
{
|
||||
Name = name;
|
||||
Icon = icon;
|
||||
Color = color;
|
||||
Callback = callback;
|
||||
Children = [];
|
||||
}
|
||||
|
||||
public Action(string name, string icon, Color color, IEnumerable<Action> children)
|
||||
{
|
||||
Name = name;
|
||||
Icon = icon;
|
||||
Color = color;
|
||||
Callback = default;
|
||||
Children = children;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string Icon { get; }
|
||||
|
||||
public Color Color { get; }
|
||||
|
||||
public EventCallback Callback { get; }
|
||||
|
||||
public IEnumerable<Action> Children { get; }
|
||||
}
|
||||
}
|
18
Lantean.QBTMudBlade/Components/TorrentsListNav.razor
Normal file
18
Lantean.QBTMudBlade/Components/TorrentsListNav.razor
Normal file
@@ -0,0 +1,18 @@
|
||||
<MudNavMenu>
|
||||
<MudNavLink Icon="@(Icons.Material.Outlined.NavigateBefore)" OnClick="NavigateBack">Back</MudNavLink>
|
||||
<MudDivider />
|
||||
@if (Torrents is null)
|
||||
{
|
||||
@for (var i = 0; i < 10; i++)
|
||||
{
|
||||
<MudSkeleton Animation="Animation.Pulse" Width="100%" Height="25px" />
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var torrent in Torrents)
|
||||
{
|
||||
<MudNavLink Href="@("/details/" + torrent.Hash)">@torrent.Name</MudNavLink>
|
||||
}
|
||||
}
|
||||
</MudNavMenu>
|
22
Lantean.QBTMudBlade/Components/TorrentsListNav.razor.cs
Normal file
22
Lantean.QBTMudBlade/Components/TorrentsListNav.razor.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
public partial class TorrentsListNav
|
||||
{
|
||||
[Inject]
|
||||
protected NavigationManager NavigationManager { get; set; } = default!;
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<Torrent>? Torrents { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? SelectedTorrent { get; set; }
|
||||
|
||||
protected void NavigateBack()
|
||||
{
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
}
|
||||
}
|
27
Lantean.QBTMudBlade/Components/TrackersTab.razor
Normal file
27
Lantean.QBTMudBlade/Components/TrackersTab.razor
Normal file
@@ -0,0 +1,27 @@
|
||||
<MudTable T="Lantean.QBitTorrentClient.Models.TorrentTrackers" Items="Trackers" >
|
||||
<HeaderContent>
|
||||
<MudTh>Tier</MudTh>
|
||||
<MudTh>URL</MudTh>
|
||||
<MudTh>Status</MudTh>
|
||||
<MudTh>Peers</MudTh>
|
||||
<MudTh>Seeds</MudTh>
|
||||
<MudTh>Leeches</MudTh>
|
||||
<MudTh>Times Downloaded</MudTh>
|
||||
<MudTh>Message</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Tier">
|
||||
@if (context.Tier >= 0)
|
||||
{
|
||||
<text>@context.Tier</text>
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd DataLabel="URL">@context.Url</MudTd>
|
||||
<MudTd DataLabel="Status">@context.Status</MudTd>
|
||||
<MudTd DataLabel="Peers">@context.Peers</MudTd>
|
||||
<MudTd DataLabel="Seeds">@context.Seeds</MudTd>
|
||||
<MudTd DataLabel="Leeches">@context.Leeches</MudTd>
|
||||
<MudTd DataLabel="Times Downloaded">@context.Downloads</MudTd>
|
||||
<MudTd DataLabel="Message">@context.Message</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
97
Lantean.QBTMudBlade/Components/TrackersTab.razor.cs
Normal file
97
Lantean.QBTMudBlade/Components/TrackersTab.razor.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using Lantean.QBTMudBlade.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using System.Net;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
public partial class TrackersTab : IAsyncDisposable
|
||||
{
|
||||
private readonly CancellationTokenSource _timerCancellationToken = new();
|
||||
private bool _disposedValue;
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public string? Hash { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Active { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public int RefreshInterval { get; set; }
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDataManager DataManager { get; set; } = default!;
|
||||
|
||||
protected IReadOnlyList<TorrentTrackers>? Trackers { get; set; }
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Trackers = await ApiClient.GetTorrentTrackers(Hash);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
using (var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(RefreshInterval)))
|
||||
{
|
||||
while (!_timerCancellationToken.IsCancellationRequested && await timer.WaitForNextTickAsync())
|
||||
{
|
||||
if (Active && Hash is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Trackers = await ApiClient.GetTorrentTrackers(Hash);
|
||||
}
|
||||
catch (HttpRequestException exception) when (exception.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
_timerCancellationToken.CancelIfNotDisposed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual async Task DisposeAsync(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_timerCancellationToken.Cancel();
|
||||
_timerCancellationToken.Dispose();
|
||||
await Task.Delay(0);
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
await DisposeAsync(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
8
Lantean.QBTMudBlade/Components/WebSeedsTab.razor
Normal file
8
Lantean.QBTMudBlade/Components/WebSeedsTab.razor
Normal file
@@ -0,0 +1,8 @@
|
||||
<MudTable T="Lantean.QBitTorrentClient.Models.WebSeed" Items="WebSeeds" >
|
||||
<HeaderContent>
|
||||
<MudTh>URL</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Files">@context.Url</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
92
Lantean.QBTMudBlade/Components/WebSeedsTab.razor.cs
Normal file
92
Lantean.QBTMudBlade/Components/WebSeedsTab.razor.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using Lantean.QBTMudBlade.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using System.Net;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Components
|
||||
{
|
||||
public partial class WebSeedsTab : IAsyncDisposable
|
||||
{
|
||||
private readonly CancellationTokenSource _timerCancellationToken = new();
|
||||
private bool _disposedValue;
|
||||
|
||||
[Parameter]
|
||||
public bool Active { get; set; }
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public string? Hash { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public int RefreshInterval { get; set; }
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDataManager DataManager { get; set; } = default!;
|
||||
|
||||
protected IReadOnlyList<WebSeed>? WebSeeds { get; set; }
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
await DisposeAsync(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual async Task DisposeAsync(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_timerCancellationToken.Cancel();
|
||||
_timerCancellationToken.Dispose();
|
||||
await Task.Delay(0);
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
using (var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(RefreshInterval)))
|
||||
{
|
||||
while (!_timerCancellationToken.IsCancellationRequested && await timer.WaitForNextTickAsync())
|
||||
{
|
||||
if (Active && Hash is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
WebSeeds = await ApiClient.GetTorrentWebSeeds(Hash);
|
||||
}
|
||||
catch (HttpRequestException exception) when (exception.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
_timerCancellationToken.CancelIfNotDisposed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (Hash is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WebSeeds = await ApiClient.GetTorrentWebSeeds(Hash);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
14
Lantean.QBTMudBlade/CookieHandler.cs
Normal file
14
Lantean.QBTMudBlade/CookieHandler.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Http;
|
||||
|
||||
namespace Lantean.QBTMudBlade
|
||||
{
|
||||
public class CookieHandler : DelegatingHandler
|
||||
{
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
|
||||
|
||||
return await base.SendAsync(request, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
7
Lantean.QBTMudBlade/CustomIcons.cs
Normal file
7
Lantean.QBTMudBlade/CustomIcons.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Lantean.QBTMudBlade
|
||||
{
|
||||
public static class CustomIcons
|
||||
{
|
||||
public const string Magnet = @"<path fill=""currentColor"" d=""M3 7v6a9 9 0 0 0 9 9a9 9 0 0 0 9-9V7h-4v6a5 5 0 0 1-5 5a5 5 0 0 1-5-5V7m10-2h4V2h-4M3 5h4V2H3""/>";
|
||||
}
|
||||
}
|
229
Lantean.QBTMudBlade/DialogHelper.cs
Normal file
229
Lantean.QBTMudBlade/DialogHelper.cs
Normal file
@@ -0,0 +1,229 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMudBlade.Components.Dialogs;
|
||||
using Lantean.QBTMudBlade.Filter;
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade
|
||||
{
|
||||
public static class DialogHelper
|
||||
{
|
||||
public static readonly DialogOptions FormDialogOptions = new() { CloseButton = true, MaxWidth = MaxWidth.Medium, ClassBackground = "background-blur" };
|
||||
|
||||
private static readonly DialogOptions _confirmDialogOptions = new() { ClassBackground = "background-blur" };
|
||||
|
||||
public static async Task InvokeAddTorrentFileDialog(this IDialogService dialogService, IApiClient apiClient)
|
||||
{
|
||||
var result = await dialogService.ShowAsync<AddTorrentFileDialog>("Upload local torrent", FormDialogOptions);
|
||||
var dialogResult = await result.Result;
|
||||
if (dialogResult.Canceled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var options = (AddTorrentFileOptions)dialogResult.Data;
|
||||
|
||||
var streams = new List<Stream>();
|
||||
|
||||
var files = new Dictionary<string, Stream>();
|
||||
foreach (var file in options.Files)
|
||||
{
|
||||
var stream = file.OpenReadStream();
|
||||
streams.Add(stream);
|
||||
files.Add(file.Name, stream);
|
||||
}
|
||||
|
||||
await apiClient.AddTorrent(
|
||||
urls: null,
|
||||
files,
|
||||
options.SavePath,
|
||||
options.Cookie,
|
||||
options.Category,
|
||||
tags: null,
|
||||
options.SkipHashCheck,
|
||||
!options.StartTorrent,
|
||||
options.ContentLayout,
|
||||
options.RenameTorrent,
|
||||
options.UploadLimit,
|
||||
options.DownloadLimit,
|
||||
ratioLimit: null,
|
||||
seedingTimeLimit: null,
|
||||
options.TorrentManagementMode,
|
||||
options.DownloadInSequentialOrder,
|
||||
options.DownloadFirstAndLastPiecesFirst);
|
||||
|
||||
foreach (var stream in streams)
|
||||
{
|
||||
await stream.DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task InvokeAddTorrentLinkDialog(this IDialogService dialogService, IApiClient apiClient)
|
||||
{
|
||||
var result = await dialogService.ShowAsync<AddTorrentLinkDialog>("Download from URLs", FormDialogOptions);
|
||||
var dialogResult = await result.Result;
|
||||
if (dialogResult.Canceled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var options = (AddTorrentLinkOptions)dialogResult.Data;
|
||||
|
||||
await apiClient.AddTorrent(
|
||||
urls: options.Urls,
|
||||
torrents: null,
|
||||
options.SavePath,
|
||||
options.Cookie,
|
||||
options.Category,
|
||||
tags: null,
|
||||
options.SkipHashCheck,
|
||||
!options.StartTorrent,
|
||||
options.ContentLayout,
|
||||
options.RenameTorrent,
|
||||
options.UploadLimit,
|
||||
options.DownloadLimit,
|
||||
ratioLimit: null,
|
||||
seedingTimeLimit: null,
|
||||
options.TorrentManagementMode,
|
||||
options.DownloadInSequentialOrder,
|
||||
options.DownloadFirstAndLastPiecesFirst);
|
||||
}
|
||||
|
||||
public static async Task InvokeDeleteTorrentDialog(this IDialogService dialogService, IApiClient apiClient, params string[] hashes)
|
||||
{
|
||||
var reference = await dialogService.ShowAsync<DeleteDialog>($"Remove torrent{(hashes.Length == 1 ? "" : "s")}?");
|
||||
var result = await reference.Result;
|
||||
if (result.Canceled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await apiClient.DeleteTorrents(hashes, (bool)result.Data);
|
||||
}
|
||||
|
||||
public static async Task InvokeRenameFilesDialog(this IDialogService dialogService, IApiClient apiClient, string hash)
|
||||
{
|
||||
await Task.Delay(0);
|
||||
}
|
||||
|
||||
public static async Task InvokeAddCategoryDialog(this IDialogService dialogService, IApiClient apiClient, IEnumerable<string>? hashes = null)
|
||||
{
|
||||
var reference = await dialogService.ShowAsync<DeleteDialog>("New Category");
|
||||
var result = await reference.Result;
|
||||
if (result.Canceled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var category = (Category)result.Data;
|
||||
|
||||
await apiClient.AddCategory(category.Name, category.SavePath);
|
||||
|
||||
if (hashes is not null)
|
||||
{
|
||||
await apiClient.SetTorrentCategory(category.Name, null, hashes.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ShowConfirmDialog(this IDialogService dialogService, string title, string content, Func<Task> onSuccess)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ nameof(ConfirmDialog.Content), content }
|
||||
};
|
||||
var result = await dialogService.ShowAsync<ConfirmDialog>(title, parameters, _confirmDialogOptions);
|
||||
|
||||
var dialogResult = await result.Result;
|
||||
if (dialogResult.Canceled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await onSuccess();
|
||||
}
|
||||
|
||||
public static async Task ShowConfirmDialog(this IDialogService dialogService, string title, string content, Action onSuccess)
|
||||
{
|
||||
await ShowConfirmDialog(dialogService, title, content, () =>
|
||||
{
|
||||
onSuccess();
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
public static async Task ShowSingleFieldDialog<T>(this IDialogService dialogService, string title, string label, T? value, Func<T, Task> onSuccess)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ nameof(SingleFieldDialog<T>.Label), label },
|
||||
{ nameof(SingleFieldDialog<T>.Value), value }
|
||||
};
|
||||
var result = await dialogService.ShowAsync<SingleFieldDialog<T>>(title, parameters, _confirmDialogOptions);
|
||||
|
||||
var dialogResult = await result.Result;
|
||||
if (dialogResult.Canceled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await onSuccess((T)dialogResult.Data);
|
||||
}
|
||||
|
||||
public static async Task InvokeUploadRateDialog(this IDialogService dialogService, IApiClient apiClient, long rate, IEnumerable<string> hashes)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ nameof(SliderFieldDialog<long>.Value), rate },
|
||||
{ nameof(SliderFieldDialog<long>.Min), 0 },
|
||||
{ nameof(SliderFieldDialog<long>.Max), 100 },
|
||||
};
|
||||
var result = await dialogService.ShowAsync<SliderFieldDialog<long>>("Upload Rate", parameters, FormDialogOptions);
|
||||
|
||||
var dialogResult = await result.Result;
|
||||
if (dialogResult.Canceled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await apiClient.SetTorrentUploadLimit((long)dialogResult.Data, null, hashes.ToArray());
|
||||
}
|
||||
|
||||
public static async Task InvokeShareRatioDialog(this IDialogService dialogService, IApiClient apiClient, float ratio, IEnumerable<string> hashes)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ nameof(SliderFieldDialog<float>.Value), ratio },
|
||||
{ nameof(SliderFieldDialog<float>.Min), 0 },
|
||||
{ nameof(SliderFieldDialog<float>.Max), 100 },
|
||||
};
|
||||
var result = await dialogService.ShowAsync<SliderFieldDialog<float>>("Upload Rate", parameters, FormDialogOptions);
|
||||
|
||||
var dialogResult = await result.Result;
|
||||
if (dialogResult.Canceled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await apiClient.SetTorrentShareLimit((float)dialogResult.Data, 0, null, hashes.ToArray());
|
||||
}
|
||||
|
||||
public static async Task<List<PropertyFilterDefinition<T>>?> ShowFilterOptionsDialog<T>(this IDialogService dialogService, List<PropertyFilterDefinition<T>>? propertyFilterDefinitions)
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ nameof(FilterOptionsDialog<T>.FilterDefinitions), propertyFilterDefinitions },
|
||||
};
|
||||
|
||||
var result = await dialogService.ShowAsync<FilterOptionsDialog<T>>("Filters", parameters, FormDialogOptions);
|
||||
|
||||
var dialogResult = await result.Result;
|
||||
if (dialogResult.Canceled)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (List<PropertyFilterDefinition<T>>?)dialogResult.Data;
|
||||
}
|
||||
}
|
||||
}
|
379
Lantean.QBTMudBlade/DisplayHelpers.cs
Normal file
379
Lantean.QBTMudBlade/DisplayHelpers.cs
Normal file
@@ -0,0 +1,379 @@
|
||||
using ByteSizeLib;
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using MudBlazor;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace Lantean.QBTMudBlade
|
||||
{
|
||||
public static class DisplayHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Formats a time period in seconds into an appropriate unit based on the size.
|
||||
/// </summary>
|
||||
/// <param name="seconds"></param>
|
||||
/// <param name="prefix"></param>
|
||||
/// <param name="suffix"></param>
|
||||
/// <returns></returns>
|
||||
public static string Duration(long? seconds, string? prefix = null, string? suffix = null)
|
||||
{
|
||||
if (seconds is null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (seconds == 8640000)
|
||||
{
|
||||
return "∞";
|
||||
}
|
||||
|
||||
var time = TimeSpan.FromSeconds(seconds.Value);
|
||||
var sb = new StringBuilder();
|
||||
if (prefix is not null)
|
||||
{
|
||||
sb.Append(prefix);
|
||||
}
|
||||
if (time.Days > 0)
|
||||
{
|
||||
sb.Append(time.Days);
|
||||
sb.Append('d');
|
||||
|
||||
if (time.Hours != 0)
|
||||
{
|
||||
sb.Append(' ');
|
||||
sb.Append(time.Hours);
|
||||
sb.Append('h');
|
||||
}
|
||||
}
|
||||
else if (time.Hours > 0)
|
||||
{
|
||||
sb.Append(time.Hours);
|
||||
sb.Append('h');
|
||||
|
||||
if (time.Minutes != 0)
|
||||
{
|
||||
sb.Append(' ');
|
||||
sb.Append(time.Minutes);
|
||||
sb.Append('m');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(time.Minutes);
|
||||
sb.Append('m');
|
||||
}
|
||||
if (suffix is not null)
|
||||
{
|
||||
sb.Append(' ');
|
||||
sb.Append(suffix);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a file size in bytes into an appropriate unit based on the size.
|
||||
/// </summary>
|
||||
/// <param name="size"></param>
|
||||
/// <param name="prefix"></param>
|
||||
/// <param name="suffix"></param>
|
||||
/// <returns></returns>
|
||||
public static string Size(long? size, string? prefix = null, string? suffix = null)
|
||||
{
|
||||
if (size is null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
var stringBuilder = new StringBuilder();
|
||||
if (prefix is not null)
|
||||
{
|
||||
stringBuilder.Append(prefix);
|
||||
}
|
||||
stringBuilder.Append(ByteSize.FromBytes(size.Value).ToString("#.##"));
|
||||
if (suffix is not null)
|
||||
{
|
||||
stringBuilder.Append(suffix);
|
||||
}
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a file size in bytes into an appropriate unit based on the size.
|
||||
/// </summary>
|
||||
/// <param name="size"></param>
|
||||
/// <param name="prefix"></param>
|
||||
/// <param name="suffix"></param>
|
||||
/// <returns></returns>
|
||||
public static string Size(object? sizeValue, string? prefix = null, string? suffix = null)
|
||||
{
|
||||
if (sizeValue is not long size)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return Size(size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a transfer speed in bytes/s into an appropriate unit based on the size.
|
||||
/// </summary>
|
||||
/// <param name="size"></param>
|
||||
/// <param name="prefix"></param>
|
||||
/// <param name="suffix"></param>
|
||||
/// <returns></returns>
|
||||
public static string Speed(long? size, string? prefix = null, string? suffix = null)
|
||||
{
|
||||
if (size is null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (size == -1)
|
||||
{
|
||||
return "∞";
|
||||
}
|
||||
|
||||
var stringBuilder = new StringBuilder();
|
||||
if (prefix is not null)
|
||||
{
|
||||
stringBuilder.Append(prefix);
|
||||
}
|
||||
stringBuilder.Append(ByteSize.FromBytes(size.Value).ToString("#.##"));
|
||||
stringBuilder.Append("/s");
|
||||
if (suffix is not null)
|
||||
{
|
||||
stringBuilder.Append(suffix);
|
||||
}
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a value into an empty string if null, otherwise the value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="prefix"></param>
|
||||
/// <param name="suffix"></param>
|
||||
/// <returns></returns>
|
||||
public static string EmptyIfNull<T>(T? value, string? prefix = null, string? suffix = null, [StringSyntax("NumericFormat")] string? format = null) where T : struct, IConvertible
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
var stringBuilder = new StringBuilder();
|
||||
if (prefix is not null)
|
||||
{
|
||||
stringBuilder.Append(prefix);
|
||||
}
|
||||
|
||||
if (format is not null)
|
||||
{
|
||||
if (value is long longValue)
|
||||
{
|
||||
stringBuilder.Append(longValue.ToString(format));
|
||||
}
|
||||
else if (value is int intValue)
|
||||
{
|
||||
stringBuilder.Append(intValue.ToString(format));
|
||||
}
|
||||
else if (value is float floatValue)
|
||||
{
|
||||
stringBuilder.Append(floatValue.ToString(format));
|
||||
}
|
||||
else if (value is double doubleValue)
|
||||
{
|
||||
stringBuilder.Append(doubleValue.ToString(format));
|
||||
}
|
||||
else if (value is decimal decimalValue)
|
||||
{
|
||||
stringBuilder.Append(decimalValue.ToString(format));
|
||||
}
|
||||
else if (value is short shortValue)
|
||||
{
|
||||
stringBuilder.Append(shortValue.ToString(format));
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.Append(value.Value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.Append(value.Value);
|
||||
}
|
||||
|
||||
if (suffix is not null)
|
||||
{
|
||||
stringBuilder.Append(suffix);
|
||||
}
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a value into an empty string if null, otherwise the value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="prefix"></param>
|
||||
/// <param name="suffix"></param>
|
||||
/// <returns></returns>
|
||||
public static string EmptyIfNull(string? value, string? prefix = null, string? suffix = null)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
var stringBuilder = new StringBuilder();
|
||||
if (prefix is not null)
|
||||
{
|
||||
stringBuilder.Append(prefix);
|
||||
}
|
||||
stringBuilder.Append(value);
|
||||
if (suffix is not null)
|
||||
{
|
||||
stringBuilder.Append(suffix);
|
||||
}
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a unix time (in seconds) into a local date time.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="negativeDescription"></param>
|
||||
/// <returns></returns>
|
||||
public static string DateTime(long? value, string negativeDescription = "")
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (value.Value == -1)
|
||||
{
|
||||
return negativeDescription;
|
||||
}
|
||||
|
||||
var dateTimeOffset = DateTimeOffset.FromUnixTimeSeconds(value.Value);
|
||||
|
||||
return dateTimeOffset.ToLocalTime().ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a value into a percentage or empty string if null.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static string Percentage(float? value)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (value == 0)
|
||||
{
|
||||
return "0%";
|
||||
}
|
||||
|
||||
return value.Value.ToString("0.#%");
|
||||
}
|
||||
|
||||
public static string State(string? state)
|
||||
{
|
||||
var status = state switch
|
||||
{
|
||||
"downloading" => "Downloading",
|
||||
"stalledDL" => "Stalled",
|
||||
"metaDL" => "Downloading metadata",
|
||||
"forcedMetaDL" => "[F] Downloading metadata",
|
||||
"forcedDL" => "[F] Downloading",
|
||||
"uploading" or "stalledUP" => "Seeding",
|
||||
"forcedUP" => "[F] Seeding",
|
||||
"queuedDL" or "queuedUP" => "Queued",
|
||||
"checkingDL" or "checkingUP" => "Checking",
|
||||
"queuedForChecking" => "Queued for checking",
|
||||
"checkingResumeData" => "Checking resume data",
|
||||
"pausedDL" => "Paused",
|
||||
"pausedUP" => "Completed",
|
||||
"moving" => "Moving",
|
||||
"missingFiles" => "Missing Files",
|
||||
"error" => "Errored",
|
||||
_ => "Unknown",
|
||||
};
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
public static (string, Color) GetStateIcon(string? state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case "forcedDL":
|
||||
case "metaDL":
|
||||
case "forcedMetaDL":
|
||||
case "downloading":
|
||||
return (Icons.Material.Filled.Downloading, Color.Success);
|
||||
case "forcedUP":
|
||||
case "uploading":
|
||||
return (Icons.Material.Filled.Upload, Color.Info);
|
||||
case "stalledUP":
|
||||
return (Icons.Material.Filled.KeyboardDoubleArrowUp, Color.Info);
|
||||
case "stalledDL":
|
||||
return (Icons.Material.Filled.KeyboardDoubleArrowDown, Color.Success);
|
||||
case "pausedDL":
|
||||
return (Icons.Material.Filled.Pause, Color.Success);
|
||||
case "pausedUP":
|
||||
return (Icons.Material.Filled.Pause, Color.Info);
|
||||
case "queuedDL":
|
||||
case "queuedUP":
|
||||
return (Icons.Material.Filled.Queue, Color.Default);
|
||||
case "checkingDL":
|
||||
case "checkingUP":
|
||||
return (Icons.Material.Filled.Loop, Color.Info);
|
||||
case "queuedForChecking":
|
||||
case "checkingResumeData":
|
||||
return (Icons.Material.Filled.Loop, Color.Warning);
|
||||
case "moving":
|
||||
return (Icons.Material.Filled.Moving, Color.Info);
|
||||
case "error":
|
||||
case "unknown":
|
||||
case "missingFiles":
|
||||
return (Icons.Material.Filled.Error, Color.Error);
|
||||
default:
|
||||
return (Icons.Material.Filled.QuestionMark, Color.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
public static (string, Color) GetStatusIcon(string statusValue)
|
||||
{
|
||||
var status = Enum.Parse<Status>(statusValue);
|
||||
return GetStatusIcon(status);
|
||||
}
|
||||
|
||||
private static (string, Color) GetStatusIcon(Status status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
Status.All => (Icons.Material.Filled.AllOut, Color.Warning),
|
||||
Status.Downloading => (Icons.Material.Filled.Downloading, Color.Success),
|
||||
Status.Seeding => (Icons.Material.Filled.Upload, Color.Info),
|
||||
Status.Completed => (Icons.Material.Filled.Check, Color.Default),
|
||||
Status.Resumed => (Icons.Material.Filled.PlayArrow, Color.Success),
|
||||
Status.Paused => (Icons.Material.Filled.Pause, Color.Default),
|
||||
Status.Active => (Icons.Material.Filled.Sort, Color.Success),
|
||||
Status.Inactive => (Icons.Material.Filled.Sort, Color.Error),
|
||||
Status.Stalled => (Icons.Material.Filled.Sort, Color.Info),
|
||||
Status.StalledUploading => (Icons.Material.Filled.KeyboardDoubleArrowUp, Color.Info),
|
||||
Status.StalledDownloading => (Icons.Material.Filled.KeyboardDoubleArrowDown, Color.Success),
|
||||
Status.Checking => (Icons.Material.Filled.Loop, Color.Info),
|
||||
Status.Errored => (Icons.Material.Filled.Error, Color.Error),
|
||||
_ => (Icons.Material.Filled.QuestionMark, Color.Inherit),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
154
Lantean.QBTMudBlade/ExpressionModifier.cs
Normal file
154
Lantean.QBTMudBlade/ExpressionModifier.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Lantean.QBTMudBlade
|
||||
{
|
||||
internal static class ExpressionModifier
|
||||
{
|
||||
internal static Expression<Func<T, bool>> Modify<T>(this Expression firstExpression, Expression secondExpression)
|
||||
{
|
||||
var bodyIdentifier = new ExpressionBodyIdentifier();
|
||||
var body = bodyIdentifier.Identify(firstExpression);
|
||||
var parameterIdentifier = new ExpressionParameterIdentifier();
|
||||
var parameter = (ParameterExpression)parameterIdentifier.Identify(firstExpression);
|
||||
var body2 = bodyIdentifier.Identify(secondExpression);
|
||||
var parameter2 = (ParameterExpression)parameterIdentifier.Identify(secondExpression);
|
||||
|
||||
var treeModifier = new ExpressionReplacer(parameter2, body);
|
||||
return Expression.Lambda<Func<T, bool>>(treeModifier.Visit(body2), parameter);
|
||||
}
|
||||
|
||||
internal static Expression ReplaceBinary(this Expression exp, ExpressionType from, ExpressionType to)
|
||||
{
|
||||
var binaryReplacer = new BinaryReplacer(from, to);
|
||||
return binaryReplacer.Visit(exp);
|
||||
}
|
||||
|
||||
public static Expression<Func<T, bool>> GenerateBinary<T>(this Expression expression, ExpressionType binaryOperation, object? value)
|
||||
{
|
||||
var bodyIdentifier = new ExpressionBodyIdentifier();
|
||||
var body = bodyIdentifier.Identify(expression);
|
||||
var parameterIdentifier = new ExpressionParameterIdentifier();
|
||||
var parameter = (ParameterExpression)parameterIdentifier.Identify(expression);
|
||||
BinaryExpression? binaryExpression;
|
||||
|
||||
if (Nullable.GetUnderlyingType(body.Type) is not null)
|
||||
{
|
||||
// property type is nullable...
|
||||
binaryExpression = Expression.MakeBinary(binaryOperation, body, Expression.Convert(Expression.Constant(value), body.Type));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
// We can short circuit here because the value to be compared is null and the property type is not nullable.
|
||||
return x => true;
|
||||
}
|
||||
|
||||
binaryExpression = Expression.MakeBinary(binaryOperation, body, Expression.Convert(Expression.Constant(value), body.Type));
|
||||
}
|
||||
|
||||
return Expression.Lambda<Func<T, bool>>(binaryExpression, parameter);
|
||||
}
|
||||
|
||||
public static Expression<Func<T, U>> ChangeExpressionReturnType<T, U>(this Expression expression)
|
||||
{
|
||||
var bodyIdentifier = new ExpressionBodyIdentifier();
|
||||
var body = bodyIdentifier.Identify(expression);
|
||||
var parameterIdentifier = new ExpressionParameterIdentifier();
|
||||
var parameter = (ParameterExpression)parameterIdentifier.Identify(expression);
|
||||
|
||||
if (body.Type is U)
|
||||
{
|
||||
// Expression already has the right type.
|
||||
return Expression.Lambda<Func<T, U>>(body, parameter);
|
||||
}
|
||||
|
||||
// Change parameter.
|
||||
var converted = Expression.Convert(body, typeof(U));
|
||||
return Expression.Lambda<Func<T, U>>(converted, parameter);
|
||||
}
|
||||
|
||||
public static (Expression<Func<T, object?>>, Type) CreatePropertySelector<T>(string propertyName)
|
||||
{
|
||||
var type = typeof(T);
|
||||
var propertyInfo = type.GetProperty(propertyName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
||||
if (propertyInfo is null)
|
||||
{
|
||||
throw new InvalidOperationException($"Unable to match property {propertyName} for {type.Name}");
|
||||
}
|
||||
var parameterExpression = Expression.Parameter(type);
|
||||
var propertyExpression = Expression.Property(parameterExpression, propertyInfo);
|
||||
var convertExpression = Expression.Convert(propertyExpression, typeof(object));
|
||||
|
||||
return (Expression.Lambda<Func<T, object?>>(convertExpression, parameterExpression), propertyInfo.PropertyType);
|
||||
}
|
||||
}
|
||||
|
||||
internal class ExpressionReplacer : ExpressionVisitor
|
||||
{
|
||||
private readonly Expression _from;
|
||||
private readonly Expression _to;
|
||||
|
||||
public ExpressionReplacer(Expression from, Expression to)
|
||||
{
|
||||
_from = from;
|
||||
_to = to;
|
||||
}
|
||||
|
||||
[return: NotNullIfNotNull(nameof(node))]
|
||||
public override Expression? Visit(Expression? node)
|
||||
{
|
||||
if (node == _from) return _to;
|
||||
return base.Visit(node);
|
||||
}
|
||||
}
|
||||
|
||||
internal class ExpressionBodyIdentifier : ExpressionVisitor
|
||||
{
|
||||
public Expression Identify(Expression node)
|
||||
{
|
||||
return base.Visit(node);
|
||||
}
|
||||
|
||||
protected override Expression VisitLambda<T>(Expression<T> node)
|
||||
{
|
||||
return node.Body;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ExpressionParameterIdentifier : ExpressionVisitor
|
||||
{
|
||||
public Expression Identify(Expression node)
|
||||
{
|
||||
return base.Visit(node);
|
||||
}
|
||||
|
||||
protected override Expression VisitLambda<T>(Expression<T> node)
|
||||
{
|
||||
return node.Parameters[0];
|
||||
}
|
||||
}
|
||||
|
||||
internal class BinaryReplacer : ExpressionVisitor
|
||||
{
|
||||
private readonly ExpressionType _from;
|
||||
private readonly ExpressionType _to;
|
||||
|
||||
public BinaryReplacer(ExpressionType from, ExpressionType to)
|
||||
{
|
||||
_from = from;
|
||||
_to = to;
|
||||
}
|
||||
|
||||
protected override Expression VisitBinary(BinaryExpression node)
|
||||
{
|
||||
if (node.NodeType == _from)
|
||||
{
|
||||
return Expression.MakeBinary(_to, node.Left, node.Right);
|
||||
}
|
||||
|
||||
return base.VisitBinary(node);
|
||||
}
|
||||
}
|
||||
}
|
54
Lantean.QBTMudBlade/Extensions.cs
Normal file
54
Lantean.QBTMudBlade/Extensions.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using Lantean.QBTMudBlade;
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
|
||||
namespace Lantean.QBTMudBlade
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public const char DirectorySeparator = '/';
|
||||
|
||||
public static string GetDirectoryPath(this string pathAndFileName)
|
||||
{
|
||||
return string.Join(DirectorySeparator, pathAndFileName.Split(DirectorySeparator)[..^1]);
|
||||
}
|
||||
|
||||
public static string GetDirectoryPath(this ContentItem contentItem)
|
||||
{
|
||||
return contentItem.Name.GetDirectoryPath();
|
||||
}
|
||||
|
||||
public static string GetFileName(this string pathAndFileName)
|
||||
{
|
||||
return pathAndFileName.Split(DirectorySeparator)[^1];
|
||||
}
|
||||
|
||||
public static string GetFileName(this ContentItem contentItem)
|
||||
{
|
||||
return contentItem.Name.GetFileName();
|
||||
}
|
||||
|
||||
public static string GetDescendantsKey(this string pathAndFileName, int? level = null)
|
||||
{
|
||||
var paths = pathAndFileName.Split(DirectorySeparator);
|
||||
var index = level is null ? new Index(1, true) : new Index(level.Value);
|
||||
return string.Join(DirectorySeparator, paths[0..index]) + DirectorySeparator;
|
||||
}
|
||||
|
||||
public static string GetDescendantsKey(this ContentItem contentItem, int? level = null)
|
||||
{
|
||||
return contentItem.Name.GetDescendantsKey(level);
|
||||
}
|
||||
|
||||
public static void CancelIfNotDisposed(this CancellationTokenSource cancellationTokenSource)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationTokenSource.Cancel();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// disposed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
134
Lantean.QBTMudBlade/Filter/FilterExpressionGenerator.cs
Normal file
134
Lantean.QBTMudBlade/Filter/FilterExpressionGenerator.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using MudBlazor;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Filter
|
||||
{
|
||||
public static class FilterExpressionGenerator
|
||||
{
|
||||
public static Expression<Func<T, bool>> GenerateExpression<T>(PropertyFilterDefinition<T> filter, bool caseSensitive)
|
||||
{
|
||||
var propertyExpression = filter.Expression;
|
||||
|
||||
if (propertyExpression is null)
|
||||
{
|
||||
return x => true;
|
||||
}
|
||||
|
||||
var fieldType = FieldType.Identify(filter.ColumnType);
|
||||
|
||||
if (fieldType.IsString)
|
||||
{
|
||||
var value = filter.Value?.ToString();
|
||||
|
||||
if (value is null && filter.Operator != FilterOperator.String.Empty && filter.Operator != FilterOperator.String.NotEmpty)
|
||||
{
|
||||
return x => true;
|
||||
}
|
||||
|
||||
var stringComparer = caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
|
||||
|
||||
return filter.Operator switch
|
||||
{
|
||||
FilterOperator.String.Contains =>
|
||||
propertyExpression.Modify<T>((Expression<Func<string?, bool>>)(x => x != null && value != null && x.Contains(value, stringComparer))),
|
||||
FilterOperator.String.NotContains =>
|
||||
propertyExpression.Modify<T>((Expression<Func<string?, bool>>)(x => x != null && value != null && !x.Contains(value, stringComparer))),
|
||||
FilterOperator.String.Equal =>
|
||||
propertyExpression.Modify<T>((Expression<Func<string?, bool>>)(x => x != null && x.Equals(value, stringComparer))),
|
||||
FilterOperator.String.NotEqual =>
|
||||
propertyExpression.Modify<T>((Expression<Func<string?, bool>>)(x => x != null && !x.Equals(value, stringComparer))),
|
||||
FilterOperator.String.StartsWith =>
|
||||
propertyExpression.Modify<T>((Expression<Func<string?, bool>>)(x => x != null && value != null && x.StartsWith(value, stringComparer))),
|
||||
FilterOperator.String.EndsWith =>
|
||||
propertyExpression.Modify<T>((Expression<Func<string?, bool>>)(x => x != null && value != null && x.EndsWith(value, stringComparer))),
|
||||
FilterOperator.String.Empty => propertyExpression.Modify<T>((Expression<Func<string?, bool>>)(x => string.IsNullOrWhiteSpace(x))),
|
||||
FilterOperator.String.NotEmpty => propertyExpression.Modify<T>((Expression<Func<string?, bool>>)(x => !string.IsNullOrWhiteSpace(x))),
|
||||
_ => x => true
|
||||
};
|
||||
}
|
||||
|
||||
if (fieldType.IsNumber)
|
||||
{
|
||||
if (filter.Value is null && filter.Operator != FilterOperator.Number.Empty && filter.Operator != FilterOperator.Number.NotEmpty)
|
||||
{
|
||||
return x => true;
|
||||
}
|
||||
|
||||
return filter.Operator switch
|
||||
{
|
||||
FilterOperator.Number.Equal => propertyExpression.GenerateBinary<T>(ExpressionType.Equal, filter.Value),
|
||||
FilterOperator.Number.NotEqual => propertyExpression.GenerateBinary<T>(ExpressionType.NotEqual, filter.Value),
|
||||
FilterOperator.Number.GreaterThan => propertyExpression.GenerateBinary<T>(ExpressionType.GreaterThan, filter.Value),
|
||||
FilterOperator.Number.GreaterThanOrEqual => propertyExpression.GenerateBinary<T>(ExpressionType.GreaterThanOrEqual, filter.Value),
|
||||
FilterOperator.Number.LessThan => propertyExpression.GenerateBinary<T>(ExpressionType.LessThan, filter.Value),
|
||||
FilterOperator.Number.LessThanOrEqual => propertyExpression.GenerateBinary<T>(ExpressionType.LessThanOrEqual, filter.Value),
|
||||
FilterOperator.Number.Empty => propertyExpression.GenerateBinary<T>(ExpressionType.Equal, null),
|
||||
FilterOperator.Number.NotEmpty => propertyExpression.GenerateBinary<T>(ExpressionType.NotEqual, null),
|
||||
_ => x => true
|
||||
};
|
||||
}
|
||||
|
||||
if (fieldType.IsDateTime)
|
||||
{
|
||||
if (filter.Value is null && filter.Operator != FilterOperator.DateTime.Empty && filter.Operator != FilterOperator.DateTime.NotEmpty)
|
||||
{
|
||||
return x => true;
|
||||
}
|
||||
|
||||
return filter.Operator switch
|
||||
{
|
||||
FilterOperator.DateTime.Is => propertyExpression.GenerateBinary<T>(ExpressionType.Equal, filter.Value),
|
||||
FilterOperator.DateTime.IsNot => propertyExpression.GenerateBinary<T>(ExpressionType.NotEqual, filter.Value),
|
||||
FilterOperator.DateTime.After => propertyExpression.GenerateBinary<T>(ExpressionType.GreaterThan, filter.Value),
|
||||
FilterOperator.DateTime.OnOrAfter => propertyExpression.GenerateBinary<T>(ExpressionType.GreaterThanOrEqual, filter.Value),
|
||||
FilterOperator.DateTime.Before => propertyExpression.GenerateBinary<T>(ExpressionType.LessThan, filter.Value),
|
||||
FilterOperator.DateTime.OnOrBefore => propertyExpression.GenerateBinary<T>(ExpressionType.LessThanOrEqual, filter.Value),
|
||||
FilterOperator.DateTime.Empty => propertyExpression.GenerateBinary<T>(ExpressionType.Equal, null),
|
||||
FilterOperator.DateTime.NotEmpty => propertyExpression.GenerateBinary<T>(ExpressionType.NotEqual, null),
|
||||
_ => x => true
|
||||
};
|
||||
}
|
||||
|
||||
if (fieldType.IsBoolean)
|
||||
{
|
||||
if (filter.Value is null)
|
||||
{
|
||||
return x => true;
|
||||
}
|
||||
|
||||
return filter.Operator switch
|
||||
{
|
||||
FilterOperator.Boolean.Is => propertyExpression.GenerateBinary<T>(ExpressionType.Equal, filter.Value),
|
||||
_ => x => true
|
||||
};
|
||||
}
|
||||
|
||||
if (fieldType.IsEnum)
|
||||
{
|
||||
if (filter.Value is null)
|
||||
{
|
||||
return x => true;
|
||||
}
|
||||
|
||||
return filter.Operator switch
|
||||
{
|
||||
FilterOperator.Enum.Is => propertyExpression.GenerateBinary<T>(ExpressionType.Equal, filter.Value),
|
||||
FilterOperator.Enum.IsNot => propertyExpression.GenerateBinary<T>(ExpressionType.NotEqual, filter.Value),
|
||||
_ => x => true
|
||||
};
|
||||
}
|
||||
|
||||
if (fieldType.IsGuid)
|
||||
{
|
||||
return filter.Operator switch
|
||||
{
|
||||
FilterOperator.Guid.Equal => propertyExpression.GenerateBinary<T>(ExpressionType.Equal, filter.Value),
|
||||
FilterOperator.Guid.NotEqual => propertyExpression.GenerateBinary<T>(ExpressionType.NotEqual, filter.Value),
|
||||
_ => x => true
|
||||
};
|
||||
}
|
||||
|
||||
return x => true;
|
||||
}
|
||||
}
|
||||
}
|
137
Lantean.QBTMudBlade/Filter/FilterOperator.cs
Normal file
137
Lantean.QBTMudBlade/Filter/FilterOperator.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Filter
|
||||
{
|
||||
public static class FilterOperator
|
||||
{
|
||||
public static class String
|
||||
{
|
||||
public const string Contains = "contains";
|
||||
public const string NotContains = "not contains";
|
||||
public const string Equal = "equals";
|
||||
public const string NotEqual = "not equals";
|
||||
public const string StartsWith = "starts with";
|
||||
public const string EndsWith = "ends with";
|
||||
public const string Empty = "is empty";
|
||||
public const string NotEmpty = "is not empty";
|
||||
}
|
||||
|
||||
public static class Number
|
||||
{
|
||||
public const string Equal = "=";
|
||||
public const string NotEqual = "!=";
|
||||
public const string GreaterThan = ">";
|
||||
public const string GreaterThanOrEqual = ">=";
|
||||
public const string LessThan = "<";
|
||||
public const string LessThanOrEqual = "<=";
|
||||
public const string Empty = "is empty";
|
||||
public const string NotEmpty = "is not empty";
|
||||
}
|
||||
|
||||
public static class Enum
|
||||
{
|
||||
public const string Is = "is";
|
||||
public const string IsNot = "is not";
|
||||
}
|
||||
|
||||
public static class Boolean
|
||||
{
|
||||
public const string Is = "is";
|
||||
}
|
||||
|
||||
public static class DateTime
|
||||
{
|
||||
public const string Is = "is";
|
||||
public const string IsNot = "is not";
|
||||
public const string After = "is after";
|
||||
public const string OnOrAfter = "is on or after";
|
||||
public const string Before = "is before";
|
||||
public const string OnOrBefore = "is on or before";
|
||||
public const string Empty = "is empty";
|
||||
public const string NotEmpty = "is not empty";
|
||||
}
|
||||
|
||||
public static class Guid
|
||||
{
|
||||
public const string Equal = "equals";
|
||||
public const string NotEqual = "not equals";
|
||||
}
|
||||
|
||||
internal static string[] GetOperatorByDataType(Type type)
|
||||
{
|
||||
var fieldType = FieldType.Identify(type);
|
||||
return GetOperatorByDataType(fieldType);
|
||||
}
|
||||
|
||||
internal static string[] GetOperatorByDataType(FieldType fieldType)
|
||||
{
|
||||
if (fieldType.IsString)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
String.Contains,
|
||||
String.NotContains,
|
||||
String.Equal,
|
||||
String.NotEqual,
|
||||
String.StartsWith,
|
||||
String.EndsWith,
|
||||
String.Empty,
|
||||
String.NotEmpty,
|
||||
};
|
||||
}
|
||||
if (fieldType.IsNumber)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
Number.Equal,
|
||||
Number.NotEqual,
|
||||
Number.GreaterThan,
|
||||
Number.GreaterThanOrEqual,
|
||||
Number.LessThan,
|
||||
Number.LessThanOrEqual,
|
||||
Number.Empty,
|
||||
Number.NotEmpty,
|
||||
};
|
||||
}
|
||||
if (fieldType.IsEnum)
|
||||
{
|
||||
return new[] {
|
||||
Enum.Is,
|
||||
Enum.IsNot,
|
||||
};
|
||||
}
|
||||
if (fieldType.IsBoolean)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
Boolean.Is,
|
||||
};
|
||||
}
|
||||
if (fieldType.IsDateTime)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
DateTime.Is,
|
||||
DateTime.IsNot,
|
||||
DateTime.After,
|
||||
DateTime.OnOrAfter,
|
||||
DateTime.Before,
|
||||
DateTime.OnOrBefore,
|
||||
DateTime.Empty,
|
||||
DateTime.NotEmpty,
|
||||
};
|
||||
}
|
||||
if (fieldType.IsGuid)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
Guid.Equal,
|
||||
Guid.NotEqual,
|
||||
};
|
||||
}
|
||||
|
||||
// default
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
}
|
32
Lantean.QBTMudBlade/Filter/PropertyFilterDefinition.cs
Normal file
32
Lantean.QBTMudBlade/Filter/PropertyFilterDefinition.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using MudBlazor;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Filter
|
||||
{
|
||||
public record PropertyFilterDefinition<T>
|
||||
{
|
||||
public PropertyFilterDefinition(string column, string @operator, object? value)
|
||||
{
|
||||
var (expression, propertyType) = ExpressionModifier.CreatePropertySelector<T>(column);
|
||||
|
||||
Column = column;
|
||||
ColumnType = propertyType;
|
||||
Operator = @operator;
|
||||
Value = value;
|
||||
Expression = expression;
|
||||
}
|
||||
|
||||
public string Column { get; }
|
||||
|
||||
public Type ColumnType { get; }
|
||||
|
||||
public string Operator { get; set; }
|
||||
|
||||
public object? Value { get; set; }
|
||||
|
||||
public Expression<Func<T, object?>> Expression { get; }
|
||||
}
|
||||
|
||||
|
||||
}
|
282
Lantean.QBTMudBlade/FilterHelper.cs
Normal file
282
Lantean.QBTMudBlade/FilterHelper.cs
Normal file
@@ -0,0 +1,282 @@
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
|
||||
namespace Lantean.QBTMudBlade
|
||||
{
|
||||
public static class FilterHelper
|
||||
{
|
||||
public const string TAG_ALL = "All";
|
||||
public const string TAG_UNTAGGED = "Untagged";
|
||||
public const string CATEGORY_ALL = "All";
|
||||
public const string CATEGORY_UNCATEGORIZED = "Uncategorized";
|
||||
public const string TRACKER_ALL = "All";
|
||||
public const string TRACKER_TRACKERLESS = "Trackerless";
|
||||
|
||||
public static IEnumerable<Torrent> Filter(this IEnumerable<Torrent> torrents, FilterState filterState)
|
||||
{
|
||||
return torrents.Where(t => FilterStatus(t, filterState.Status))
|
||||
.Where(t => FilterTag(t, filterState.Tag))
|
||||
.Where(t => FilterCategory(t, filterState.Category, filterState.UseSubcategories))
|
||||
.Where(t => FilterTracker(t, filterState.Tracker))
|
||||
.Where(t => FilterTerms(t.Name, filterState.Terms));
|
||||
}
|
||||
|
||||
public static HashSet<string> ToHashesHashSet(this IEnumerable<Torrent> torrents)
|
||||
{
|
||||
return torrents.Select(t => t.Hash).ToHashSet();
|
||||
}
|
||||
|
||||
public static bool AddIfTrue(this HashSet<string> hashSet, string value, bool condition)
|
||||
{
|
||||
if (condition)
|
||||
{
|
||||
return hashSet.Add(value);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool RemoveIfTrue(this HashSet<string> hashSet, string value, bool condition)
|
||||
{
|
||||
if (condition)
|
||||
{
|
||||
return hashSet.Remove(value);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool AddIfTrueOrRemove(this HashSet<string> hashSet, string value, bool condition)
|
||||
{
|
||||
if (condition)
|
||||
{
|
||||
return hashSet.Add(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return hashSet.Remove(value);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ContainsAllTerms(string text, IEnumerable<string> terms)
|
||||
{
|
||||
return terms.Any(t =>
|
||||
{
|
||||
var term = t;
|
||||
var isTermRequired = term[0] == '+';
|
||||
var isTermExcluded = term[0] == '-';
|
||||
|
||||
if (isTermRequired || isTermExcluded)
|
||||
{
|
||||
if (term.Length == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
term = term[1..];
|
||||
}
|
||||
|
||||
var textContainsTerm = text.Contains(term, StringComparison.OrdinalIgnoreCase);
|
||||
return isTermExcluded ? !textContainsTerm : textContainsTerm;
|
||||
});
|
||||
}
|
||||
|
||||
public static bool FilterTerms(string field, string? terms)
|
||||
{
|
||||
if (terms is null || terms == "")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return ContainsAllTerms(field, terms.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
|
||||
}
|
||||
|
||||
public static bool FilterTerms(Torrent torrent, string? terms)
|
||||
{
|
||||
if (terms is null || terms == "")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return ContainsAllTerms(torrent.Name, terms.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
|
||||
}
|
||||
|
||||
public static bool FilterTracker(Torrent torrent, string tracker)
|
||||
{
|
||||
if (tracker == TRACKER_ALL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tracker == TRACKER_TRACKERLESS)
|
||||
{
|
||||
return torrent.Tracker == "";
|
||||
}
|
||||
|
||||
return torrent.Tracker == tracker;
|
||||
}
|
||||
|
||||
public static bool FilterCategory(Torrent torrent, string category, bool useSubcategories)
|
||||
{
|
||||
switch (category)
|
||||
{
|
||||
case CATEGORY_ALL:
|
||||
break;
|
||||
|
||||
case CATEGORY_UNCATEGORIZED:
|
||||
if (!string.IsNullOrEmpty(torrent.Category))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (!useSubcategories)
|
||||
{
|
||||
if (torrent.Category != category)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!torrent.Category.StartsWith(category))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool FilterTag(Torrent torrent, string tag)
|
||||
{
|
||||
if (tag == TAG_ALL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tag == TAG_UNTAGGED)
|
||||
{
|
||||
return torrent.Tags.Count == 0;
|
||||
}
|
||||
|
||||
return torrent.Tags.Contains(tag);
|
||||
}
|
||||
|
||||
public static bool FilterStatus(Torrent torrent, Status status)
|
||||
{
|
||||
var state = torrent.State;
|
||||
bool inactive = false;
|
||||
switch (status)
|
||||
{
|
||||
case Status.All:
|
||||
return true;
|
||||
|
||||
case Status.Downloading:
|
||||
if (state != "downloading" && !state.Contains("DL"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case Status.Seeding:
|
||||
if (state != "uploading" && state != "forcedUP" && state != "stalledUP" && state != "queuedUP" && state != "checkingUP")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case Status.Completed:
|
||||
if (state != "uploading" && !state.Contains("UL"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case Status.Resumed:
|
||||
if (!state.Contains("resumed"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case Status.Paused:
|
||||
if (!state.Contains("paused"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case Status.Inactive:
|
||||
case Status.Active:
|
||||
if (status == Status.Inactive)
|
||||
{
|
||||
inactive = true;
|
||||
}
|
||||
bool check;
|
||||
if (state == "stalledDL")
|
||||
{
|
||||
check = torrent.UploadSpeed > 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
check = state == "metaDL" || state == "forcedMetaDL" || state == "downloading" || state == "forcedDL" || state == "uploading" || state == "forcedUP";
|
||||
}
|
||||
|
||||
if (check == inactive)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case Status.Stalled:
|
||||
if (state != "stalledUP" && state != "stalledDL")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case Status.StalledUploading:
|
||||
if (state != "stalledUP")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case Status.StalledDownloading:
|
||||
if (state != "stalledDL")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case Status.Checking:
|
||||
if (state != "checkingUP" && state != "checkingDL" && state != "checkingResumeData")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case Status.Errored:
|
||||
if (state != "error" && state != "unknown" && state != "missingFiles")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string GetStatusName(this string status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
nameof(Status.StalledUploading) => "Stalled Uploading",
|
||||
nameof(Status.StalledDownloading) => "Stalled Downloading",
|
||||
_ => status,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
24
Lantean.QBTMudBlade/FilterState.cs
Normal file
24
Lantean.QBTMudBlade/FilterState.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
|
||||
namespace Lantean.QBTMudBlade
|
||||
{
|
||||
public struct FilterState
|
||||
{
|
||||
public FilterState(string category, Status status, string tag, string tracker, bool useSubcategories, string? terms)
|
||||
{
|
||||
Category = category;
|
||||
Status = status;
|
||||
Tag = tag;
|
||||
Tracker = tracker;
|
||||
UseSubcategories = useSubcategories;
|
||||
Terms = terms;
|
||||
}
|
||||
|
||||
public string Category { get; } = "all";
|
||||
public Status Status { get; } = Status.All;
|
||||
public string Tag { get; } = "all";
|
||||
public string Tracker { get; } = "all";
|
||||
public bool UseSubcategories { get; }
|
||||
public string? Terms { get; }
|
||||
}
|
||||
}
|
8
Lantean.QBTMudBlade/GlobalSuppressions.cs
Normal file
8
Lantean.QBTMudBlade/GlobalSuppressions.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
// This file is used by Code Analysis to maintain SuppressMessage
|
||||
// attributes that are applied to this project.
|
||||
// Project-level suppressions either have no target or are given
|
||||
// a specific target and scoped to a namespace, type, member, etc.
|
||||
|
||||
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)")]
|
21
Lantean.QBTMudBlade/Interop/BoundingClientRect.cs
Normal file
21
Lantean.QBTMudBlade/Interop/BoundingClientRect.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace Lantean.QBTMudBlade.Interop
|
||||
{
|
||||
public class BoundingClientRect
|
||||
{
|
||||
public int Bottom { get; set; }
|
||||
|
||||
public int Top { get; set; }
|
||||
|
||||
public int Left { get; set; }
|
||||
|
||||
public int Right { get; set; }
|
||||
|
||||
public int Width { get; set; }
|
||||
|
||||
public int Height { get; set; }
|
||||
|
||||
public int X { get; set; }
|
||||
|
||||
public int Y { get; set; }
|
||||
}
|
||||
}
|
12
Lantean.QBTMudBlade/Interop/InteropHelper.cs
Normal file
12
Lantean.QBTMudBlade/Interop/InteropHelper.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Interop
|
||||
{
|
||||
public static class InteropHelper
|
||||
{
|
||||
public static async Task<BoundingClientRect?> GetBoundingClientRect(this IJSRuntime runtime, string id)
|
||||
{
|
||||
return await runtime.InvokeAsync<BoundingClientRect?>("qbt.getBoundingClientRect", id);
|
||||
}
|
||||
}
|
||||
}
|
23
Lantean.QBTMudBlade/Lantean.QBTMudBlade.csproj
Normal file
23
Lantean.QBTMudBlade/Lantean.QBTMudBlade.csproj
Normal file
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
|
||||
<PackageReference Include="ByteSize" Version="2.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.3" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="MudBlazor" Version="6.17.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Lantean.QBitTorrentClient\Lantean.QBitTorrentClient.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
9
Lantean.QBTMudBlade/Layout/DetailsLayout.razor
Normal file
9
Lantean.QBTMudBlade/Layout/DetailsLayout.razor
Normal file
@@ -0,0 +1,9 @@
|
||||
@inherits LayoutComponentBase
|
||||
@layout LoggedInLayout
|
||||
|
||||
<MudDrawer Open="DrawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2" DisableOverlay="true">
|
||||
<TorrentsListNav Torrents="Torrents" SelectedTorrent="@SelectedTorrent" />
|
||||
</MudDrawer>
|
||||
<MudMainContent>
|
||||
@Body
|
||||
</MudMainContent>
|
29
Lantean.QBTMudBlade/Layout/DetailsLayout.razor.cs
Normal file
29
Lantean.QBTMudBlade/Layout/DetailsLayout.razor.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Layout
|
||||
{
|
||||
public partial class DetailsLayout
|
||||
{
|
||||
[CascadingParameter(Name = "DrawerOpen")]
|
||||
public bool DrawerOpen { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public IEnumerable<Torrent>? Torrents { get; set; }
|
||||
|
||||
protected string? SelectedTorrent { get; set; }
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (Body?.Target is not RouteView routeView || routeView.RouteData.RouteValues is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (routeView.RouteData.RouteValues.TryGetValue("hash", out var hash))
|
||||
{
|
||||
SelectedTorrent = hash?.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Lantean.QBTMudBlade/Layout/ListLayout.razor
Normal file
11
Lantean.QBTMudBlade/Layout/ListLayout.razor
Normal file
@@ -0,0 +1,11 @@
|
||||
@inherits LayoutComponentBase
|
||||
@layout LoggedInLayout
|
||||
|
||||
<MudDrawer Open="DrawerOpen" ClipMode="DrawerClipMode.Always" Elevation="2" DisableOverlay="true">
|
||||
<FiltersNav CategoryChanged="CategoryChanged" StatusChanged="StatusChanged" TagChanged="TagChanged" TrackerChanged="TrackerChanged" />
|
||||
</MudDrawer>
|
||||
<MudMainContent>
|
||||
<CascadingValue Value="SearchTermChanged" Name="SearchTermChanged">
|
||||
@Body
|
||||
</CascadingValue>
|
||||
</MudMainContent>
|
26
Lantean.QBTMudBlade/Layout/ListLayout.razor.cs
Normal file
26
Lantean.QBTMudBlade/Layout/ListLayout.razor.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Layout
|
||||
{
|
||||
public partial class ListLayout
|
||||
{
|
||||
[CascadingParameter(Name = "DrawerOpen")]
|
||||
public bool DrawerOpen { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "StatusChanged")]
|
||||
public EventCallback<Status> StatusChanged { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "CategoryChanged")]
|
||||
public EventCallback<string> CategoryChanged { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "TagChanged")]
|
||||
public EventCallback<string> TagChanged { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "TrackerChanged")]
|
||||
public EventCallback<string> TrackerChanged { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "SearchTermChanged")]
|
||||
public EventCallback<string> SearchTermChanged { get; set; }
|
||||
}
|
||||
}
|
57
Lantean.QBTMudBlade/Layout/LoggedInLayout.razor
Normal file
57
Lantean.QBTMudBlade/Layout/LoggedInLayout.razor
Normal file
@@ -0,0 +1,57 @@
|
||||
@inherits LayoutComponentBase
|
||||
@layout MainLayout
|
||||
|
||||
<PageTitle>qBittorrent @Version Web UI</PageTitle>
|
||||
|
||||
@if (!IsAuthenticated)
|
||||
{
|
||||
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Class="my-7" Style="width: 100%; height: 30px" />
|
||||
return;
|
||||
}
|
||||
|
||||
<CascadingValue Value="Torrents">
|
||||
<CascadingValue Value="MainData">
|
||||
<CascadingValue Value="CategoryChanged" Name="CategoryChanged">
|
||||
<CascadingValue Value="StatusChanged" Name="StatusChanged">
|
||||
<CascadingValue Value="TagChanged" Name="TagChanged">
|
||||
<CascadingValue Value="TrackerChanged" Name="TrackerChanged">
|
||||
<CascadingValue Value="SearchTermChanged" Name="SearchTermChanged">
|
||||
<CascadingValue Value="@(MainData?.LostConnection ?? false)" Name="LostConnection">
|
||||
@Body
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
<MudAppBar Bottom="true" Style="background-color: var(--mud-palette-dark-lighten);">
|
||||
@if (MainData?.LostConnection == true)
|
||||
{
|
||||
<MudText Color="Color.Error">qBittorrent client is not reachable</MudText>
|
||||
}
|
||||
<MudSpacer />
|
||||
<MudText Class="pl-1 pr-1">@DisplayHelpers.Size(MainData?.ServerState.FreeSpaceOnDisk, "Free space: ")</MudText>
|
||||
<MudDivider Vertical="true" />
|
||||
<MudText Class="pl-1 pr-1">DHT @(MainData?.ServerState.DHTNodes ?? 0) nodes</MudText>
|
||||
<MudDivider Vertical="true" />
|
||||
@{
|
||||
var (icon, colour) = GetConnectionIcon(MainData?.ServerState.ConnectionStatus);
|
||||
}
|
||||
<MudIcon Class="pl-1 pr-1" Icon="@icon" Color="@colour" Title="MainData?.ServerState.ConnectionStatus" />
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIcon Class="pl-1 pr-1" Icon="@Icons.Material.Outlined.Speed" Color="@((MainData?.ServerState.UseAltSpeedLimits ?? false) ? Color.Error : Color.Success)" />
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIcon Class="pl-1" Icon="@Icons.Material.Filled.KeyboardDoubleArrowUp" Color="Color.Success" />
|
||||
<MudText Class="pr-1">
|
||||
@DisplayHelpers.Size(MainData?.ServerState.DownloadInfoSpeed, null, "/s")
|
||||
@DisplayHelpers.Size(MainData?.ServerState.DownloadInfoData, "(", ")")
|
||||
</MudText>
|
||||
<MudDivider Vertical="true" />
|
||||
<MudIcon Class="pl-1" Icon="@Icons.Material.Filled.KeyboardDoubleArrowDown" Color="Color.Info" />
|
||||
<MudText Class="pr-1">
|
||||
@DisplayHelpers.Size(MainData?.ServerState.UploadInfoSpeed, null, "/s")
|
||||
@DisplayHelpers.Size(MainData?.ServerState.UploadInfoData, "(", ")")
|
||||
</MudText>
|
||||
</MudAppBar>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
173
Lantean.QBTMudBlade/Layout/LoggedInLayout.razor.cs
Normal file
173
Lantean.QBTMudBlade/Layout/LoggedInLayout.razor.cs
Normal file
@@ -0,0 +1,173 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBTMudBlade.Models;
|
||||
using Lantean.QBTMudBlade.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Layout
|
||||
{
|
||||
public partial class LoggedInLayout : IDisposable
|
||||
{
|
||||
private readonly bool _refreshEnabled = true;
|
||||
|
||||
private int _requestId = 0;
|
||||
private bool _disposedValue;
|
||||
private readonly CancellationTokenSource _timerCancellationToken = new();
|
||||
private int _refreshInterval = 1500;
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected IDataManager DataManager { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected NavigationManager NavigationManager { get; set; } = default!;
|
||||
|
||||
[CascadingParameter(Name = "DrawerOpen")]
|
||||
public bool DrawerOpen { get; set; }
|
||||
|
||||
protected MainData? MainData { get; set; }
|
||||
|
||||
protected string Category { get; set; } = FilterHelper.CATEGORY_ALL;
|
||||
|
||||
protected string Tag { get; set; } = FilterHelper.TAG_ALL;
|
||||
|
||||
protected string Tracker { get; set; } = FilterHelper.TRACKER_ALL;
|
||||
|
||||
protected Status Status { get; set; } = Status.All;
|
||||
|
||||
protected string Version { get; set; } = "";
|
||||
|
||||
protected string? SearchText { get; set; }
|
||||
|
||||
protected IEnumerable<Torrent> Torrents => GetTorrents();
|
||||
|
||||
protected bool IsAuthenticated { get; set; }
|
||||
|
||||
protected bool LostConnection { get; set; }
|
||||
|
||||
private List<Torrent> GetTorrents()
|
||||
{
|
||||
if (MainData is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var filterState = new FilterState(Category, Status, Tag, Tracker, MainData.ServerState.UseSubcategories, SearchText);
|
||||
|
||||
return MainData.Torrents.Values.Filter(filterState).ToList();
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (!await ApiClient.CheckAuthState())
|
||||
{
|
||||
NavigationManager.NavigateTo("/login");
|
||||
return;
|
||||
}
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
Version = await ApiClient.GetApplicationVersion();
|
||||
var data = await ApiClient.GetMainData(_requestId);
|
||||
MainData = DataManager.CreateMainData(data);
|
||||
|
||||
_requestId = data.ResponseId;
|
||||
_refreshInterval = MainData.ServerState.RefreshInterval;
|
||||
|
||||
IsAuthenticated = true;
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (!_refreshEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstRender)
|
||||
{
|
||||
using (var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(_refreshInterval)))
|
||||
{
|
||||
while (!_timerCancellationToken.IsCancellationRequested && await timer.WaitForNextTickAsync())
|
||||
{
|
||||
if (!IsAuthenticated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
QBitTorrentClient.Models.MainData data;
|
||||
try
|
||||
{
|
||||
data = await ApiClient.GetMainData(_requestId);
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
if (MainData is not null)
|
||||
{
|
||||
MainData.LostConnection = true;
|
||||
}
|
||||
_timerCancellationToken.CancelIfNotDisposed();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
return;
|
||||
}
|
||||
|
||||
if (MainData is null || data.FullUpdate)
|
||||
{
|
||||
MainData = DataManager.CreateMainData(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
DataManager.MergeMainData(data, MainData);
|
||||
}
|
||||
|
||||
_refreshInterval = MainData.ServerState.RefreshInterval;
|
||||
_requestId = data.ResponseId;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected EventCallback<string> CategoryChanged => EventCallback.Factory.Create<string>(this, category => Category = category);
|
||||
|
||||
protected EventCallback<Status> StatusChanged => EventCallback.Factory.Create<Status>(this, status => Status = status);
|
||||
|
||||
protected EventCallback<string> TagChanged => EventCallback.Factory.Create<string>(this, tag => Tag = tag);
|
||||
|
||||
protected EventCallback<string> TrackerChanged => EventCallback.Factory.Create<string>(this, tracker => Tracker = tracker);
|
||||
|
||||
protected EventCallback<string> SearchTermChanged => EventCallback.Factory.Create<string>(this, term => SearchText = term);
|
||||
|
||||
protected static (string, Color) GetConnectionIcon(string? status)
|
||||
{
|
||||
if (status is null)
|
||||
{
|
||||
return (Icons.Material.Outlined.SignalWifiOff, Color.Warning);
|
||||
}
|
||||
|
||||
return (Icons.Material.Outlined.SignalWifi4Bar, Color.Success);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_timerCancellationToken.Cancel();
|
||||
_timerCancellationToken.Dispose();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
22
Lantean.QBTMudBlade/Layout/MainLayout.razor
Normal file
22
Lantean.QBTMudBlade/Layout/MainLayout.razor
Normal file
@@ -0,0 +1,22 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<MudThemeProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
<PageTitle>qBittorrent Web UI</PageTitle>
|
||||
|
||||
<MudLayout>
|
||||
<MudAppBar Elevation="1">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="ToggleDrawer" />
|
||||
<MudText Typo="Typo.h5" Class="ml-3">qBittorrent Web UI</MudText>
|
||||
<MudSpacer />
|
||||
@if (ShowMenu)
|
||||
{
|
||||
<Menu />
|
||||
}
|
||||
</MudAppBar>
|
||||
<CascadingValue Value="DrawerOpen" Name="DrawerOpen">
|
||||
@Body
|
||||
</CascadingValue>
|
||||
</MudLayout>
|
89
Lantean.QBTMudBlade/Layout/MainLayout.razor.cs
Normal file
89
Lantean.QBTMudBlade/Layout/MainLayout.razor.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
using MudBlazor.Services;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Layout
|
||||
{
|
||||
public partial class MainLayout : IBrowserViewportObserver, IAsyncDisposable
|
||||
{
|
||||
private bool _disposedValue;
|
||||
|
||||
[Inject]
|
||||
protected NavigationManager NavigationManager { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
private IBrowserViewportService BrowserViewportService { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
private IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
protected bool DrawerOpen { get; set; } = true;
|
||||
|
||||
protected bool ShowMenu { get; set; } = false;
|
||||
|
||||
public Guid Id => Guid.NewGuid();
|
||||
|
||||
ResizeOptions IBrowserViewportObserver.ResizeOptions { get; } = new()
|
||||
{
|
||||
ReportRate = 50,
|
||||
NotifyOnBreakpointOnly = true
|
||||
};
|
||||
|
||||
protected void ToggleDrawer()
|
||||
{
|
||||
DrawerOpen = !DrawerOpen;
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (!ShowMenu)
|
||||
{
|
||||
ShowMenu = await ApiClient.CheckAuthState();
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await BrowserViewportService.SubscribeAsync(this, fireImmediately: true);
|
||||
}
|
||||
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
}
|
||||
|
||||
public async Task NotifyBrowserViewportChangeAsync(BrowserViewportEventArgs browserViewportEventArgs)
|
||||
{
|
||||
if (browserViewportEventArgs.Breakpoint == Breakpoint.Sm && DrawerOpen)
|
||||
{
|
||||
DrawerOpen = false;
|
||||
}
|
||||
else if (browserViewportEventArgs.Breakpoint > Breakpoint.Sm && !DrawerOpen)
|
||||
{
|
||||
DrawerOpen = true;
|
||||
}
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
protected virtual async Task DisposeAsync(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
await BrowserViewportService.UnsubscribeAsync(this);
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
await DisposeAsync(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
14
Lantean.QBTMudBlade/Models/AddTorrentFileOptions.cs
Normal file
14
Lantean.QBTMudBlade/Models/AddTorrentFileOptions.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
|
||||
namespace Lantean.QBTMudBlade.Models
|
||||
{
|
||||
public record AddTorrentFileOptions : TorrentOptions
|
||||
{
|
||||
public AddTorrentFileOptions(IReadOnlyList<IBrowserFile> files, TorrentOptions options) : base(options)
|
||||
{
|
||||
Files = files;
|
||||
}
|
||||
|
||||
public IReadOnlyList<IBrowserFile> Files { get; }
|
||||
}
|
||||
}
|
12
Lantean.QBTMudBlade/Models/AddTorrentLinkOptions.cs
Normal file
12
Lantean.QBTMudBlade/Models/AddTorrentLinkOptions.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Lantean.QBTMudBlade.Models
|
||||
{
|
||||
public record AddTorrentLinkOptions : TorrentOptions
|
||||
{
|
||||
public AddTorrentLinkOptions(string urls, TorrentOptions options) : base(options)
|
||||
{
|
||||
Urls = urls.Split('\n', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
public IReadOnlyList<string> Urls { get; }
|
||||
}
|
||||
}
|
14
Lantean.QBTMudBlade/Models/Category.cs
Normal file
14
Lantean.QBTMudBlade/Models/Category.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace Lantean.QBTMudBlade.Models
|
||||
{
|
||||
public record Category
|
||||
{
|
||||
public Category(string name, string savePath)
|
||||
{
|
||||
Name = name;
|
||||
SavePath = savePath;
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
public string SavePath { get; set; }
|
||||
}
|
||||
}
|
62
Lantean.QBTMudBlade/Models/ContentItem.cs
Normal file
62
Lantean.QBTMudBlade/Models/ContentItem.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
namespace Lantean.QBTMudBlade.Models
|
||||
{
|
||||
public class ContentItem
|
||||
{
|
||||
public ContentItem(
|
||||
string name,
|
||||
string displayName,
|
||||
int index,
|
||||
Priority priority,
|
||||
float progress,
|
||||
long size,
|
||||
float availability,
|
||||
bool isFolder = false,
|
||||
int level = 0)
|
||||
{
|
||||
Name = name;
|
||||
DisplayName = displayName;
|
||||
Index = index;
|
||||
Priority = priority;
|
||||
Progress = progress;
|
||||
Size = size;
|
||||
Availability = availability;
|
||||
IsFolder = isFolder;
|
||||
Level = level;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string Path => IsFolder ? Name : Name.GetDirectoryPath();
|
||||
|
||||
public string DisplayName { get; }
|
||||
|
||||
public int Index { get; }
|
||||
|
||||
public Priority Priority { get; set; }
|
||||
|
||||
public float Progress { get; set; }
|
||||
|
||||
public long Size { get; set; }
|
||||
|
||||
public float Availability { get; set; }
|
||||
|
||||
public long Downloaded => (long)Math.Round(Size * Progress, 0);
|
||||
|
||||
public long Remaining => Size - Downloaded;
|
||||
|
||||
public bool IsFolder { get; }
|
||||
|
||||
public int Level { get; }
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is null) return false;
|
||||
return ((ContentItem)obj).Name == Name;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Name.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
8
Lantean.QBTMudBlade/Models/ContentItemType.cs
Normal file
8
Lantean.QBTMudBlade/Models/ContentItemType.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Lantean.QBTMudBlade.Models
|
||||
{
|
||||
public enum ContentItemType
|
||||
{
|
||||
File,
|
||||
Folder
|
||||
}
|
||||
}
|
46
Lantean.QBTMudBlade/Models/GlobalTransferInfo.cs
Normal file
46
Lantean.QBTMudBlade/Models/GlobalTransferInfo.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
namespace Lantean.QBTMudBlade.Models
|
||||
{
|
||||
public record GlobalTransferInfo
|
||||
{
|
||||
public GlobalTransferInfo(
|
||||
string connectionStatus,
|
||||
int dHTNodes,
|
||||
long downloadInfoData,
|
||||
long downloadInfoSpeed,
|
||||
long downloadRateLimit,
|
||||
long uploadInfoData,
|
||||
long uploadInfoSpeed,
|
||||
long uploadRateLimit)
|
||||
{
|
||||
ConnectionStatus = connectionStatus;
|
||||
DHTNodes = dHTNodes;
|
||||
DownloadInfoData = downloadInfoData;
|
||||
DownloadInfoSpeed = downloadInfoSpeed;
|
||||
DownloadRateLimit = downloadRateLimit;
|
||||
UploadInfoData = uploadInfoData;
|
||||
UploadInfoSpeed = uploadInfoSpeed;
|
||||
UploadRateLimit = uploadRateLimit;
|
||||
}
|
||||
|
||||
public GlobalTransferInfo()
|
||||
{
|
||||
ConnectionStatus = "Unknown";
|
||||
}
|
||||
|
||||
public string ConnectionStatus { get; set; }
|
||||
|
||||
public int DHTNodes { get; set; }
|
||||
|
||||
public long DownloadInfoData { get; set; }
|
||||
|
||||
public long DownloadInfoSpeed { get; set; }
|
||||
|
||||
public long DownloadRateLimit { get; set; }
|
||||
|
||||
public long UploadInfoData { get; set; }
|
||||
|
||||
public long UploadInfoSpeed { get; set; }
|
||||
|
||||
public long UploadRateLimit { get; set; }
|
||||
}
|
||||
}
|
40
Lantean.QBTMudBlade/Models/MainData.cs
Normal file
40
Lantean.QBTMudBlade/Models/MainData.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
namespace Lantean.QBTMudBlade.Models
|
||||
{
|
||||
public record MainData
|
||||
{
|
||||
public MainData(
|
||||
IDictionary<string, Torrent> torrents,
|
||||
IEnumerable<string> tags,
|
||||
IDictionary<string, Category> categories,
|
||||
IDictionary<string, IReadOnlyList<string>> trackers,
|
||||
ServerState serverState,
|
||||
Dictionary<string, HashSet<string>> tagState,
|
||||
Dictionary<string, HashSet<string>> categoriesState,
|
||||
Dictionary<string, HashSet<string>> statusState,
|
||||
Dictionary<string, HashSet<string>> trackersState)
|
||||
{
|
||||
Torrents = torrents.ToDictionary();
|
||||
Tags = tags.ToHashSet();
|
||||
Categories = categories.ToDictionary();
|
||||
Trackers = trackers.ToDictionary();
|
||||
ServerState = serverState;
|
||||
TagState = tagState;
|
||||
CategoriesState = categoriesState;
|
||||
StatusState = statusState;
|
||||
TrackersState = trackersState;
|
||||
}
|
||||
|
||||
public Dictionary<string, Torrent> Torrents { get; }
|
||||
public HashSet<string> Tags { get; }
|
||||
public Dictionary<string, Category> Categories { get; }
|
||||
public Dictionary<string, IReadOnlyList<string>> Trackers { get; }
|
||||
public ServerState ServerState { get; }
|
||||
|
||||
public Dictionary<string, HashSet<string>> TagState { get; }
|
||||
public Dictionary<string, HashSet<string>> CategoriesState { get; }
|
||||
public Dictionary<string, HashSet<string>> StatusState { get; }
|
||||
public Dictionary<string, HashSet<string>> TrackersState { get; }
|
||||
public string? SelectedTorrentHash { get; set; }
|
||||
public bool LostConnection { get; set; }
|
||||
}
|
||||
}
|
72
Lantean.QBTMudBlade/Models/Peer.cs
Normal file
72
Lantean.QBTMudBlade/Models/Peer.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
namespace Lantean.QBTMudBlade.Models
|
||||
{
|
||||
public class Peer
|
||||
{
|
||||
public Peer(
|
||||
string ip,
|
||||
string client,
|
||||
string clientId,
|
||||
string connection,
|
||||
string country,
|
||||
string countryCode,
|
||||
long downloaded,
|
||||
long downloadSpeed,
|
||||
string files,
|
||||
string flags,
|
||||
string flagsDescription,
|
||||
string iPAddress,
|
||||
int port,
|
||||
float progress,
|
||||
float relevance,
|
||||
long uploaded,
|
||||
long uploadSpeed)
|
||||
{
|
||||
IP = ip;
|
||||
Client = client;
|
||||
ClientId = clientId;
|
||||
Connection = connection;
|
||||
Country = country;
|
||||
CountryCode = countryCode;
|
||||
Downloaded = downloaded;
|
||||
DownloadSpeed = downloadSpeed;
|
||||
Files = files;
|
||||
Flags = flags;
|
||||
FlagsDescription = flagsDescription;
|
||||
IPAddress = iPAddress;
|
||||
Port = port;
|
||||
Progress = progress;
|
||||
Relevance = relevance;
|
||||
Uploaded = uploaded;
|
||||
UploadSpeed = uploadSpeed;
|
||||
}
|
||||
|
||||
public string IP { get; }
|
||||
public string Client { get; set; }
|
||||
public string ClientId { get; set; }
|
||||
public string Connection { get; set; }
|
||||
public string Country { get; set; }
|
||||
public string CountryCode { get; set; }
|
||||
public long Downloaded { get; set; }
|
||||
public long DownloadSpeed { get; set; }
|
||||
public string Files { get; set; }
|
||||
public string Flags { get; set; }
|
||||
public string FlagsDescription { get; set; }
|
||||
public string IPAddress { get; set; }
|
||||
public int Port { get; set; }
|
||||
public float Progress { get; set; }
|
||||
public float Relevance { get; set; }
|
||||
public long Uploaded { get; set; }
|
||||
public long UploadSpeed { get; set; }
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is null) return false;
|
||||
return ((Peer)obj).IP == IP;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return IP.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user