35 Commits

Author SHA1 Message Date
ahjephson
1fc62adfaa Merge remote-tracking branch 'origin/develop' into feature/new-options 2025-10-18 16:18:43 +01:00
ahjephson
d4ac79af00 Merge pull request #10 from lantean-code/feature/bugfixes
- Fixed an issue where the tag wasn't being correctly applied to the filter in qBittorrent 5.1+ (#9)
- Fixed an issue where the category wasn't being applied to the filter correctly (#9)
- Fixed invalid ValueChanged for "Default Torrent Management Mode"
- Fixed a crash where TimeSpan.FromSeconds was crashing
- Fixed an invalid icon to appear when Paused/Stopped
2025-10-18 16:18:10 +01:00
ahjephson
7370d73c59 Fix minor display issues 2025-10-18 16:01:53 +01:00
ahjephson
8796cc0f24 Fix #9 and bug related to invalid TimeSpan in duration 2025-10-18 15:37:04 +01:00
ahjephson
9cb6643606 Add new options 2025-10-18 14:31:00 +01:00
ahjephson
b24ae440d4 Merge pull request #8 from ehaughee/develop
Fix MaxRatio to allow float values
2025-10-02 15:01:10 +01:00
Eric Haughee
bb90ce5216 Fix MaxRatio to allow float values 2025-09-20 18:29:18 -07:00
ahjephson
1cf9f97187 Merge tag '1.1.0' into develop
1.1.0
2025-05-30 15:46:03 +01:00
ahjephson
4f9129fd46 Merge branch 'release/1.1.0' 2025-05-30 15:45:32 +01:00
ahjephson
9a9d2c2ee2 Update packages 2025-05-30 15:43:22 +01:00
ahjephson
736bc46745 Merge pull request #2 from lantean-code/feature/fix-statuses
Fix Paused/Stopped Duplicate
2025-05-30 14:19:34 +01:00
ahjephson
23ae19c4c7 Update readme.md 2025-05-20 13:35:59 +01:00
ahjephson
603470eb30 Merge pull request #3 from lantean-code/feature/fix-relative-resources
Fix Reverse Proxy Issue
2025-05-20 13:24:53 +01:00
ahjephson
27c2406340 Fixes #1 2025-04-22 14:08:55 +01:00
ahjephson
4578dcc11f FIx issue with duplicate paused/stopped status lists when handling v4/5 differences 2025-04-22 14:03:33 +01:00
ahjephson
3215fa3936 Merge tag '1.0.2' into develop
1.0.2
2025-03-22 13:52:41 +00:00
ahjephson
78e62f31d0 Merge branch 'hotfix/1.0.2' 2025-03-22 13:52:33 +00:00
ahjephson
e23842fcb0 Fix invalid exception being caught. 2025-03-22 13:51:44 +00:00
ahjephson
411c7f87cc Merge tag '1.0.1' into develop
1.0.1
2025-02-10 08:57:20 +00:00
ahjephson
4098f8f5a9 Merge branch 'hotfix/1.0.1' 2025-02-10 08:57:01 +00:00
ahjephson
12f81c5978 Fix issue with TorrentActions treating actions as all downloaded. 2025-02-10 08:55:46 +00:00
ahjephson
717738d720 Update readme.md 2025-02-07 13:24:25 +00:00
ahjephson
885c34c8cf Update readme.md 2025-02-07 13:10:15 +00:00
ahjephson
ef3c68a6aa Merge tag '1.0.0' into develop
1.0.0
2025-02-07 13:02:16 +00:00
ahjephson
a29e64fc1b Merge branch 'release/1.0.0' 2025-02-07 13:01:36 +00:00
ahjephson
e55955c75e Fix small screen issues 2025-02-07 11:49:37 +00:00
ahjephson
aa80396862 Update dotnet.yml 2025-02-07 09:53:01 +00:00
ahjephson
30ced3293c Update dotnet.yml 2025-02-07 09:52:18 +00:00
ahjephson
c54f73a517 Merge tag '0.2.0' into develop
0.2.0
2025-02-07 09:49:18 +00:00
ahjephson
bad509e40f Merge branch 'release/0.2.0' 2025-02-07 09:48:56 +00:00
ahjephson
6a0796ef20 Update actions to build .net 9 2025-02-07 09:46:14 +00:00
ahjephson
dc4b515763 Fix styling issues with torrent list
Only display errors in debug mode
Add column sorting
2025-02-07 09:23:54 +00:00
ahjephson
938702a7b3 Partial .net9 upgrade 2025-02-04 13:58:24 +00:00
ahjephson
6ca1c6edd4 Update to net9.0 2025-01-07 09:18:45 +00:00
ahjephson
24eb5cf5e9 Merge tag '0.1.0' into develop
0.1.0
2024-11-02 13:46:03 +00:00
75 changed files with 1075 additions and 461 deletions

View File

@@ -21,12 +21,12 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
dotnet-version: '9.0.x'
- name: Install GitVersion
uses: gittools/actions/gitversion/setup@v3.0.0
with:
versionSpec: '6.x'
versionSpec: '6.0.0'
- name: Determine Version
id: gitversion

View File

@@ -1,24 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="AwesomeAssertions" Version="9.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Net.Http" Version="4.3.4" />
</ItemGroup>
<ItemGroup>

View File

@@ -21,7 +21,7 @@ namespace Lantean.QBTMud.Test
Test2(a => a.Name);
}
private void Test2(Expression<Func<TestClass, object>> expr)
private void Test2(Expression<Func<TestClass, object?>> expr)
{
var body = expr.Body;
}
@@ -38,7 +38,7 @@ namespace Lantean.QBTMud.Test
var l = Expression.Lambda<Func<TestClass, object>>(convertExpression, expression);
Expression<Func<TestClass, object>> expr2 = a => a.Name;
Expression<Func<TestClass, object?>> expr2 = a => a.Name;
var x = l.Compile();
var res = (long)x(new TestClass { Name = "Name", Value = 12 });
@@ -58,9 +58,9 @@ namespace Lantean.QBTMud.Test
public class TestClass
{
public string Name { get; set; }
public string? Name { get; set; }
public string Description { get; set; }
public string? Description { get; set; }
public long Value { get; set; }
}

View File

@@ -8,7 +8,7 @@ namespace Lantean.QBTMud.Components.Dialogs
public partial class AddPeerDialog
{
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
public IMudDialogInstance MudDialog { get; set; } = default!;
protected HashSet<PeerId> Peers { get; } = [];

View File

@@ -14,7 +14,7 @@ namespace Lantean.QBTMud.Components.Dialogs
protected IDialogService DialogService { get; set; } = default!;
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
IMudDialogInstance MudDialog { get; set; } = default!;
protected HashSet<string> Tags { get; } = [];

View File

@@ -8,7 +8,7 @@ namespace Lantean.QBTMud.Components.Dialogs
public partial class AddTorrentFileDialog
{
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
IMudDialogInstance MudDialog { get; set; } = default!;
protected IReadOnlyList<IBrowserFile> Files { get; set; } = [];

View File

@@ -18,7 +18,7 @@ namespace Lantean.QBTMud.Components.Dialogs
protected IKeyboardService KeyboardService { get; set; } = default!;
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
IMudDialogInstance MudDialog { get; set; } = default!;
[Parameter]
public string? Url { get; set; }

View File

@@ -1,6 +1,6 @@
<MudGrid>
<MudItem xs="12">
<MudSwitch Label="Additional Options" @bind-Value="Expanded" LabelPosition="LabelPosition.End" />
<MudSwitch Label="Additional Options" @bind-Value="Expanded" LabelPlacement="Placement.End" />
</MudItem>
</MudGrid>
<MudCollapse Expanded="Expanded">

View File

@@ -1,6 +1,7 @@
using Lantean.QBitTorrentClient;
using Lantean.QBTMud.Models;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace Lantean.QBTMud.Components.Dialogs
{

View File

@@ -7,7 +7,7 @@ namespace Lantean.QBTMud.Components.Dialogs
public partial class AddTrackerDialog
{
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
IMudDialogInstance MudDialog { get; set; } = default!;
protected HashSet<string> Trackers { get; } = [];

View File

@@ -10,7 +10,7 @@ namespace Lantean.QBTMud.Components.Dialogs
private string _savePath = string.Empty;
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
IMudDialogInstance MudDialog { get; set; } = default!;
[Inject]
protected IApiClient ApiClient { get; set; } = default!;

View File

@@ -5,12 +5,13 @@
<DialogContent>
<MudCard Class="w-100" Elevation="0">
<MudGrid>
@for (var i = 0; i < Columns.Count; i++)
@for (var i = 0; i < OrderedColumns.Length; i++)
{
var column = Columns[i];
var item = OrderedColumns[i];
var column = Columns.First(c => c.Id == item);
var index = i;
<MudItem xs="7">
<MudCheckBox T="bool" ValueChanged="@(c => SetSelected(c, column.Id))" Label="@column.Header" LabelPosition="LabelPosition.End" Value="@(SelectedColumnsInternal.Contains(column.Id))" />
<MudCheckBox T="bool" ValueChanged="@(c => SetSelected(c, column.Id))" Label="@column.Header" LabelPlacement="Placement.End" Value="@(SelectedColumnsInternal.Contains(column.Id))" />
</MudItem>
<MudItem xs="3">
<MudTextField T="string" Value="@(GetValue(column.Width, column.Id))" ValueChanged="@(c => SetWidth(c, column.Id))" Label="Width" Variant="Variant.Text" HelperText="px" Adornment="Adornment.End" AdornmentIcon="@Icons.Material.Outlined.WidthNormal" OnAdornmentClick="@(c => SetWidth("auto", column.Id))" />

View File

@@ -7,7 +7,7 @@ namespace Lantean.QBTMud.Components.Dialogs
public partial class ColumnOptionsDialog<T>
{
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
private IMudDialogInstance MudDialog { get; set; } = default!;
[Parameter]
[EditorRequired]
@@ -20,10 +20,15 @@ namespace Lantean.QBTMud.Components.Dialogs
[Parameter]
public Dictionary<string, int?> Widths { get; set; } = [];
[Parameter]
public Dictionary<string, int> Order { get; set; } = [];
protected HashSet<string> SelectedColumnsInternal { get; set; } = [];
protected Dictionary<string, int?> WidthsInternal { get; set; } = [];
protected Dictionary<string, int> OrderInternal { get; set; } = [];
protected override void OnParametersSet()
{
if (SelectedColumnsInternal.Count == 0)
@@ -51,6 +56,25 @@ namespace Lantean.QBTMud.Components.Dialogs
WidthsInternal[width.Key] = width.Value;
}
}
if (OrderInternal.Count == 0)
{
if (Order.Count == 0)
{
for (int i = 0; i < Columns.Count; i++)
{
var column = Columns[i];
OrderInternal.Add(column.Id, i);
}
}
else
{
foreach (var order in Order)
{
OrderInternal[order.Key] = order.Value;
}
}
}
}
protected void SetSelected(bool selected, string id)
@@ -101,7 +125,15 @@ namespace Lantean.QBTMud.Components.Dialogs
return;
}
(Columns[index], Columns[index - 1]) = (Columns[index - 1], Columns[index]);
var currentId = OrderInternal.FirstOrDefault(o => o.Value == index).Key;
var otherId = OrderInternal.FirstOrDefault(o => o.Value == index - 1).Key;
OrderInternal[otherId] = index;
OrderInternal[currentId] = index - 1;
//(Columns[index], Columns[index - 1]) = (Columns[index - 1], Columns[index]);
StateHasChanged();
}
protected void MoveDown(int index)
@@ -111,7 +143,15 @@ namespace Lantean.QBTMud.Components.Dialogs
return;
}
(Columns[index], Columns[index + 1]) = (Columns[index + 1], Columns[index]);
var currentId = OrderInternal.FirstOrDefault(o => o.Value == index).Key;
var otherId = OrderInternal.FirstOrDefault(o => o.Value == index + 1).Key;
OrderInternal[otherId] = index;
OrderInternal[currentId] = index + 1;
//(Columns[index], Columns[index + 1]) = (Columns[index + 1], Columns[index]);
StateHasChanged();
}
protected string GetValue(int? value, string columnId)
@@ -134,6 +174,13 @@ namespace Lantean.QBTMud.Components.Dialogs
return value.Value.ToString();
}
private string[] OrderedColumns => GetOrderedColumns();
private string[] GetOrderedColumns()
{
return OrderInternal.OrderBy(x => x.Value).Select(x => x.Key).ToArray();
}
protected void Cancel()
{
MudDialog.Cancel();
@@ -141,7 +188,7 @@ namespace Lantean.QBTMud.Components.Dialogs
protected void Submit()
{
MudDialog.Close(DialogResult.Ok((SelectedColumnsInternal, WidthsInternal)));
MudDialog.Close(DialogResult.Ok((SelectedColumnsInternal, WidthsInternal, OrderInternal)));
}
protected override Task Submit(KeyboardEvent keyboardEvent)

View File

@@ -7,7 +7,7 @@ namespace Lantean.QBTMud.Components.Dialogs
public partial class ConfirmDialog
{
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
IMudDialogInstance MudDialog { get; set; } = default!;
[Parameter]
public string Content { get; set; } = default!;

View File

@@ -6,7 +6,7 @@
<MudGrid>
<MudItem xs="12">
<MudCheckBox Label="Also permanently delete the files" @bind-Value="DeleteFiles" LabelPosition="LabelPosition.End" />
<MudCheckBox Label="Also permanently delete the files" @bind-Value="DeleteFiles" LabelPlacement="Placement.End" />
</MudItem>
</MudGrid>
</DialogContent>

View File

@@ -7,7 +7,7 @@ namespace Lantean.QBTMud.Components.Dialogs
public partial class DeleteDialog
{
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
IMudDialogInstance MudDialog { get; set; } = default!;
[Parameter]
public int Count { get; set; }

View File

@@ -6,7 +6,7 @@ namespace Lantean.QBTMud.Components.Dialogs
public partial class ExceptionDialog
{
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
IMudDialogInstance MudDialog { get; set; } = default!;
[Parameter]
public Exception? Exception { get; set; }

View File

@@ -11,7 +11,7 @@ namespace Lantean.QBTMud.Components.Dialogs
private static readonly IReadOnlyList<PropertyInfo> _properties = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
IMudDialogInstance MudDialog { get; set; } = default!;
protected IReadOnlyList<PropertyInfo> Columns => _properties;

View File

@@ -14,7 +14,7 @@ namespace Lantean.QBTMud.Components.Dialogs
protected IDialogService DialogService { get; set; } = default!;
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
IMudDialogInstance MudDialog { get; set; } = default!;
[Parameter]
public IEnumerable<string> Hashes { get; set; } = [];

View File

@@ -14,7 +14,7 @@ namespace Lantean.QBTMud.Components.Dialogs
protected IDialogService DialogService { get; set; } = default!;
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
IMudDialogInstance MudDialog { get; set; } = default!;
[Parameter]
public IEnumerable<string> Hashes { get; set; } = [];

View File

@@ -7,7 +7,7 @@ namespace Lantean.QBTMud.Components.Dialogs
public partial class MultipleFieldDialog
{
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
IMudDialogInstance MudDialog { get; set; } = default!;
[Parameter]
public string Label { get; set; } = default!;

View File

@@ -8,7 +8,7 @@ namespace Lantean.QBTMud.Components.Dialogs
public partial class NumericFieldDialog<T> where T : struct, INumber<T>
{
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
IMudDialogInstance MudDialog { get; set; } = default!;
[Parameter]
public string? Label { get; set; }

View File

@@ -6,7 +6,6 @@ using Lantean.QBTMud.Services;
using Microsoft.AspNetCore.Components;
using MudBlazor;
using System.Collections.ObjectModel;
using static MudBlazor.Colors;
namespace Lantean.QBTMud.Components.Dialogs
{
@@ -31,7 +30,7 @@ namespace Lantean.QBTMud.Components.Dialogs
protected ILocalStorageService LocalStorage { get; set; } = default!;
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
IMudDialogInstance MudDialog { get; set; } = default!;
[Parameter]
public string? Hash { get; set; }

View File

@@ -10,7 +10,7 @@ namespace Lantean.QBTMud.Components.Dialogs
private readonly List<string> _unsavedRuleNames = [];
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
IMudDialogInstance MudDialog { get; set; } = default!;
[Inject]
protected IDialogService DialogService { get; set; } = default!;

View File

@@ -8,7 +8,7 @@ namespace Lantean.QBTMud.Components.Dialogs
public partial class ShareRatioDialog
{
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
IMudDialogInstance MudDialog { get; set; } = default!;
[Parameter]
public string? Label { get; set; }

View File

@@ -8,7 +8,7 @@ namespace Lantean.QBTMud.Components.Dialogs
public partial class SliderFieldDialog<T> where T : struct, INumber<T>
{
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
IMudDialogInstance MudDialog { get; set; } = default!;
[Parameter]
public string? Label { get; set; }

View File

@@ -7,7 +7,7 @@ namespace Lantean.QBTMud.Components.Dialogs
public partial class StringFieldDialog
{
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
IMudDialogInstance MudDialog { get; set; } = default!;
[Parameter]
public string? Label { get; set; }

View File

@@ -7,7 +7,7 @@ namespace Lantean.QBTMud.Components.Dialogs
public partial class SubMenuDialog
{
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
IMudDialogInstance MudDialog { get; set; } = default!;
[Parameter]
public UIAction? ParentAction { get; set; }

View File

@@ -7,7 +7,7 @@ namespace Lantean.QBTMud.Components.Dialogs
public partial class TorrentOptionsDialog
{
[CascadingParameter]
public MudDialogInstance MudDialog { get; set; } = default!;
IMudDialogInstance MudDialog { get; set; } = default!;
[Parameter]
[EditorRequired]

View File

@@ -2,6 +2,7 @@
<MudMenuItem Icon="@Icons.Material.Filled.DriveFileRenameOutline" OnClick="RenameFileContextMenu">Rename</MudMenuItem>
</ContextMenu>
<div style="overflow-x: auto; white-space: nowrap; width: 100%;">
<MudToolBar Gutters="false" Dense="true">
<MudIconButton Icon="@Icons.Material.Filled.DriveFileRenameOutline" OnClick="RenameFileToolbar" title="Rename" />
<MudDivider Vertical="true" />
@@ -22,6 +23,7 @@
<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>
</MudToolBar>
</div>
<DynamicTable
@ref="Table"

View File

@@ -1,4 +1,4 @@
<ContextMenu @ref="StatusContextMenu" Dense="true" AdjustmentY="-60">
<ContextMenu @ref="StatusContextMenu" Dense="true" AdjustmentY="-60">
@TorrentControls(_statusType)
</ContextMenu>

View File

@@ -1,4 +1,4 @@
@inherits Options
@inherits Options
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
<MudCardHeader>
@@ -15,7 +15,7 @@
</MudSelect>
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Physical memory (RAM) usage limit (applied if libtorrent &gt;= 2.0)" Value="MemoryWorkingSetLimit" ValueChanged="MemoryWorkingSetLimitChanged" Min="0" HelperText="This option is less effective on Linux" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="MiB" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Physical memory (RAM) usage limit (applied if libtorrent &gt;= 2.0)" Value="MemoryWorkingSetLimit" ValueChanged="MemoryWorkingSetLimitChanged" Min="0" HelperText="This option is less effective on Linux" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="MiB" />
</MudItem>
<MudItem xs="12">
<MudSelect T="string" Label="Network interface" Value="CurrentNetworkInterface" ValueChanged="CurrentNetworkInterfaceChanged" Variant="Variant.Outlined">
@@ -38,16 +38,19 @@
</MudSelect>
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Save resume data interval" Value="SaveResumeDataInterval" ValueChanged="SaveResumeDataIntervalChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="min" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Save resume data interval" Value="SaveResumeDataInterval" ValueChanged="SaveResumeDataIntervalChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="min" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label=".torrent file size limit" Value="TorrentFileSizeLimit" ValueChanged="TorrentFileSizeLimitChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="MiB" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Save transfer statistics interval" Value="SaveStatisticsInterval" ValueChanged="SaveStatisticsIntervalChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="min" />
</MudItem>
<MudItem xs="12">
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label=".torrent file size limit" Value="TorrentFileSizeLimit" ValueChanged="TorrentFileSizeLimitChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="MiB" />
</MudItem>
<MudItem xs="12">
<FieldSwitch Label="Recheck torrents on completion" Value="RecheckCompletedTorrents" ValueChanged="RecheckCompletedTorrentsChanged" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Refresh interval" Value="RefreshInterval" ValueChanged="RefreshIntervalChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="ms" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Refresh interval" Value="RefreshInterval" ValueChanged="RefreshIntervalChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="ms" />
</MudItem>
<MudItem xs="12">
<FieldSwitch Label="Resolve peer countries" Value="ResolvePeerCountries" ValueChanged="ResolvePeerCountriesChanged" />
@@ -59,7 +62,7 @@
<FieldSwitch Label="Enable embedded tracker" Value="EnableEmbeddedTracker" ValueChanged="EnableEmbeddedTrackerChanged" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Embedded tracker port" Value="EmbeddedTrackerPort" ValueChanged="EmbeddedTrackerPortChanged" Min="@Options.MinPortValue" Max="@Options.MaxPortValue" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Embedded tracker port" Value="EmbeddedTrackerPort" ValueChanged="EmbeddedTrackerPortChanged" Min="@Options.MinPortValue" Max="@Options.MaxPortValue" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<FieldSwitch Label="Enable port forwarding for embedded tracker" Value="EmbeddedTrackerPortForwarding" ValueChanged="EmbeddedTrackerPortForwardingChanged" />
@@ -77,31 +80,31 @@
<MudCardContent Class="pt-0">
<MudGrid>
<MudItem xs="12">
<MudNumericField T="int" Label="Bdecode depth limit" Value="BdecodeDepthLimit" ValueChanged="BdecodeDepthLimitChanged" Min="0" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Bdecode depth limit" Value="BdecodeDepthLimit" ValueChanged="BdecodeDepthLimitChanged" Min="0" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Bdecode token limit" Value="BdecodeTokenLimit" ValueChanged="BdecodeTokenLimitChanged" Min="0" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Bdecode token limit" Value="BdecodeTokenLimit" ValueChanged="BdecodeTokenLimitChanged" Min="0" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Asynchronous I/O threads" Value="AsyncIoThreads" ValueChanged="AsyncIoThreadsChanged" Min="0" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Asynchronous I/O threads" Value="AsyncIoThreads" ValueChanged="AsyncIoThreadsChanged" Min="0" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Hashing threads (requires libtorrent &gt;= 2.0)" Value="HashingThreads" ValueChanged="HashingThreadsChanged" Min="0" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Hashing threads (requires libtorrent &gt;= 2.0)" Value="HashingThreads" ValueChanged="HashingThreadsChanged" Min="0" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="File pool size" Value="FilePoolSize" ValueChanged="FilePoolSizeChanged" Min="0" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="File pool size" Value="FilePoolSize" ValueChanged="FilePoolSizeChanged" Min="0" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Outstanding memory when checking torrents" Value="CheckingMemoryUse" ValueChanged="CheckingMemoryUseChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="MiB" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Outstanding memory when checking torrents" Value="CheckingMemoryUse" ValueChanged="CheckingMemoryUseChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="MiB" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Disk cache (requires libtorrent &lt; 2.0)" Value="DiskCache" ValueChanged="DiskCacheChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="MiB" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Disk cache (requires libtorrent &lt; 2.0)" Value="DiskCache" ValueChanged="DiskCacheChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="MiB" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Disk cache expiry interval (requires libtorrent &lt; 2.0)" Value="DiskCacheTtl" ValueChanged="DiskCacheTtlChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="s" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Disk cache expiry interval (requires libtorrent &lt; 2.0)" Value="DiskCacheTtl" ValueChanged="DiskCacheTtlChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="s" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Disk queue size" Value="DiskQueueSize" ValueChanged="DiskQueueSizeChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Disk queue size" Value="DiskQueueSize" ValueChanged="DiskQueueSizeChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
</MudItem>
<MudItem xs="12">
<MudSelect T="int" Label="Disk IO type (libtorrent &gt;= 2.0; requires restart)" Value="DiskIoType" ValueChanged="DiskIoTypeChanged" Variant="Variant.Outlined">
@@ -133,40 +136,40 @@
<FieldSwitch Label="Send upload piece suggestions" Value="EnableUploadSuggestions" ValueChanged="EnableUploadSuggestionsChanged" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Send buffer watermark" Value="SendBufferWatermark" ValueChanged="SendBufferWatermarkChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Send buffer watermark" Value="SendBufferWatermark" ValueChanged="SendBufferWatermarkChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Send buffer low watermark" Value="SendBufferLowWatermark" ValueChanged="SendBufferLowWatermarkChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Send buffer low watermark" Value="SendBufferLowWatermark" ValueChanged="SendBufferLowWatermarkChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Send buffer watermark factor" Value="SendBufferWatermarkFactor" ValueChanged="SendBufferWatermarkFactorChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="%" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Send buffer watermark factor" Value="SendBufferWatermarkFactor" ValueChanged="SendBufferWatermarkFactorChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="%" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Outgoing connections per second" Value="ConnectionSpeed" ValueChanged="ConnectionSpeedChanged" Min="0" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Outgoing connections per second" Value="ConnectionSpeed" ValueChanged="ConnectionSpeedChanged" Min="0" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Socket send buffer size [0: system default]" Value="SocketSendBufferSize" ValueChanged="SocketSendBufferSizeChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Socket send buffer size [0: system default]" Value="SocketSendBufferSize" ValueChanged="SocketSendBufferSizeChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Socket receive buffer size [0: system default]" Value="SocketReceiveBufferSize" ValueChanged="SocketReceiveBufferSizeChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Socket receive buffer size [0: system default]" Value="SocketReceiveBufferSize" ValueChanged="SocketReceiveBufferSizeChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Socket backlog size" Value="SocketBacklogSize" ValueChanged="SocketBacklogSizeChanged" Min="0" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Socket backlog size" Value="SocketBacklogSize" ValueChanged="SocketBacklogSizeChanged" Min="0" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Outgoing ports (Min) [0: disabled]" Value="OutgoingPortsMin" ValueChanged="OutgoingPortsMinChanged" Min="0" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Outgoing ports (Min) [0: disabled]" Value="OutgoingPortsMin" ValueChanged="OutgoingPortsMinChanged" Min="0" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Outgoing ports (Max) [0: disabled]" Value="OutgoingPortsMax" ValueChanged="OutgoingPortsMaxChanged" Min="0" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Outgoing ports (Max) [0: disabled]" Value="OutgoingPortsMax" ValueChanged="OutgoingPortsMaxChanged" Min="0" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="UPnP lease duration [0: permanent lease]" Value="UpnpLeaseDuration" ValueChanged="UpnpLeaseDurationChanged" Min="0" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="UPnP lease duration [0: permanent lease]" Value="UpnpLeaseDuration" ValueChanged="UpnpLeaseDurationChanged" Min="0" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Type of service (ToS) for connections to peers" Value="PeerTos" ValueChanged="PeerTosChanged" Min="0" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Type of service (ToS) for connections to peers" Value="PeerTos" ValueChanged="PeerTosChanged" Min="0" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudSelect T="int" Label="μTP-TCP mixed mode algorithm" Value="UtpTcpMixedMode" ValueChanged="UtpTcpMixedModeChanged" Variant="Variant.Outlined">
<MudSelect T="int" Label="<EFBFBD>TP-TCP mixed mode algorithm" Value="UtpTcpMixedMode" ValueChanged="UtpTcpMixedModeChanged" Variant="Variant.Outlined">
<MudSelectItem T="int" Value="0">Prefer TCP</MudSelectItem>
<MudSelectItem T="int" Value="1">Peer proportional (throttles TCP)</MudSelectItem>
</MudSelect>
@@ -180,6 +183,9 @@
<MudItem xs="12">
<FieldSwitch Label="Validate HTTPS tracker certificate" Value="ValidateHttpsTrackerCertificate" ValueChanged="ValidateHttpsTrackerCertificateChanged" />
</MudItem>
<MudItem xs="12">
<FieldSwitch Label="Ignore SSL errors" Value="IgnoreSslErrors" ValueChanged="IgnoreSslErrorsChanged" />
</MudItem>
<MudItem xs="12">
<FieldSwitch Label="Server-side request forgery (SSRF) mitigation" Value="SsrfMitigation" ValueChanged="SsrfMitigationChanged" />
</MudItem>
@@ -206,38 +212,47 @@
<FieldSwitch Label="Always announce to all tiers" Value="AnnounceToAllTiers" ValueChanged="AnnounceToAllTiersChanged" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="IP address reported to trackers (requires restart)" Value="AnnounceIp" ValueChanged="AnnounceIpChanged" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="IP address reported to trackers (requires restart)" Value="AnnounceIp" ValueChanged="AnnounceIpChanged" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Max concurrent HTTP announces" Value="MaxConcurrentHttpAnnounces" ValueChanged="MaxConcurrentHttpAnnouncesChanged" Min="0" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Port reported to trackers (requires restart)" Value="AnnouncePort" ValueChanged="AnnouncePortChanged" Min="0" Max="65535" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Stop tracker timeout [0: disabled]" Value="StopTrackerTimeout" ValueChanged="StopTrackerTimeoutChanged" Min="0" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Max concurrent HTTP announces" Value="MaxConcurrentHttpAnnounces" ValueChanged="MaxConcurrentHttpAnnouncesChanged" Min="0" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Peer turnover disconnect percentage:" Value="PeerTurnover" ValueChanged="PeerTurnoverChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="%" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Stop tracker timeout [0: disabled]" Value="StopTrackerTimeout" ValueChanged="StopTrackerTimeoutChanged" Min="0" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Peer turnover threshold percentage" Value="PeerTurnoverCutoff" ValueChanged="PeerTurnoverCutoffChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="%" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Peer turnover disconnect percentage:" Value="PeerTurnover" ValueChanged="PeerTurnoverChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="%" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Peer turnover disconnect interval" Value="PeerTurnoverInterval" ValueChanged="PeerTurnoverIntervalChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="s" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Peer turnover threshold percentage" Value="PeerTurnoverCutoff" ValueChanged="PeerTurnoverCutoffChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="%" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Maximum outstanding requests to a single peer" Value="RequestQueueSize" ValueChanged="RequestQueueSizeChanged" Min="0" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Peer turnover disconnect interval" Value="PeerTurnoverInterval" ValueChanged="PeerTurnoverIntervalChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="s" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="I2P inbound quantity (requires libtorrent &gt;= 2.0)" Value="I2pInboundQuantity" ValueChanged="I2pInboundQuantityChanged" Min="0" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Hostname lookup cache TTL" Value="HostnameCacheTtl" ValueChanged="HostnameCacheTtlChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="s" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="I2P outbound quantity (requires libtorrent &gt;= 2.0)" Value="I2pOutboundQuantity" ValueChanged="I2pOutboundQuantityChanged" Min="0" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Maximum outstanding requests to a single peer" Value="RequestQueueSize" ValueChanged="RequestQueueSizeChanged" Min="0" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="I2P inbound length (requires libtorrent &gt;= 2.0)" Value="I2pInboundLength" ValueChanged="I2pInboundLengthChanged" Min="0" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="I2P inbound quantity (requires libtorrent &gt;= 2.0)" Value="I2pInboundQuantity" ValueChanged="I2pInboundQuantityChanged" Min="0" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="I2P outbound length (requires libtorrent &gt;= 2.0)" Value="I2pOutboundLength" ValueChanged="I2pOutboundLengthChanged" Min="0" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="I2P outbound quantity (requires libtorrent &gt;= 2.0)" Value="I2pOutboundQuantity" ValueChanged="I2pOutboundQuantityChanged" Min="0" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="I2P inbound length (requires libtorrent &gt;= 2.0)" Value="I2pInboundLength" ValueChanged="I2pInboundLengthChanged" Min="0" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="I2P outbound length (requires libtorrent &gt;= 2.0)" Value="I2pOutboundLength" ValueChanged="I2pOutboundLengthChanged" Min="0" Variant="Variant.Outlined" />
</MudItem>
</MudGrid>
</MudCardContent>
</MudCard>

View File

@@ -14,6 +14,7 @@ namespace Lantean.QBTMud.Components.Options
protected string? CurrentNetworkInterface { get; private set; }
protected string? CurrentInterfaceAddress { get; private set; }
protected int SaveResumeDataInterval { get; private set; }
protected int SaveStatisticsInterval { get; private set; }
protected int TorrentFileSizeLimit { get; private set; }
protected bool RecheckCompletedTorrents { get; private set; }
protected string? AppInstanceName { get; private set; }
@@ -50,6 +51,7 @@ namespace Lantean.QBTMud.Components.Options
protected bool IdnSupportEnabled { get; private set; }
protected bool EnableMultiConnectionsFromSameIp { get; private set; }
protected bool ValidateHttpsTrackerCertificate { get; private set; }
protected bool IgnoreSslErrors { get; private set; }
protected bool SsrfMitigation { get; private set; }
protected bool BlockPeersOnPrivilegedPorts { get; private set; }
protected bool EnableEmbeddedTracker { get; private set; }
@@ -62,11 +64,13 @@ namespace Lantean.QBTMud.Components.Options
protected bool AnnounceToAllTrackers { get; private set; }
protected bool AnnounceToAllTiers { get; private set; }
protected string? AnnounceIp { get; private set; }
protected int AnnouncePort { get; private set; }
protected int MaxConcurrentHttpAnnounces { get; private set; }
protected int StopTrackerTimeout { get; private set; }
protected int PeerTurnover { get; private set; }
protected int PeerTurnoverCutoff { get; private set; }
protected int PeerTurnoverInterval { get; private set; }
protected int HostnameCacheTtl { get; private set; }
protected int RequestQueueSize { get; private set; }
protected string? DhtBootstrapNodes { get; private set; }
protected int I2pInboundQuantity { get; private set; }
@@ -95,6 +99,7 @@ namespace Lantean.QBTMud.Components.Options
CurrentNetworkInterface = Preferences.CurrentNetworkInterface;
CurrentInterfaceAddress = Preferences.CurrentInterfaceAddress;
SaveResumeDataInterval = Preferences.SaveResumeDataInterval;
SaveStatisticsInterval = Preferences.SaveStatisticsInterval;
TorrentFileSizeLimit = Preferences.TorrentFileSizeLimit / 1024 / 1024;
RecheckCompletedTorrents = Preferences.RecheckCompletedTorrents;
AppInstanceName = Preferences.AppInstanceName;
@@ -131,6 +136,7 @@ namespace Lantean.QBTMud.Components.Options
IdnSupportEnabled = Preferences.IdnSupportEnabled;
EnableMultiConnectionsFromSameIp = Preferences.EnableMultiConnectionsFromSameIp;
ValidateHttpsTrackerCertificate = Preferences.ValidateHttpsTrackerCertificate;
IgnoreSslErrors = Preferences.IgnoreSslErrors;
SsrfMitigation = Preferences.SsrfMitigation;
BlockPeersOnPrivilegedPorts = Preferences.BlockPeersOnPrivilegedPorts;
EnableEmbeddedTracker = Preferences.EnableEmbeddedTracker;
@@ -143,11 +149,13 @@ namespace Lantean.QBTMud.Components.Options
AnnounceToAllTrackers = Preferences.AnnounceToAllTrackers;
AnnounceToAllTiers = Preferences.AnnounceToAllTiers;
AnnounceIp = Preferences.AnnounceIp;
AnnouncePort = Preferences.AnnouncePort;
MaxConcurrentHttpAnnounces = Preferences.MaxConcurrentHttpAnnounces;
StopTrackerTimeout = Preferences.StopTrackerTimeout;
PeerTurnover = Preferences.PeerTurnover;
PeerTurnoverCutoff = Preferences.PeerTurnoverCutoff;
PeerTurnoverInterval = Preferences.PeerTurnoverInterval;
HostnameCacheTtl = Preferences.HostnameCacheTtl;
RequestQueueSize = Preferences.RequestQueueSize;
DhtBootstrapNodes = Preferences.DhtBootstrapNodes;
I2pInboundQuantity = Preferences.I2pInboundQuantity;
@@ -195,6 +203,13 @@ namespace Lantean.QBTMud.Components.Options
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
protected async Task SaveStatisticsIntervalChanged(int value)
{
SaveStatisticsInterval = value;
UpdatePreferences.SaveStatisticsInterval = value;
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
protected async Task TorrentFileSizeLimitChanged(int value)
{
TorrentFileSizeLimit = value;
@@ -447,6 +462,13 @@ namespace Lantean.QBTMud.Components.Options
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
protected async Task IgnoreSslErrorsChanged(bool value)
{
IgnoreSslErrors = value;
UpdatePreferences.IgnoreSslErrors = value;
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
protected async Task SsrfMitigationChanged(bool value)
{
SsrfMitigation = value;
@@ -531,6 +553,13 @@ namespace Lantean.QBTMud.Components.Options
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
protected async Task AnnouncePortChanged(int value)
{
AnnouncePort = value;
UpdatePreferences.AnnouncePort = value;
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
protected async Task MaxConcurrentHttpAnnouncesChanged(int value)
{
MaxConcurrentHttpAnnounces = value;
@@ -559,6 +588,13 @@ namespace Lantean.QBTMud.Components.Options
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
protected async Task HostnameCacheTtlChanged(int value)
{
HostnameCacheTtl = value;
UpdatePreferences.HostnameCacheTtl = value;
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
protected async Task PeerTurnoverIntervalChanged(int value)
{
PeerTurnoverInterval = value;
@@ -608,4 +644,4 @@ namespace Lantean.QBTMud.Components.Options
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
}
}
}

View File

@@ -1,4 +1,4 @@
@inherits Options
@inherits Options
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
<MudCardHeader>
@@ -29,13 +29,13 @@
<FieldSwitch Label="Log file" Value="FileLogEnabled" ValueChanged="FileLogEnabledChanged" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="Save Path" Value="FileLogPath" ValueChanged="FileLogPathChanged" Disabled="@(!FileLogEnabled)" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Save Path" Value="FileLogPath" ValueChanged="FileLogPathChanged" Disabled="@(!FileLogEnabled)" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="3">
<FieldSwitch Label="Backup the log after" Value="FileLogBackupEnabled" ValueChanged="FileLogBackupEnabledChanged" Disabled="@(!FileLogEnabled)" />
</MudItem>
<MudItem xs="9">
<MudNumericField T="int" Value="FileLogMaxSize" ValueChanged="FileLogMaxSizeChanged" Disabled="@(!FileLogEnabled)" Min="1" Max="1024000" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Value="FileLogMaxSize" ValueChanged="FileLogMaxSizeChanged" Disabled="@(!FileLogEnabled)" Min="1" Max="1024000" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB" />
</MudItem>
<MudItem xs="3">
<FieldSwitch Label="Delete backups older than" Value="FileLogDeleteOld" ValueChanged="FileLogDeleteOldChanged" Disabled="@(!FileLogEnabled)" />
@@ -43,7 +43,7 @@
<MudItem xs="9">
<MudGrid>
<MudItem xs="9">
<MudNumericField T="int" Value="FileLogAge" ValueChanged="FileLogAgeChanged" Disabled="@(!FileLogEnabled)" Min="1" Max="365" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Value="FileLogAge" ValueChanged="FileLogAgeChanged" Disabled="@(!FileLogEnabled)" Min="1" Max="365" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="3">
<MudSelect T="int" Value="FileLogAgeType" ValueChanged="FileLogAgeTypeChanged" Disabled="@(!FileLogEnabled)" Variant="Variant.Outlined">
@@ -71,4 +71,43 @@
</MudItem>
</MudGrid>
</MudCardContent>
</MudCard>
</MudCard>
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.subtitle2">Status Bar</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudGrid>
<MudItem xs="12">
<FieldSwitch Label="Display current external IP address on status bar" Value="StatusBarExternalIp" ValueChanged="StatusBarExternalIpChanged" />
</MudItem>
</MudGrid>
</MudCardContent>
</MudCard>
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.subtitle2">Confirmation</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudGrid>
<MudItem xs="12">
<FieldSwitch Label="Confirm when deleting torrents" Value="ConfirmTorrentDeletion" ValueChanged="ConfirmTorrentDeletionChanged" />
</MudItem>
<MudItem xs="12">
<FieldSwitch Label="Confirm when rechecking torrents" Value="ConfirmTorrentRecheck" ValueChanged="ConfirmTorrentRecheckChanged" />
</MudItem>
<MudItem xs="12">
<FieldSwitch Label="Also delete torrent content when deleting torrents" Value="DeleteTorrentContentFiles" ValueChanged="DeleteTorrentContentFilesChanged" />
</MudItem>
</MudGrid>
</MudCardContent>
</MudCard>

View File

@@ -20,6 +20,14 @@ namespace Lantean.QBTMud.Components.Options
protected bool PerformanceWarning { get; set; }
protected bool StatusBarExternalIp { get; set; }
protected bool ConfirmTorrentDeletion { get; set; }
protected bool ConfirmTorrentRecheck { get; set; }
protected bool DeleteTorrentContentFiles { get; set; }
protected override bool SetOptions()
{
if (Preferences is null)
@@ -35,6 +43,10 @@ namespace Lantean.QBTMud.Components.Options
FileLogAge = Preferences.FileLogAge;
FileLogAgeType = Preferences.FileLogAgeType;
PerformanceWarning = Preferences.PerformanceWarning;
StatusBarExternalIp = Preferences.StatusBarExternalIp;
ConfirmTorrentDeletion = Preferences.ConfirmTorrentDeletion;
ConfirmTorrentRecheck = Preferences.ConfirmTorrentRecheck;
DeleteTorrentContentFiles = Preferences.DeleteTorrentContentFiles;
return true;
}
@@ -95,5 +107,33 @@ namespace Lantean.QBTMud.Components.Options
UpdatePreferences.PerformanceWarning = value;
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
protected async Task StatusBarExternalIpChanged(bool value)
{
StatusBarExternalIp = value;
UpdatePreferences.StatusBarExternalIp = value;
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
protected async Task ConfirmTorrentDeletionChanged(bool value)
{
ConfirmTorrentDeletion = value;
UpdatePreferences.ConfirmTorrentDeletion = value;
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
protected async Task ConfirmTorrentRecheckChanged(bool value)
{
ConfirmTorrentRecheck = value;
UpdatePreferences.ConfirmTorrentRecheck = value;
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
protected async Task DeleteTorrentContentFilesChanged(bool value)
{
DeleteTorrentContentFiles = value;
UpdatePreferences.DeleteTorrentContentFiles = value;
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
}
}
}

View File

@@ -1,4 +1,4 @@
@inherits Options
@inherits Options
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
<MudCardHeader>
@@ -38,7 +38,7 @@
<MudCardContent Class="pt-0">
<MudGrid>
<MudItem xs="12">
<MudNumericField T="int" Label="Max active checking torrents" Value="MaxActiveCheckingTorrents" ValueChanged="MaxActiveCheckingTorrentsChanged" Min="0" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Max active checking torrents" Value="MaxActiveCheckingTorrents" ValueChanged="MaxActiveCheckingTorrentsChanged" Min="0" Variant="Variant.Outlined" />
</MudItem>
</MudGrid>
</MudCardContent>
@@ -56,25 +56,28 @@
<FieldSwitch Label="Queueing enabled" Value="QueueingEnabled" ValueChanged="QueueingEnabledChanged" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Maximum active downloads" Value="MaxActiveDownloads" ValueChanged="MaxActiveDownloadsChanged" Min="-1" Disabled="@(!QueueingEnabled)" Variant="Variant.Outlined" Validation="MaxActiveDownloadsValidation" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Maximum active downloads" Value="MaxActiveDownloads" ValueChanged="MaxActiveDownloadsChanged" Min="-1" Disabled="@(!QueueingEnabled)" Variant="Variant.Outlined" Validation="MaxActiveDownloadsValidation" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Maximum active uploads" Value="MaxActiveUploads" ValueChanged="MaxActiveUploadsChanged" Min="-1" Disabled="@(!QueueingEnabled)" Variant="Variant.Outlined" Validation="MaxActiveUploadsValidation" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Maximum active uploads" Value="MaxActiveUploads" ValueChanged="MaxActiveUploadsChanged" Min="-1" Disabled="@(!QueueingEnabled)" Variant="Variant.Outlined" Validation="MaxActiveUploadsValidation" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Maximum active torrents" Value="MaxActiveTorrents" ValueChanged="MaxActiveTorrentsChanged" Min="-1" Disabled="@(!QueueingEnabled)" Variant="Variant.Outlined" Validation="MaxActiveTorrentsValidation" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Maximum active torrents" Value="MaxActiveTorrents" ValueChanged="MaxActiveTorrentsChanged" Min="-1" Disabled="@(!QueueingEnabled)" Variant="Variant.Outlined" Validation="MaxActiveTorrentsValidation" />
</MudItem>
<MudItem xs="12">
<FieldSwitch Label="Do not count slow torrents in these limits" Value="DontCountSlowTorrents" ValueChanged="DontCountSlowTorrentsChanged" Disabled="@(!QueueingEnabled)" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Download rate threshold" Value="SlowTorrentDlRateThreshold" ValueChanged="SlowTorrentDlRateThresholdChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" Validation="SlowTorrentDlRateThresholdValidation" />
<FieldSwitch Label="Merge trackers from different torrents" Value="MergeTrackers" ValueChanged="MergeTrackersChanged" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Upload rate threshold" Value="SlowTorrentUlRateThreshold" ValueChanged="SlowTorrentUlRateThresholdChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" Validation="SlowTorrentUlRateThresholdValidation" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Download rate threshold" Value="SlowTorrentDlRateThreshold" ValueChanged="SlowTorrentDlRateThresholdChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" Validation="SlowTorrentDlRateThresholdValidation" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Torrent inactivity timer" Value="SlowTorrentInactiveTimer" ValueChanged="SlowTorrentInactiveTimerChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" Validation="SlowTorrentInactiveTimerValidation" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Upload rate threshold" Value="SlowTorrentUlRateThreshold" ValueChanged="SlowTorrentUlRateThresholdChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" Validation="SlowTorrentUlRateThresholdValidation" />
</MudItem>
<MudItem xs="12">
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Torrent inactivity timer" Value="SlowTorrentInactiveTimer" ValueChanged="SlowTorrentInactiveTimerChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="s" Validation="SlowTorrentInactiveTimerValidation" />
</MudItem>
</MudGrid>
</MudCardContent>
@@ -92,19 +95,21 @@
<FieldSwitch Label="When ratio reaches" Value="MaxRatioEnabled" ValueChanged="MaxRatioEnabledChanged" />
</MudItem>
<MudItem xs="9">
<MudNumericField T="int" Label="" Value="MaxRatio" ValueChanged="MaxRatioChanged" Disabled="@(!MaxRatioEnabled)" Min="0" Max="9998" Variant="Variant.Outlined" Validation="MaxRatioValidation" />
<MudNumericField Immediate="true" DebounceInterval="250" T="float" Label="" Value="MaxRatio" ValueChanged="MaxRatioChanged"
Disabled="@(!MaxRatioEnabled)" Min="0" Max="9998" Variant="Variant.Outlined"
Validation="MaxRatioValidation" />
</MudItem>
<MudItem xs="3">
<FieldSwitch Label="When total seeding time reaches" Value="MaxSeedingTimeEnabled" ValueChanged="MaxSeedingTimeEnabledChanged" />
</MudItem>
<MudItem xs="9">
<MudNumericField T="int" Label="minutes" Value="MaxSeedingTime" ValueChanged="MaxSeedingTimeChanged" Disabled="@(!MaxSeedingTimeEnabled)" Min="0" Max="525600" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="minutes" Validation="MaxSeedingTimeValidation" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="minutes" Value="MaxSeedingTime" ValueChanged="MaxSeedingTimeChanged" Disabled="@(!MaxSeedingTimeEnabled)" Min="0" Max="525600" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="minutes" Validation="MaxSeedingTimeValidation" />
</MudItem>
<MudItem xs="3">
<FieldSwitch Label="When inactive seeding time reaches" Value="MaxInactiveSeedingTimeEnabled" ValueChanged="MaxInactiveSeedingTimeEnabledChanged" />
</MudItem>
<MudItem xs="9">
<MudNumericField T="int" Label="minutes" Value="MaxInactiveSeedingTime" ValueChanged="MaxInactiveSeedingTimeChanged" Disabled="@(!MaxInactiveSeedingTimeEnabled)" Min="0" Max="525600" Variant="Variant.Outlined" Validation="MaxInactiveSeedingTimeValidation" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="minutes" Value="MaxInactiveSeedingTime" ValueChanged="MaxInactiveSeedingTimeChanged" Disabled="@(!MaxInactiveSeedingTimeEnabled)" Min="0" Max="525600" Variant="Variant.Outlined" Validation="MaxInactiveSeedingTimeValidation" />
</MudItem>
<MudItem xs="12">
<MudSelect T="int" Value="MaxRatioAct" ValueChanged="MaxRatioActChanged" Disabled="@(!MaxRatioEnabled && !MaxSeedingTimeEnabled && !MaxInactiveSeedingTimeEnabled)" Variant="Variant.Outlined">
@@ -121,17 +126,29 @@
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
<MudCardHeader>
<CardHeaderContent>
<MudText Typo="Typo.subtitle2">Seeding Limits</MudText>
<MudText Typo="Typo.subtitle2">Trackers</MudText>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent Class="pt-0">
<MudGrid>
<MudItem xs="12">
<FieldSwitch Label="Automatically add these trackers to new downloads" Value="AddTrackersEnabled" ValueChanged="AddTrackersEnabledChanged" />
<FieldSwitch Label="Automatically add these trackers to new torrents" Value="AddTrackersEnabled" ValueChanged="AddTrackersEnabledChanged" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="Trackers" Value="AddTrackers" ValueChanged="AddTrackersChanged" Lines="5" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Trackers" Value="AddTrackers" ValueChanged="AddTrackersChanged" Lines="5" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<FieldSwitch Label="Fetch additional trackers from URL" Value="AddTrackersFromUrlEnabled" ValueChanged="AddTrackersFromUrlEnabledChanged" />
</MudItem>
<MudItem xs="12">
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Trackers URL" Value="AddTrackersUrl" ValueChanged="AddTrackersUrlChanged" Disabled="@(!AddTrackersFromUrlEnabled)" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Last fetched trackers" Value="AddTrackersUrlList" ValueChanged="AddTrackersUrlListChanged" Disabled="@(!AddTrackersFromUrlEnabled)" Lines="5" Variant="Variant.Outlined" />
</MudItem>
</MudGrid>
</MudCardContent>
</MudCard>
</MudCard>

View File

@@ -17,7 +17,7 @@
protected int SlowTorrentUlRateThreshold { get; private set; }
protected int SlowTorrentInactiveTimer { get; private set; }
protected bool MaxRatioEnabled { get; private set; }
protected int MaxRatio { get; private set; }
protected float MaxRatio { get; private set; }
protected bool MaxSeedingTimeEnabled { get; private set; }
protected int MaxSeedingTime { get; private set; }
protected int MaxRatioAct { get; private set; }
@@ -25,6 +25,10 @@
protected int MaxInactiveSeedingTime { get; private set; }
protected bool AddTrackersEnabled { get; private set; }
protected string? AddTrackers { get; private set; }
protected bool AddTrackersFromUrlEnabled { get; private set; }
protected string? AddTrackersUrl { get; private set; }
protected string? AddTrackersUrlList { get; private set; }
protected bool MergeTrackers { get; private set; }
protected Func<int, string?> MaxActiveDownloadsValidation = value =>
{
@@ -166,6 +170,10 @@
AddTrackersEnabled = Preferences.AddTrackersEnabled;
AddTrackers = Preferences.AddTrackers;
AddTrackersFromUrlEnabled = Preferences.AddTrackersFromUrlEnabled;
AddTrackersUrl = Preferences.AddTrackersUrl;
AddTrackersUrlList = Preferences.AddTrackersUrlList;
MergeTrackers = Preferences.MergeTrackers;
return true;
}
@@ -275,7 +283,7 @@
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
protected async Task MaxRatioChanged(int value)
protected async Task MaxRatioChanged(float value)
{
MaxRatio = value;
UpdatePreferences.MaxRatio = value;
@@ -330,5 +338,33 @@
UpdatePreferences.AddTrackers = value;
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
protected async Task AddTrackersFromUrlEnabledChanged(bool value)
{
AddTrackersFromUrlEnabled = value;
UpdatePreferences.AddTrackersFromUrlEnabled = value;
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
protected async Task AddTrackersUrlChanged(string value)
{
AddTrackersUrl = value;
UpdatePreferences.AddTrackersUrl = value;
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
protected async Task AddTrackersUrlListChanged(string value)
{
AddTrackersUrlList = value;
UpdatePreferences.AddTrackersUrlList = value;
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
protected async Task MergeTrackersChanged(bool value)
{
MergeTrackers = value;
UpdatePreferences.MergeTrackers = value;
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
}
}
}

View File

@@ -1,13 +1,13 @@
@inherits Options
@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" Variant="Variant.Outlined">
<MudSelectItem T="int" Value="0">TCP and μTP</MudSelectItem>
<MudSelectItem T="int" Value="0">TCP and <EFBFBD>TP</MudSelectItem>
<MudSelectItem T="int" Value="1">TCP</MudSelectItem>
<MudSelectItem T="int" Value="2">μTP</MudSelectItem>
<MudSelectItem T="int" Value="2"><EFBFBD>TP</MudSelectItem>
</MudSelect>
</MudItem>
</MudGrid>
@@ -23,7 +23,7 @@
<MudCardContent Class="pt-0">
<MudGrid>
<MudItem xs="12">
<MudNumericField T="int" Label="Port used for incoming connections" Value="ListenPort" ValueChanged="ListenPortChanged" Min="@MinNonNegativePortValue" Max="@MaxPortValue" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentIcon="@CustomIcons.Random" OnAdornmentClick="GenerateRandomPort" HelperText="Set to 0 to let your system pick an unused port" Validation="PortNonNegativeValidation" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Port used for incoming connections" Value="ListenPort" ValueChanged="ListenPortChanged" Min="@MinNonNegativePortValue" Max="@MaxPortValue" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentIcon="@CustomIcons.Random" OnAdornmentClick="GenerateRandomPort" HelperText="Set to 0 to let your system pick an unused port" Validation="PortNonNegativeValidation" />
</MudItem>
<MudItem xs="12">
<FieldSwitch Label="Use UPnp / NAT-PMP port forwarding from my router" Value="Upnp" ValueChanged="UpnpChanged" />
@@ -44,25 +44,25 @@
<FieldSwitch Label="Global maximum number of connections" Value="MaxConnecEnabled" ValueChanged="MaxConnecEnabledChanged" />
</MudItem>
<MudItem xs="12" md="6">
<MudNumericField T="int" Label="Connections" Value="MaxConnec" ValueChanged="MaxConnecChanged" Min="0" Disabled="@(!MaxConnecEnabled)" Variant="Variant.Outlined" Validation="MaxConnectValidation" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Connections" Value="MaxConnec" ValueChanged="MaxConnecChanged" Min="0" Disabled="@(!MaxConnecEnabled)" Variant="Variant.Outlined" Validation="MaxConnectValidation" />
</MudItem>
<MudItem xs="12" md="6">
<FieldSwitch Label="Maximum number of connections per torrent" Value="MaxConnecPerTorrentEnabled" ValueChanged="MaxConnecPerTorrentEnabledChanged" />
</MudItem>
<MudItem xs="12" md="6">
<MudNumericField T="int" Label="Connections" Value="MaxConnecPerTorrent" ValueChanged="MaxConnecPerTorrentChanged" Min="0" Disabled="@(!MaxConnecPerTorrentEnabled)" Variant="Variant.Outlined" Validation="MaxConnecPerTorrentValidation" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Connections" Value="MaxConnecPerTorrent" ValueChanged="MaxConnecPerTorrentChanged" Min="0" Disabled="@(!MaxConnecPerTorrentEnabled)" Variant="Variant.Outlined" Validation="MaxConnecPerTorrentValidation" />
</MudItem>
<MudItem xs="12" md="6">
<FieldSwitch Label="Global maximum number of upload slots" Value="MaxUploadsEnabled" ValueChanged="MaxUploadsEnabledChanged" />
</MudItem>
<MudItem xs="12" md="6">
<MudNumericField T="int" Label="Slots" Value="MaxUploads" ValueChanged="MaxUploadsChanged" Min="0" Disabled="@(!MaxUploadsEnabled)" Variant="Variant.Outlined" Validation="MaxUploadsValidation" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Slots" Value="MaxUploads" ValueChanged="MaxUploadsChanged" Min="0" Disabled="@(!MaxUploadsEnabled)" Variant="Variant.Outlined" Validation="MaxUploadsValidation" />
</MudItem>
<MudItem xs="12" md="6">
<FieldSwitch Label="Maximum number of upload slots per torrent" Value="MaxUploadsPerTorrentEnabled" ValueChanged="MaxUploadsPerTorrentEnabledChanged" />
</MudItem>
<MudItem xs="12" md="6">
<MudNumericField T="int" Label="Slots" Value="MaxUploadsPerTorrent" ValueChanged="MaxUploadsPerTorrentChanged" Min="0" Disabled="@(!MaxUploadsPerTorrentEnabled)" Variant="Variant.Outlined" Validation="MaxUploadsPerTorrentValidation" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Slots" Value="MaxUploadsPerTorrent" ValueChanged="MaxUploadsPerTorrentChanged" Min="0" Disabled="@(!MaxUploadsPerTorrentEnabled)" Variant="Variant.Outlined" Validation="MaxUploadsPerTorrentValidation" />
</MudItem>
</MudGrid>
</MudCardContent>
@@ -75,10 +75,10 @@
<FieldSwitch Label="I2P (Experimental)" Value="I2pEnabled" ValueChanged="I2pEnabledChanged" />
</MudItem>
<MudItem xs="12" md="6">
<MudTextField T="string" Label="Host" Value="I2pAddress" ValueChanged="I2pAddressChanged" Disabled="@(!I2pEnabled)" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Host" Value="I2pAddress" ValueChanged="I2pAddressChanged" Disabled="@(!I2pEnabled)" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12" md="6">
<MudNumericField T="int" Label="Slots" Value="I2pPort" ValueChanged="I2pPortChanged" Min="0" Max="65535" Disabled="@(!I2pEnabled)" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Slots" Value="I2pPort" ValueChanged="I2pPortChanged" Min="0" Max="65535" Disabled="@(!I2pEnabled)" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<FieldSwitch Label="Mixed mode" Value="I2pMixedMode" ValueChanged="I2pMixedModeChanged" Disabled="@(!I2pEnabled)" HelperText="If &quot;mixed mode&quot; is enabled, I2P torrents are allowed to also get peers from other sources than the tracker, and connect to regular IPs, not providing any anonymization. This may be useful if the user is not interested in the anonymization of I2P, but still wants to be able to connect to I2P peers." />
@@ -104,10 +104,10 @@
</MudSelect>
</MudItem>
<MudItem xs="12" md="4">
<MudTextField T="string" Label="Host" Value="ProxyIp" ValueChanged="ProxyIpChanged" Disabled="ProxyDisabled" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Host" Value="ProxyIp" ValueChanged="ProxyIpChanged" Disabled="ProxyDisabled" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12" md="4">
<MudNumericField T="int" Label="Port" Value="ProxyPort" ValueChanged="ProxyPortChanged" Min="1" Max="@ConnectionOptions.MaxPortValue" Disabled="ProxyDisabled" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Port" Value="ProxyPort" ValueChanged="ProxyPortChanged" Min="1" Max="@ConnectionOptions.MaxPortValue" Disabled="ProxyDisabled" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<FieldSwitch Label="Perform hostname lookup via proxy" Value="ProxyHostnameLookup" ValueChanged="ProxyHostnameLookupChanged" HelperText="If checked, hostname lookups are done via the proxy." />
@@ -116,10 +116,10 @@
<FieldSwitch Label="Authentication" Value="ProxyAuthEnabled" ValueChanged="ProxyAuthEnabledChanged" Disabled="@(ProxyDisabled || ProxySocks4)" />
</MudItem>
<MudItem xs="12" md="6">
<MudTextField T="string" Label="Username" Value="ProxyUsername" ValueChanged="ProxyUsernameChanged" Disabled="@(ProxyDisabled || ProxySocks4)" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Username" Value="ProxyUsername" ValueChanged="ProxyUsernameChanged" Disabled="@(ProxyDisabled || ProxySocks4)" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12" md="6">
<MudTextField T="string" Label="Password" Value="ProxyPassword" ValueChanged="ProxyPasswordChanged" Disabled="@(ProxyDisabled || ProxySocks4)" Variant="Variant.Outlined " HelperText="Info: The password is saved unencrypted" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Password" Value="ProxyPassword" ValueChanged="ProxyPasswordChanged" Disabled="@(ProxyDisabled || ProxySocks4)" Variant="Variant.Outlined " HelperText="Info: The password is saved unencrypted" />
</MudItem>
<MudItem xs="12">
@@ -150,14 +150,17 @@
<FieldSwitch Label="IP Filter" Value="IpFilterEnabled" ValueChanged="IpFilterEnabledChanged" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="Filter path (.dat, .p2p, .p2b)" Value="IpFilterPath" ValueChanged="IpFilterPathChanged" Disabled="@(!IpFilterEnabled)" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Filter path (.dat, .p2p, .p2b)" Value="IpFilterPath" ValueChanged="IpFilterPathChanged" Disabled="@(!IpFilterEnabled)" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<FieldSwitch Label="Apply to trackers" Value="IpFilterTrackers" ValueChanged="IpFilterTrackersChanged" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="Manually banned IP addresses" Value="BannedIPs" ValueChanged="BannedIPsChanged" Lines="5" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Manually banned IP addresses" Value="BannedIPs" ValueChanged="BannedIPsChanged" Lines="5" Variant="Variant.Outlined" />
</MudItem>
</MudGrid>
</MudCardContent>
</MudCard>
</MudCard>

View File

@@ -1,4 +1,4 @@
@inherits Options
@inherits Options
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
<MudCardHeader>
@@ -29,7 +29,11 @@
</MudSelect>
</MudItem>
<MudItem xs="12">
<FieldSwitch Label="Delete .torrent files afterwards" Value="AutoDeleteMode" ValueChanged="AutoDeleteModeChanged" />
<MudSelect T="int" Label="Delete .torrent files afterwards" Value="AutoDeleteMode" ValueChanged="AutoDeleteModeChanged" Variant="Variant.Outlined">
<MudSelectItem Value="0">Never</MudSelectItem>
<MudSelectItem Value="1">If added successfully</MudSelectItem>
<MudSelectItem Value="2">Always</MudSelectItem>
</MudSelect>
</MudItem>
</MudGrid>
</MudCardContent>
@@ -62,7 +66,7 @@
<MudCardContent Class="pt-0">
<MudGrid>
<MudItem xs="12">
<MudSelect T="bool" Label="Default Torrent Management Mode" Value="AutoTmmEnabled" ValueChanged="AutoDeleteModeChanged" Variant="Variant.Outlined">
<MudSelect T="bool" Label="Default Torrent Management Mode" Value="AutoTmmEnabled" ValueChanged="AutoTmmEnabledChanged" Variant="Variant.Outlined">
<MudSelectItem Value="false">Manual</MudSelectItem>
<MudSelectItem Value="true">Automatic</MudSelectItem>
</MudSelect>
@@ -89,7 +93,7 @@
<FieldSwitch Label="Use Subcategories" Value="UseSubcategories" ValueChanged="UseSubcategoriesChanged" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="Default Save Path" Value="SavePath" ValueChanged="SavePathChanged" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Default Save Path" Value="SavePath" ValueChanged="SavePathChanged" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudGrid>
@@ -97,7 +101,7 @@
<FieldSwitch Label="Keep incomplete torrents in" Value="TempPathEnabled" ValueChanged="TempPathEnabledChanged" />
</MudItem>
<MudItem xs="12" sm="6" md="9">
<MudTextField T="string" Label="Path" Value="TempPath" ValueChanged="TempPathChanged" Disabled="@(!TempPathEnabled)" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Path" Value="TempPath" ValueChanged="TempPathChanged" Disabled="@(!TempPathEnabled)" Variant="Variant.Outlined" />
</MudItem>
</MudGrid>
</MudItem>
@@ -107,7 +111,7 @@
<FieldSwitch Label="Copy .torrent files to" Value="ExportDirEnabled" ValueChanged="ExportDirEnabledChanged" />
</MudItem>
<MudItem xs="12" sm="6" md="9">
<MudTextField T="string" Label="Path" Value="ExportDir" ValueChanged="ExportDirChanged" Disabled="@(!TempPathEnabled)" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Path" Value="ExportDir" ValueChanged="ExportDirChanged" Disabled="@(!TempPathEnabled)" Variant="Variant.Outlined" />
</MudItem>
</MudGrid>
</MudItem>
@@ -117,7 +121,7 @@
<FieldSwitch Label="Copy .torrent files for finished downloads to" Value="ExportDirFinEnabled" ValueChanged="ExportDirFinEnabledChanged" />
</MudItem>
<MudItem xs="12" sm="6" md="9">
<MudTextField T="string" Label="Path" Value="ExportDirFin" ValueChanged="ExportDirFinChanged" Disabled="@(!TempPathEnabled)" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Path" Value="ExportDirFin" ValueChanged="ExportDirFinChanged" Disabled="@(!TempPathEnabled)" Variant="Variant.Outlined" />
</MudItem>
</MudGrid>
</MudItem>
@@ -144,7 +148,7 @@
{
<tr>
<td>
<MudTextField T="string" Label="Path" Value="@item.Key" ValueChanged="@(v => ScanDirsKeyChanged(item.Key, v))" Validation="IsValidNewKey" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Path" Value="@item.Key" ValueChanged="@(v => ScanDirsKeyChanged(item.Key, v))" Validation="IsValidNewKey" />
</td>
<td>
<MudGrid>
@@ -158,7 +162,7 @@
@if (item.Value.SavePath is not null)
{
<MudItem xs="8">
<MudTextField T="string" Label="Path" Value="@item.Value.SavePath" ValueChanged="@(v => ScanDirsValueChanged(item.Key, v))" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Path" Value="@item.Value.SavePath" ValueChanged="@(v => ScanDirsValueChanged(item.Key, v))" Variant="Variant.Outlined" />
</MudItem>
}
<MudItem xs="1">
@@ -177,7 +181,7 @@
<tr>
<td>
<MudTextField T="string" Label="Path" Value="@item.Key" ValueChanged="@(v => AddedScanDirsKeyChanged(index, v))" Validation="IsValidNewKey" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Path" Value="@item.Key" ValueChanged="@(v => AddedScanDirsKeyChanged(index, v))" Validation="IsValidNewKey" />
</td>
<td>
<MudGrid>
@@ -191,7 +195,7 @@
@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))" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Path" Value="@item.Value.SavePath" ValueChanged="@(v => AddedScanDirsValueChanged(index, v))" Variant="Variant.Outlined" />
</MudItem>
}
<MudItem xs="1">
@@ -225,7 +229,7 @@
<FieldSwitch Label="Excluded file names" Value="ExcludedFileNamesEnabled" ValueChanged="ExcludedFileNamesEnabledChanged" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="Excluded files names" Value="ExcludedFileNames" ValueChanged="ExcludedFileNamesChanged" Lines="5" Disabled="@(!ExcludedFileNamesEnabled)" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Excluded files names" Value="ExcludedFileNames" ValueChanged="ExcludedFileNamesChanged" Lines="5" Disabled="@(!ExcludedFileNamesEnabled)" Variant="Variant.Outlined" />
</MudItem>
</MudGrid>
</MudCardContent>
@@ -243,13 +247,13 @@
<FieldSwitch Label="Email notification upon download completion" Value="MailNotificationEnabled" ValueChanged="MailNotificationEnabledChanged" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="From" Value="MailNotificationSender" ValueChanged="MailNotificationSenderChanged" Disabled="@(!MailNotificationEnabled)" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="From" Value="MailNotificationSender" ValueChanged="MailNotificationSenderChanged" Disabled="@(!MailNotificationEnabled)" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="To" Value="MailNotificationEmail" ValueChanged="MailNotificationEmailChanged" Disabled="@(!MailNotificationEnabled)" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="To" Value="MailNotificationEmail" ValueChanged="MailNotificationEmailChanged" Disabled="@(!MailNotificationEnabled)" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="SMTP server" Value="MailNotificationSmtp" ValueChanged="MailNotificationSmtpChanged" Disabled="@(!MailNotificationEnabled)" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="SMTP server" Value="MailNotificationSmtp" ValueChanged="MailNotificationSmtpChanged" Disabled="@(!MailNotificationEnabled)" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<FieldSwitch Label="This server requires a secure connection (SSL)" Value="MailNotificationSslEnabled" ValueChanged="MailNotificationSslEnabledChanged" Disabled="@(!MailNotificationEnabled)" />
@@ -258,10 +262,10 @@
<FieldSwitch Label="Authentication" Value="MailNotificationAuthEnabled" ValueChanged="MailNotificationAuthEnabledChanged" Disabled="@(!MailNotificationEnabled)" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="Username" Value="MailNotificationUsername" ValueChanged="MailNotificationUsernameChanged" Disabled="@(!(MailNotificationEnabled && MailNotificationAuthEnabled))" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Username" Value="MailNotificationUsername" ValueChanged="MailNotificationUsernameChanged" Disabled="@(!(MailNotificationEnabled && MailNotificationAuthEnabled))" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="Password" Value="MailNotificationPassword" ValueChanged="MailNotificationPasswordChanged" Disabled="@(!(MailNotificationEnabled && MailNotificationAuthEnabled))" InputType="InputType.Password" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Password" Value="MailNotificationPassword" ValueChanged="MailNotificationPasswordChanged" Disabled="@(!(MailNotificationEnabled && MailNotificationAuthEnabled))" InputType="InputType.Password" Variant="Variant.Outlined" />
</MudItem>
</MudGrid>
</MudCardContent>
@@ -279,13 +283,13 @@
<FieldSwitch Label="Run external program on torrent added" Value="AutorunOnTorrentAddedEnabled" ValueChanged="AutorunOnTorrentAddedEnabledChanged" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="External program" Value="AutorunOnTorrentAddedProgram" ValueChanged="AutorunOnTorrentAddedProgramChanged" Disabled="@(!AutorunOnTorrentAddedEnabled)" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="External program" Value="AutorunOnTorrentAddedProgram" ValueChanged="AutorunOnTorrentAddedProgramChanged" Disabled="@(!AutorunOnTorrentAddedEnabled)" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<FieldSwitch Label="Run external program on torrent finished" Value="AutorunEnabled" ValueChanged="AutorunEnabledChanged" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="External program" Value="AutorunProgram" ValueChanged="AutorunProgramChanged" Disabled="@(!AutorunEnabled)" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="External program" Value="AutorunProgram" ValueChanged="AutorunProgramChanged" Disabled="@(!AutorunEnabled)" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudText>Supported parameters (case sensitive):</MudText>
@@ -306,4 +310,7 @@
</MudItem>
</MudGrid>
</MudCardContent>
</MudCard>
</MudCard>

View File

@@ -8,7 +8,7 @@ namespace Lantean.QBTMud.Components.Options
protected bool AddToTopOfQueue { get; set; }
protected bool StartPausedEnabled { get; set; }
protected string? TorrentStopCondition { get; set; }
protected bool AutoDeleteMode { get; set; }
protected int AutoDeleteMode { get; set; }
protected bool PreallocateAll { get; set; }
protected bool IncompleteFilesExt { get; set; }
protected bool AutoTmmEnabled { get; set; }
@@ -51,9 +51,9 @@ namespace Lantean.QBTMud.Components.Options
// when adding a torrent
TorrentContentLayout = Preferences.TorrentContentLayout;
AddToTopOfQueue = Preferences.AddToTopOfQueue;
StartPausedEnabled = Preferences.StartPausedEnabled;
StartPausedEnabled = Preferences.AddStoppedEnabled || Preferences.StartPausedEnabled;
TorrentStopCondition = Preferences.TorrentStopCondition;
AutoDeleteMode = Preferences.AutoDeleteMode == 1;
AutoDeleteMode = Preferences.AutoDeleteMode is >= 0 and <= 2 ? Preferences.AutoDeleteMode : 0;
PreallocateAll = Preferences.PreallocateAll;
IncompleteFilesExt = Preferences.IncompleteFilesExt;
@@ -120,6 +120,7 @@ namespace Lantean.QBTMud.Components.Options
{
StartPausedEnabled = value;
UpdatePreferences.StartPausedEnabled = value;
UpdatePreferences.AddStoppedEnabled = value;
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
@@ -130,10 +131,10 @@ namespace Lantean.QBTMud.Components.Options
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
protected async Task AutoDeleteModeChanged(bool value)
protected async Task AutoDeleteModeChanged(int value)
{
AutoDeleteMode = value;
UpdatePreferences.AutoDeleteMode = value ? 1 : 0;
UpdatePreferences.AutoDeleteMode = value;
await PreferencesChanged.InvokeAsync(UpdatePreferences);
}
@@ -410,4 +411,4 @@ namespace Lantean.QBTMud.Components.Options
return null;
}
}
}
}

View File

@@ -1,4 +1,4 @@
@inherits Options
@inherits Options
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
<MudCardHeader>
@@ -12,10 +12,10 @@
<FieldSwitch Label="Enable fetching RSS feeds" Value="RssProcessingEnabled" ValueChanged="RssProcessingEnabledChanged" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Feeds refresh interval" Value="RssRefreshInterval" ValueChanged="RssRefreshIntervalChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="min" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Feeds refresh interval" Value="RssRefreshInterval" ValueChanged="RssRefreshIntervalChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="min" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Maximum number of articles per feed" Value="RssMaxArticlesPerFeed" ValueChanged="RssMaxArticlesPerFeedChanged" Min="0" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Maximum number of articles per feed" Value="RssMaxArticlesPerFeed" ValueChanged="RssMaxArticlesPerFeedChanged" Min="0" Variant="Variant.Outlined" />
</MudItem>
</MudGrid>
</MudCardContent>
@@ -51,8 +51,11 @@
<FieldSwitch Label="Download REPACK/PROPER episodes" Value="RssDownloadRepackProperEpisodes" ValueChanged="RssDownloadRepackProperEpisodesChanged" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="Filters" Value="RssSmartEpisodeFilters" ValueChanged="RssSmartEpisodeFiltersChanged" Lines="5" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Filters" Value="RssSmartEpisodeFilters" ValueChanged="RssSmartEpisodeFiltersChanged" Lines="5" Variant="Variant.Outlined" />
</MudItem>
</MudGrid>
</MudCardContent>
</MudCard>
</MudCard>

View File

@@ -1,4 +1,4 @@
@inherits Options
@inherits Options
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
<MudCardHeader>
@@ -9,10 +9,10 @@
<MudCardContent Class="pt-0">
<MudGrid>
<MudItem xs="12">
<MudNumericField T="int" Label="Upload" Value="UpLimit" ValueChanged="UpLimitChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" HelperText="0 means unlimited" Validation="UpLimitValidation" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Upload" Value="UpLimit" ValueChanged="UpLimitChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" HelperText="0 means unlimited" Validation="UpLimitValidation" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Download" Value="DlLimit" ValueChanged="DlLimitChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" HelperText="0 means unlimited" Validation="DlLimitValidation" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Download" Value="DlLimit" ValueChanged="DlLimitChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" HelperText="0 means unlimited" Validation="DlLimitValidation" />
</MudItem>
</MudGrid>
</MudCardContent>
@@ -27,10 +27,10 @@
<MudCardContent Class="pt-0">
<MudGrid>
<MudItem xs="12">
<MudNumericField T="int" Label="Upload" Value="AltUpLimit" ValueChanged="AltUpLimitChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" HelperText="0 means unlimited" Validation="AltUpLimitValidation" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Upload" Value="AltUpLimit" ValueChanged="AltUpLimitChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" HelperText="0 means unlimited" Validation="AltUpLimitValidation" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Download" Value="AltDlLimit" ValueChanged="AltDlLimitChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" HelperText="0 means unlimited" Validation="AltDlLimitValidation" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Download" Value="AltDlLimit" ValueChanged="AltDlLimitChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="KiB/s" HelperText="0 means unlimited" Validation="AltDlLimitValidation" />
</MudItem>
<MudItem xs="12">
<FieldSwitch Label="Schedule the use of alternative rate limits" Value="SchedulerEnabled" ValueChanged="SchedulerEnabledChanged" />
@@ -68,7 +68,7 @@
<MudCardContent Class="pt-0">
<MudGrid>
<MudItem xs="12">
<FieldSwitch Label="Apply rate limit to µTP protocol" Value="LimitUtpRate" ValueChanged="LimitUtpRateChanged" />
<FieldSwitch Label="Apply rate limit to <EFBFBD>TP protocol" Value="LimitUtpRate" ValueChanged="LimitUtpRateChanged" />
</MudItem>
<MudItem xs="12">
<FieldSwitch Label="Apply rate limit to transport overhead" Value="LimitTcpOverhead" ValueChanged="LimitTcpOverheadChanged" />
@@ -78,4 +78,6 @@
</MudItem>
</MudGrid>
</MudCardContent>
</MudCard>
</MudCard>

View File

@@ -1,4 +1,4 @@
@inherits Options
@inherits Options
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
<MudCardHeader>
@@ -9,10 +9,10 @@
<MudCardContent Class="pt-0">
<MudGrid>
<MudItem xs="12" md="8">
<MudTextField T="string" Label="Host" Value="WebUiAddress" ValueChanged="WebUiAddressChanged" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Host" Value="WebUiAddress" ValueChanged="WebUiAddressChanged" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12" md="4">
<MudNumericField T="int" Label="Port" Value="WebUiPort" ValueChanged="WebUiPortChanged" Min="1" Max="@Options.MaxPortValue" Variant="Variant.Outlined" Validation="WebUiPortValidation" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Port" Value="WebUiPort" ValueChanged="WebUiPortChanged" Min="1" Max="@Options.MaxPortValue" Variant="Variant.Outlined" Validation="WebUiPortValidation" />
</MudItem>
<MudItem xs="12">
<FieldSwitch Label="Use UPnP / NAT-PMP to forward the port from my router" Value="WebUiUpnp" ValueChanged="WebUiUpnpChanged" />
@@ -33,10 +33,10 @@
<FieldSwitch Label="Use HTTPS instead of HTTP" Value="UseHttps" ValueChanged="UseHttpsChanged" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="Certificate" Value="WebUiHttpsCertPath" ValueChanged="WebUiHttpsCertPathChanged" Disabled="@(!UseHttps)" Variant="Variant.Outlined" Validation="WebUiHttpsCertPathValidation" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Certificate" Value="WebUiHttpsCertPath" ValueChanged="WebUiHttpsCertPathChanged" Disabled="@(!UseHttps)" Variant="Variant.Outlined" Validation="WebUiHttpsCertPathValidation" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="Key" Value="WebUiHttpsKeyPath" ValueChanged="WebUiHttpsKeyPathChanged" Disabled="@(!UseHttps)" Variant="Variant.Outlined" Validation="WebUiHttpsKeyPathValidation" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Key" Value="WebUiHttpsKeyPath" ValueChanged="WebUiHttpsKeyPathChanged" Disabled="@(!UseHttps)" Variant="Variant.Outlined" Validation="WebUiHttpsKeyPathValidation" />
</MudItem>
</MudGrid>
</MudCardContent>
@@ -51,10 +51,10 @@
<MudCardContent Class="pt-0">
<MudGrid>
<MudItem xs="12">
<MudTextField T="string" Label="Username" Value="WebUiUsername" ValueChanged="WebUiUsernameChanged" Variant="Variant.Outlined" Validation="WebUiUsernameValidation" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Username" Value="WebUiUsername" ValueChanged="WebUiUsernameChanged" Variant="Variant.Outlined" Validation="WebUiUsernameValidation" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="Password" Value="WebUiPassword" ValueChanged="WebUiPasswordChanged" InputType="InputType.Password" Variant="Variant.Outlined" Validation="WebUiPasswordValidation" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Password" Value="WebUiPassword" ValueChanged="WebUiPasswordChanged" InputType="InputType.Password" Variant="Variant.Outlined" Validation="WebUiPasswordValidation" />
</MudItem>
<MudItem xs="12">
<FieldSwitch Label="Bypass authentication for clients on localhost" Value="BypassLocalAuth" ValueChanged="BypassLocalAuthChanged" />
@@ -63,16 +63,16 @@
<FieldSwitch Label="Bypass authentication for clients in whitelisted IP subnets" Value="BypassAuthSubnetWhitelistEnabled" ValueChanged="BypassAuthSubnetWhitelistEnabledChanged" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="Trackers" Value="BypassAuthSubnetWhitelist" ValueChanged="BypassAuthSubnetWhitelistChanged" Lines="5" Disabled="@(!BypassAuthSubnetWhitelistEnabled)" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Trackers" Value="BypassAuthSubnetWhitelist" ValueChanged="BypassAuthSubnetWhitelistChanged" Lines="5" Disabled="@(!BypassAuthSubnetWhitelistEnabled)" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Ban client after consecutive failures" Value="WebUiMaxAuthFailCount" ValueChanged="WebUiMaxAuthFailCountChanged" Min="0" Variant="Variant.Outlined" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Ban client after consecutive failures" Value="WebUiMaxAuthFailCount" ValueChanged="WebUiMaxAuthFailCountChanged" Min="0" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="ban for" Value="WebUiBanDuration" ValueChanged="WebUiBanDurationChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="seconds" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="ban for" Value="WebUiBanDuration" ValueChanged="WebUiBanDurationChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="seconds" />
</MudItem>
<MudItem xs="12">
<MudNumericField T="int" Label="Session timeout" Value="WebUiSessionTimeout" ValueChanged="WebUiSessionTimeoutChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="seconds" />
<MudNumericField Immediate="true" DebounceInterval="250" T="int" Label="Session timeout" Value="WebUiSessionTimeout" ValueChanged="WebUiSessionTimeoutChanged" Min="0" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="seconds" />
</MudItem>
</MudGrid>
</MudCardContent>
@@ -90,7 +90,7 @@
<FieldSwitch Label="Use alternative Web UI" Value="AlternativeWebuiEnabled" ValueChanged="AlternativeWebuiEnabledChanged" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="Files location" Value="AlternativeWebuiPath" ValueChanged="AlternativeWebuiPathChanged" Variant="Variant.Outlined" Validation="AlternativeWebuiPathValidation" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Files location" Value="AlternativeWebuiPath" ValueChanged="AlternativeWebuiPathChanged" Variant="Variant.Outlined" Validation="AlternativeWebuiPathValidation" />
</MudItem>
</MudGrid>
</MudCardContent>
@@ -117,7 +117,7 @@
<FieldSwitch Label="Enable Host header validation" Value="WebUiHostHeaderValidationEnabled" ValueChanged="WebUiHostHeaderValidationEnabledChanged" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="Server domains" Value="WebUiDomainList" ValueChanged="WebUiDomainListChanged" Lines="5" Disabled="@(!WebUiHostHeaderValidationEnabled)" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Server domains" Value="WebUiDomainList" ValueChanged="WebUiDomainListChanged" Lines="5" Disabled="@(!WebUiHostHeaderValidationEnabled)" Variant="Variant.Outlined" />
</MudItem>
</MudGrid>
</MudCardContent>
@@ -144,7 +144,7 @@
<FieldSwitch Label="Enable Host header validation" Value="WebUiHostHeaderValidationEnabled" ValueChanged="WebUiHostHeaderValidationEnabledChanged" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="Server domains" Value="WebUiDomainList" ValueChanged="WebUiDomainListChanged" Lines="5" Disabled="@(!WebUiHostHeaderValidationEnabled)" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Server domains" Value="WebUiDomainList" ValueChanged="WebUiDomainListChanged" Lines="5" Disabled="@(!WebUiHostHeaderValidationEnabled)" Variant="Variant.Outlined" />
</MudItem>
</MudGrid>
</MudCardContent>
@@ -162,7 +162,7 @@
<FieldSwitch Label="Add custom HTTP headers" Value="WebUiUseCustomHttpHeadersEnabled" ValueChanged="WebUiUseCustomHttpHeadersEnabledChanged" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="Server domains" Value="WebUiCustomHttpHeaders" ValueChanged="WebUiCustomHttpHeadersChanged" Lines="5" Disabled="@(!WebUiUseCustomHttpHeadersEnabled)" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Server domains" Value="WebUiCustomHttpHeaders" ValueChanged="WebUiCustomHttpHeadersChanged" Lines="5" Disabled="@(!WebUiUseCustomHttpHeadersEnabled)" Variant="Variant.Outlined" />
</MudItem>
</MudGrid>
</MudCardContent>
@@ -180,7 +180,7 @@
<FieldSwitch Label="Enable reverse proxy support" Value="WebUiReverseProxyEnabled" ValueChanged="WebUiReverseProxyEnabledChanged" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="Trusted proxies list" Value="WebUiReverseProxiesList" ValueChanged="WebUiReverseProxiesListChanged" Lines="5" Disabled="@(!WebUiReverseProxyEnabled)" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Trusted proxies list" Value="WebUiReverseProxiesList" ValueChanged="WebUiReverseProxiesListChanged" Lines="5" Disabled="@(!WebUiReverseProxyEnabled)" Variant="Variant.Outlined" />
</MudItem>
</MudGrid>
</MudCardContent>
@@ -207,14 +207,17 @@
<MudButton OnClick="RegisterDyndnsService" Disabled="@(!DyndnsEnabled)" Variant="Variant.Filled">Register</MudButton>
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="Domain name" Value="DyndnsDomain" ValueChanged="DyndnsDomainChanged" Disabled="@(!DyndnsEnabled)" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Domain name" Value="DyndnsDomain" ValueChanged="DyndnsDomainChanged" Disabled="@(!DyndnsEnabled)" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="Username" Value="DyndnsUsername" ValueChanged="DyndnsUsernameChanged" Disabled="@(!DyndnsEnabled)" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Username" Value="DyndnsUsername" ValueChanged="DyndnsUsernameChanged" Disabled="@(!DyndnsEnabled)" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="12">
<MudTextField T="string" Label="Password" Value="DyndnsPassword" ValueChanged="DyndnsPasswordChanged" Disabled="@(!DyndnsEnabled)" InputType="InputType.Password" Variant="Variant.Outlined" />
<MudTextField Immediate="true" DebounceInterval="250" T="string" Label="Password" Value="DyndnsPassword" ValueChanged="DyndnsPasswordChanged" Disabled="@(!DyndnsEnabled)" InputType="InputType.Password" Variant="Variant.Outlined" />
</MudItem>
</MudGrid>
</MudCardContent>
</MudCard>
</MudCard>

View File

@@ -191,13 +191,13 @@ else if (RenderType == RenderType.MenuItems)
if (!action.Children.Any())
{
<MudMenuItem Icon="@action.Icon" IconColor="action.Color" OnClick="action.Callback" Disabled="Disabled">
<MudMenuItem Icon="@action.Icon" IconColor="action.Color" OnClick="action.Callback" Disabled="Disabled" Class="icon-menu-dense">
@action.Text
</MudMenuItem>
}
else
{
<MudMenuItem Icon="@action.Icon" IconColor="action.Color" OnClick="@(t => SubMenuTouch(action))">
<MudMenuItem Icon="@action.Icon" IconColor="action.Color" OnClick="@(t => SubMenuTouch(action))" Class="icon-menu-dense">
<MudMenu ListClass="unselectable" Dense="true" AnchorOrigin="Origin.TopRight" TransformOrigin="Origin.TopLeft" ActivationEvent="MouseEvent.MouseOver" Icon="@Icons.Material.Filled.ArrowDropDown" Ripple="false" Class="sub-menu">
<ActivatorContent>
@action.Text

View File

@@ -12,10 +12,7 @@ namespace Lantean.QBTMud.Components
{
public partial class TorrentActions : IAsyncDisposable
{
private const int _defaultVersion = 5;
private bool _disposedValue;
private int? _version;
private List<UIAction>? _actions;
@@ -63,7 +60,7 @@ namespace Lantean.QBTMud.Components
public QBitTorrentClient.Models.Preferences? Preferences { get; set; }
[Parameter]
public MudDialogInstance? MudDialog { get; set; }
public IMudDialogInstance? MudDialog { get; set; }
[Parameter]
public UIAction? ParentAction { get; set; }
@@ -74,37 +71,14 @@ namespace Lantean.QBTMud.Components
protected bool OverlayVisible { get; set; }
protected int MajorVersion
{
get
{
if (_version is not null)
{
return _version.Value;
}
if (string.IsNullOrEmpty(Version))
{
return _defaultVersion;
}
if (!System.Version.TryParse(Version.Replace("v", ""), out var version))
{
return _defaultVersion;
}
_version = version.Major;
return _version.Value;
}
}
protected int MajorVersion => VersionHelper.GetMajorVersion(Version);
protected override void OnInitialized()
{
_actions =
[
new("start", "Start", Icons.Material.Filled.PlayArrow, Color.Success, CreateCallback(Resume)),
new("pause", "Pause", Icons.Material.Filled.Pause, Color.Warning, CreateCallback(Pause)),
new("pause", "Pause", MajorVersion < 5 ? Icons.Material.Filled.Pause : Icons.Material.Filled.Stop, Color.Warning, CreateCallback(Pause)),
new("forceStart", "Force start", Icons.Material.Filled.Forward, Color.Warning, CreateCallback(ForceStart)),
new("delete", "Remove", Icons.Material.Filled.Delete, Color.Error, CreateCallback(Remove), separatorBefore: true),
new("setLocation", "Set location", Icons.Material.Filled.MyLocation, Color.Info, CreateCallback(SetLocation), separatorBefore: true),
@@ -441,7 +415,7 @@ namespace Lantean.QBTMud.Components
thereAreFirstLastPiecePrio = true;
}
if (torrent.Progress != 1.0) // not downloaded
if (torrent.Progress < 0.999999) // not downloaded
{
allAreDownloaded = false;
}

View File

@@ -8,19 +8,17 @@
Class="unselectable"
MaxHeight="@MaxHeight"
AnchorOrigin="@AnchorOrigin"
TransformOrigin="TransformOrigin"
RelativeWidth="@FullWidth"
TransformOrigin="@TransformOrigin"
RelativeWidth="@RelativeWidth"
OverflowBehavior="OverflowBehavior.FlipAlways"
Style="@_popoverStyle"
@ontouchend:preventDefault>
<CascadingValue Value="@(FakeMenu)">
@if (_showChildren)
{
<MudList T="object"
Class="unselectable"
Dense="@Dense">
<MudList T="object" Class="unselectable" Dense="@Dense">
@ChildContent
</MudList>
</MudList>
}
</CascadingValue>
</MudPopover>

View File

@@ -7,12 +7,6 @@ using MudBlazor.Utilities;
namespace Lantean.QBTMud.Components.UI
{
// This is a very hacky approach but works for now.
// This needs to inherit from MudMenu because MudMenuItem needs a MudMenu passed to it to control the close of the menu when an item is clicked.
// MudPopover isn't ideal for this because that is designed to be used relative to an activator which in these cases it isn't.
// Ideally this should be changed to use something like the way the DialogService works.
// Or - rework this to have a hidden MudMenu and hook into the OpenChanged event to monitor when the MudMenuItem closes it.
public partial class ContextMenu : MudComponentBase
{
private bool _open;
@@ -61,7 +55,7 @@ namespace Lantean.QBTMud.Components.UI
/// </summary>
[Parameter]
[Category(CategoryTypes.Menu.PopupAppearance)]
public bool FullWidth { get; set; }
public DropdownWidth RelativeWidth { get; set; }
/// <summary>
/// Sets the max height the menu can have when open.
@@ -219,56 +213,58 @@ namespace Lantean.QBTMud.Components.UI
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
protected override Task OnAfterRenderAsync(bool firstRender)
{
if (!_isResized)
{
await DeterminePosition();
//await DeterminePosition();
}
return Task.CompletedTask;
}
private async Task DeterminePosition()
{
var mainContentSize = await JSRuntime.GetInnerDimensions(".mud-main-content");
double? contextMenuHeight = null;
double? contextMenuWidth = null;
//private async Task DeterminePosition()
//{
// var mainContentSize = await JSRuntime.GetInnerDimensions(".mud-main-content");
// double? contextMenuHeight = null;
// double? contextMenuWidth = null;
var popoverHolder = PopoverService.ActivePopovers.FirstOrDefault(p => p.UserAttributes.ContainsKey("tracker") && (string?)p.UserAttributes["tracker"] == Id);
// var popoverHolder = PopoverService.ActivePopovers.FirstOrDefault(p => p.UserAttributes.ContainsKey("tracker") && (string?)p.UserAttributes["tracker"] == Id);
var popoverSize = await JSRuntime.GetBoundingClientRect($"#popovercontent-{popoverHolder?.Id}");
if (popoverSize.Height > 0)
{
contextMenuHeight = popoverSize.Height;
contextMenuWidth = popoverSize.Width;
}
else
{
return;
}
// var popoverSize = await JSRuntime.GetBoundingClientRect($"#popovercontent-{popoverHolder?.Id}");
// if (popoverSize.Height > 0)
// {
// contextMenuHeight = popoverSize.Height;
// contextMenuWidth = popoverSize.Width;
// }
// else
// {
// return;
// }
// the bottom position of the popover will be rendered off screen
if (_y - _diff + contextMenuHeight.Value >= mainContentSize.Height)
{
// adjust the top of the context menu
var overshoot = Math.Abs(mainContentSize.Height - (_y - _diff + contextMenuHeight.Value));
_y -= overshoot;
// // the bottom position of the popover will be rendered off screen
// if (_y - _diff + contextMenuHeight.Value >= mainContentSize.Height)
// {
// // adjust the top of the context menu
// var overshoot = Math.Abs(mainContentSize.Height - (_y - _diff + contextMenuHeight.Value));
// _y -= overshoot;
if (_y - _diff + contextMenuHeight >= mainContentSize.Height)
{
MaxHeight = (int)(mainContentSize.Height - _y + _diff);
}
}
// if (_y - _diff + contextMenuHeight >= mainContentSize.Height)
// {
// MaxHeight = (int)(mainContentSize.Height - _y + _diff);
// }
// }
if (_x + contextMenuWidth.Value > mainContentSize.Width)
{
var overshoot = Math.Abs(mainContentSize.Width - (_x + contextMenuWidth.Value));
_x -= overshoot;
}
// if (_x + contextMenuWidth.Value > mainContentSize.Width)
// {
// var overshoot = Math.Abs(mainContentSize.Width - (_x + contextMenuWidth.Value));
// _x -= overshoot;
// }
SetPopoverStyle(_x, _y);
_isResized = true;
await InvokeAsync(StateHasChanged);
}
// SetPopoverStyle(_x, _y);
// _isResized = true;
// await InvokeAsync(StateHasChanged);
//}
private (double x, double y) GetPositionFromArgs(EventArgs eventArgs)
{

View File

@@ -1,5 +1,5 @@
<div class="@Classname">
<div @onclick="EventUtil.AsNonRenderingEventHandler<MouseEventArgs>(OnClickHandler)" class="@LinkClassname" @onlongpress="OnLongPressInternal" @oncontextmenu="OnContextMenuInternal" @oncontextmenu:preventDefault>
<div @onclick="this.AsNonRenderingEventHandler<MouseEventArgs>(OnClickHandler)" class="@LinkClassname" @onlongpress="OnLongPressInternal" @oncontextmenu="OnContextMenuInternal" @oncontextmenu:preventDefault>
@if (!string.IsNullOrEmpty(Icon))
{
<MudIcon Icon="@Icon" Color="@IconColor" Class="@IconClassname" />

View File

@@ -13,6 +13,7 @@ namespace Lantean.QBTMud.Components.UI
private readonly string _columnSelectionStorageKey = $"DynamicTable{_typeName}.ColumnSelection";
private readonly string _columnSortStorageKey = $"DynamicTable{_typeName}.ColumnSort";
private readonly string _columnWidthsStorageKey = $"DynamicTable{_typeName}.ColumnWidths";
private readonly string _columnOrderStorageKey = $"DynamicTable{_typeName}.ColumnOrder";
[Inject]
public ILocalStorageService LocalStorage { get; set; } = default!;
@@ -82,6 +83,8 @@ namespace Lantean.QBTMud.Components.UI
private Dictionary<string, int?> _columnWidths = [];
private Dictionary<string, int> _columnOrder = [];
private string? _sortColumn;
private SortDirection _sortDirection;
@@ -165,8 +168,29 @@ namespace Lantean.QBTMud.Components.UI
protected IEnumerable<ColumnDefinition<T>> GetColumns()
{
var filteredColumns = ColumnDefinitions.Where(c => SelectedColumns.Contains(c.Id)).Where(ColumnFilter);
foreach (var column in filteredColumns)
if (_columnOrder.Count == 0)
{
foreach (var column in filteredColumns)
{
if (_columnWidths.TryGetValue(column.Id, out var value))
{
column.Width = value;
}
yield return column;
}
yield break;
}
var columnDictionary = filteredColumns.ToDictionary(c => c.Id);
foreach (var columnId in _columnOrder.OrderBy(c => c.Value).Select(c => c.Key))
{
if (!columnDictionary.TryGetValue(columnId, out var column))
{
continue;
}
if (_columnWidths.TryGetValue(column.Id, out var value))
{
column.Width = value;
@@ -280,7 +304,7 @@ namespace Lantean.QBTMud.Components.UI
public async Task ShowColumnOptionsDialog()
{
var result = await DialogService.ShowColumnsOptionsDialog(ColumnDefinitions.Where(ColumnFilter).ToList(), SelectedColumns, _columnWidths);
var result = await DialogService.ShowColumnsOptionsDialog(ColumnDefinitions.Where(ColumnFilter).ToList(), SelectedColumns, _columnWidths, _columnOrder);
if (result == default)
{
@@ -299,11 +323,17 @@ namespace Lantean.QBTMud.Components.UI
_columnWidths = result.ColumnWidths;
await LocalStorage.SetItemAsync(_columnWidthsStorageKey, _columnWidths);
}
if (!DictionaryEqual(_columnOrder, result.ColumnOrder))
{
_columnOrder = result.ColumnOrder;
await LocalStorage.SetItemAsync(_columnOrderStorageKey, _columnOrder);
}
}
private static bool DictionaryEqual(Dictionary<string, int?> left, Dictionary<string, int?> right)
private static bool DictionaryEqual<TKey, TValue>(Dictionary<TKey, TValue> left, Dictionary<TKey, TValue> right) where TKey : notnull
{
return left.Keys.Count == right.Keys.Count && left.Keys.All(k => right.ContainsKey(k) && left[k] == right[k]);
return left.Keys.Count == right.Keys.Count && left.Keys.All(k => right.ContainsKey(k) && Equals(left[k], right[k]));
}
private static string? GetColumnStyle(ColumnDefinition<T> column)

View File

@@ -328,13 +328,14 @@ namespace Lantean.QBTMud.Helpers
return tags;
}
public static async Task<(HashSet<string> SelectedColumns, Dictionary<string, int?> ColumnWidths)> ShowColumnsOptionsDialog<T>(this IDialogService dialogService, List<ColumnDefinition<T>> columnDefinitions, HashSet<string> selectedColumns, Dictionary<string, int?> widths)
public static async Task<(HashSet<string> SelectedColumns, Dictionary<string, int?> ColumnWidths, Dictionary<string, int> ColumnOrder)> ShowColumnsOptionsDialog<T>(this IDialogService dialogService, List<ColumnDefinition<T>> columnDefinitions, HashSet<string> selectedColumns, Dictionary<string, int?> widths, Dictionary<string, int> order)
{
var parameters = new DialogParameters
{
{ nameof(ColumnOptionsDialog<T>.Columns), columnDefinitions },
{ nameof(ColumnOptionsDialog<T>.SelectedColumns), selectedColumns },
{ nameof(ColumnOptionsDialog<T>.Widths), widths },
{ nameof(ColumnOptionsDialog<T>.Order), order },
};
var reference = await dialogService.ShowAsync<ColumnOptionsDialog<T>>("Column Options", parameters, FormDialogOptions);
@@ -344,7 +345,7 @@ namespace Lantean.QBTMud.Helpers
return default;
}
return ((HashSet<string>, Dictionary<string, int?>))dialogResult.Data;
return ((HashSet<string>, Dictionary<string, int?>, Dictionary<string, int>))dialogResult.Data;
}
public static async Task<bool> ShowConfirmDialog(this IDialogService dialogService, string title, string content)

View File

@@ -19,28 +19,28 @@ namespace Lantean.QBTMud.Helpers
{
if (seconds is null)
{
return "";
return string.Empty;
}
if (seconds == 8640000)
const long InfiniteEtaSentinelSeconds = 8_640_000; // ~100 days, used by qBittorrent for "infinite" ETA.
var value = seconds.Value;
if (value >= long.MaxValue || value >= TimeSpan.MaxValue.TotalSeconds || value == InfiniteEtaSentinelSeconds)
{
return "∞";
}
if (seconds < 60)
if (value <= 0)
{
return "< 1m";
}
TimeSpan time;
try
var time = TimeSpan.FromSeconds(value);
if (time.TotalMinutes < 1)
{
time = TimeSpan.FromSeconds(seconds.Value);
}
catch (OverflowException)
{
return "∞";
return "< 1m";
}
var sb = new StringBuilder();
if (prefix is not null)
{
@@ -83,6 +83,7 @@ namespace Lantean.QBTMud.Helpers
return sb.ToString();
}
/// <summary>
/// Formats a file size in bytes into an appropriate unit based on the size.
/// </summary>
@@ -129,7 +130,7 @@ namespace Lantean.QBTMud.Helpers
return "";
}
return Size(size);
return Size(size, prefix, suffix);
}
/// <summary>

View File

@@ -119,34 +119,34 @@ namespace Lantean.QBTMud.Helpers
switch (category)
{
case CATEGORY_ALL:
break;
return true;
case CATEGORY_UNCATEGORIZED:
if (!string.IsNullOrEmpty(torrent.Category))
{
return false;
}
break;
return true;
default:
if (string.IsNullOrEmpty(torrent.Category))
{
return false;
}
if (!useSubcategories)
{
if (torrent.Category != category)
{
return false;
}
else
{
if (!torrent.Category.StartsWith(category))
{
return false;
}
}
return string.Equals(torrent.Category, category, StringComparison.Ordinal);
}
break;
}
return true;
if (string.Equals(torrent.Category, category, StringComparison.Ordinal))
{
return true;
}
var prefix = string.Concat(category, "/");
return torrent.Category.StartsWith(prefix, StringComparison.Ordinal);
}
}
public static bool FilterTag(Torrent torrent, string tag)
@@ -207,7 +207,7 @@ namespace Lantean.QBTMud.Helpers
break;
case Status.Paused:
if (!state.Contains("paused") || !state.Contains("stopped"))
if (!state.Contains("paused") && !state.Contains("stopped"))
{
return false;
}

View File

@@ -0,0 +1,34 @@

namespace Lantean.QBTMud.Helpers
{
internal static class VersionHelper
{
private static int? _version;
private const int _defaultVersion = 5;
public static int DefaultVersion => _defaultVersion;
public static int GetMajorVersion(string? version)
{
if (_version is not null)
{
return _version.Value;
}
if (string.IsNullOrEmpty(version))
{
return _defaultVersion;
}
if (!Version.TryParse(version?.Replace("v", ""), out var theVersion))
{
return _defaultVersion;
}
_version = theVersion.Major;
return _version.Value;
}
}
}

View File

@@ -1,24 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<CompressionEnabled>false</CompressionEnabled>
<LangVersion>12</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<CompressionEnabled>false</CompressionEnabled>
<LangVersion>12</LangVersion>
</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.10" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.10" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
<PackageReference Include="MudBlazor" Version="7.15.0" />
<PackageReference Include="MudBlazor.ThemeManager" Version="2.1.0" />
<!-- added to fix vuln in dependency -->
<PackageReference Include="System.Text.Json" Version="8.0.5" />
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
<PackageReference Include="ByteSize" Version="2.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.5" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.5" />
<PackageReference Include="MudBlazor" Version="8.7.0" />
<PackageReference Include="MudBlazor.ThemeManager" Version="3.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -36,23 +36,23 @@
</CascadingValue>
</CascadingValue>
</CascadingValue>
<MudAppBar Bottom="true" Fixed="true" Elevation="0" Dense="true" Style="background-color: var(--mud-palette-dark-lighten);">
<MudAppBar Bottom="true" Fixed="true" Elevation="0" Dense="true" Style="background-color: var(--mud-palette-dark-lighten); z-index: 900">
@if (MainData?.LostConnection == true)
{
<MudText Class="mx-2 mb-1" Color="Color.Error">qBittorrent client is not reachable</MudText>
<MudText Class="mx-2 mb-1 d-none d-sm-flex" Color="Color.Error">qBittorrent client is not reachable</MudText>
}
<MudSpacer />
<MudText Class="mx-2 mb-1">@DisplayHelpers.Size(MainData?.ServerState.FreeSpaceOnDisk, "Free space: ")</MudText>
<MudDivider Vertical="true" />
<MudText Class="mx-2 mb-1">DHT @(MainData?.ServerState.DHTNodes ?? 0) nodes</MudText>
<MudDivider Vertical="true" />
<MudText Class="mx-2 mb-1 d-none d-sm-flex">@DisplayHelpers.Size(MainData?.ServerState.FreeSpaceOnDisk, "Free space: ")</MudText>
<MudDivider Vertical="true" Class="d-none d-sm-flex" />
<MudText Class="mx-2 mb-1 d-none d-sm-flex">DHT @(MainData?.ServerState.DHTNodes ?? 0) nodes</MudText>
<MudDivider Vertical="true" Class="d-none d-sm-flex" />
@{
var (icon, colour) = GetConnectionIcon(MainData?.ServerState.ConnectionStatus);
}
<MudIcon Class="mx-1 mb-1" Icon="@icon" Color="@colour" Title="MainData?.ServerState.ConnectionStatus" />
<MudDivider Vertical="true" />
<MudDivider Vertical="true" Class="" />
<MudIcon Class="mx-1 mb-1" Icon="@Icons.Material.Outlined.Speed" Color="@((MainData?.ServerState.UseAltSpeedLimits ?? false) ? Color.Error : Color.Success)" />
<MudDivider Vertical="true" />
<MudDivider Vertical="true" Class="" />
<MudIcon Class="ml-1 mb-1" Icon="@Icons.Material.Filled.KeyboardDoubleArrowDown" Color="Color.Success" />
<MudText Class="mr-1 mb-1">
@DisplayHelpers.Size(MainData?.ServerState.DownloadInfoSpeed, null, "/s")

View File

@@ -83,7 +83,7 @@ namespace Lantean.QBTMud.Layout
Preferences = await ApiClient.GetApplicationPreferences();
Version = await ApiClient.GetApplicationVersion();
var data = await ApiClient.GetMainData(_requestId);
MainData = DataManager.CreateMainData(data);
MainData = DataManager.CreateMainData(data, Version);
_requestId = data.ResponseId;
_refreshInterval = MainData.ServerState.RefreshInterval;
@@ -128,7 +128,7 @@ namespace Lantean.QBTMud.Layout
if (MainData is null || data.FullUpdate)
{
MainData = DataManager.CreateMainData(data);
MainData = DataManager.CreateMainData(data, Version);
}
else
{

View File

@@ -20,16 +20,18 @@
<MudIconButton Icon="@Icons.Material.Filled.Error" Color="Color.Default" OnClick="ToggleErrorDrawer" />
</MudBadge>
}
<MudSwitch T="bool" Label="Dark Mode" LabelPosition="LabelPosition.End" Value="IsDarkMode" ValueChanged="DarkModeChanged" Class="pl-3" />
<MudSwitch T="bool" Label="Dark Mode" LabelPlacement="Placement.End" Value="IsDarkMode" ValueChanged="DarkModeChanged" Class="pl-3" />
<Menu @ref="Menu" />
</MudAppBar>
<MudDrawer Open="ErrorDrawerOpen" ClipMode="DrawerClipMode.Docked" Elevation="2" Anchor="Anchor.Right">
<MudDrawer @bind-Open="ErrorDrawerOpen" ClipMode="DrawerClipMode.Docked" Elevation="2" Anchor="Anchor.Right">
<ErrorDisplay ErrorBoundary="ErrorBoundary" />
</MudDrawer>
<CascadingValue Value="Theme">
<CascadingValue Value="IsDarkMode" Name="IsDarkMode">
<CascadingValue Value="Menu">
@Body
<CascadingValue Value="DrawerOpen" Name="DrawerOpen">
@Body
</CascadingValue>
</CascadingValue>
</CascadingValue>
</CascadingValue>

View File

@@ -13,9 +13,6 @@ namespace Lantean.QBTMud.Layout
private bool _disposedValue;
[Inject]
protected NavigationManager NavigationManager { get; set; } = default!;
[Inject]
private IBrowserViewportService BrowserViewportService { get; set; } = default!;
@@ -78,21 +75,21 @@ namespace Lantean.QBTMud.Layout
{
IsDarkMode = isDarkMode.Value;
}
await MudThemeProvider.WatchSystemPreference(OnSystemPreferenceChanged);
await MudThemeProvider.WatchSystemDarkModeAsync(OnSystemDarkModeChanged);
await BrowserViewportService.SubscribeAsync(this, fireImmediately: true);
await InvokeAsync(StateHasChanged);
}
}
protected Task OnSystemPreferenceChanged(bool value)
protected Task OnSystemDarkModeChanged(bool value)
{
IsDarkMode = value;
return Task.CompletedTask;
}
public Task NotifyBrowserViewportChangeAsync(BrowserViewportEventArgs browserViewportEventArgs)
public async Task NotifyBrowserViewportChangeAsync(BrowserViewportEventArgs browserViewportEventArgs)
{
if (browserViewportEventArgs.Breakpoint == Breakpoint.Sm && DrawerOpen)
if (browserViewportEventArgs.Breakpoint <= Breakpoint.Sm)
{
DrawerOpen = false;
}
@@ -101,7 +98,17 @@ namespace Lantean.QBTMud.Layout
DrawerOpen = true;
}
return Task.CompletedTask;
if (ErrorBoundary?.Errors.Count > 0)
{
ErrorDrawerOpen = true;
}
else
{
await Task.Delay(250);
ErrorDrawerOpen = false;
}
await InvokeAsync(StateHasChanged);
}
protected void ToggleErrorDrawer()

View File

@@ -11,7 +11,8 @@
Dictionary<string, HashSet<string>> tagState,
Dictionary<string, HashSet<string>> categoriesState,
Dictionary<string, HashSet<string>> statusState,
Dictionary<string, HashSet<string>> trackersState)
Dictionary<string, HashSet<string>> trackersState,
int majorVersion)
{
Torrents = torrents.ToDictionary();
Tags = tags.ToHashSet();
@@ -22,6 +23,7 @@
CategoriesState = categoriesState;
StatusState = statusState;
TrackersState = trackersState;
MajorVersion = majorVersion;
}
public Dictionary<string, Torrent> Torrents { get; }
@@ -36,5 +38,6 @@
public Dictionary<string, HashSet<string>> TrackersState { get; }
public string? SelectedTorrentHash { get; set; }
public bool LostConnection { get; set; }
public int MajorVersion { get; }
}
}

View File

@@ -8,6 +8,7 @@
Completed,
Resumed,
Paused,
Stopped,
Active,
Inactive,
Stalled,
@@ -15,6 +16,6 @@
StalledDownloading,
Checking,
Errored,
Stopped
}
}

View File

@@ -12,89 +12,96 @@
<MudTabs Elevation="2" ApplyEffectsToContainer="true">
<MudTabPanel Text="About">
<div class="d-flex gap-4">
<MudImage Src="images/mascot.png" Alt="Mascot" Class="ma-6" Fluid ObjectFit="ObjectFit.None" ObjectPosition="ObjectPosition.LeftTop" Height="162" Width="94" />
<MudGrid Class="mx-0 mt-0 mb-3">
<MudItem xs="12">
<div class="d-flex gap-3">
<MudImage Src="images/qbittorrent32.png" Fluid ObjectFit="ObjectFit.None" Alt="QBT" Height="32" Width="32" /><MudText Typo="Typo.h6">qBittorrent @QBittorrentVersion</MudText>
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-3">
<MudGrid Class="mt-0 mb-4">
<MudItem xs="12" sm="3" md="2" lg="2" xl="1" Class="d-flex justify-center">
<MudImage Src="images/mascot.png" Alt="Mascot" Class="ma-6"
Fluid ObjectFit="ObjectFit.None" ObjectPosition="ObjectPosition.LeftTop"
Height="162" Width="94" />
</MudItem>
<MudItem xs="12" sm="9" md="10" lg="10" xl="11">
<div class="d-flex flex-column gap-2">
<div class="d-flex gap-3 align-items-center">
<MudImage Src="images/qbittorrent32.png" Fluid ObjectFit="ObjectFit.None"
Alt="QBT" Height="32" Width="32" />
<MudText Typo="Typo.h6">qBittorrent @QBittorrentVersion</MudText>
</div>
<MudText Typo="Typo.body1">
An advanced BitTorrent client programmed in C++, based on Qt toolkit and libtorrent-rasterbar.
</MudText>
<MudText Typo="Typo.body1">Copyright © 2006-2024 The qBittorrent project</MudText>
<div class="d-flex flex-wrap">
<MudText Typo="Typo.body1" Class="fw-bold">Home Page: </MudText>
<MudLink Href="https://www.qbittorrent.org" Target="_blank" Class="ms-2">
qbittorrent.org
</MudLink>
</div>
<div class="d-flex flex-wrap">
<MudText Typo="Typo.body1" Class="fw-bold">Bug Tracker: </MudText>
<MudLink Href="https://bugs.qbittorrent.org" Target="_blank" Class="ms-2">
bugs.qbittorrent.org
</MudLink>
</div>
<div class="d-flex flex-wrap">
<MudText Typo="Typo.body1" Class="fw-bold">Forum: </MudText>
<MudLink Href="https://forum.qbittorrent.org" Target="_blank" Class="ms-2">
forum.qbittorrent.org
</MudLink>
</div>
</div>
</MudItem>
<MudItem xs="12">
<MudText Typo="Typo.body1">An advanced BitTorrent client programmed in C++, based on Qt toolkit and libtorrent-rasterbar.</MudText>
</MudItem>
<MudItem xs="12">
<MudText Typo="Typo.body1">Copyright © 2006-2024 The qBittorrent project</MudText>
</MudItem>
<MudItem xs="2">
<MudText Typo="Typo.body1">Home Page</MudText>
</MudItem>
<MudItem xs="10">
<MudLink Href="https://www.qbittorrent.org" Target="https://www.qbittorrent.org">https://www.qbittorrent.org</MudLink>
</MudItem>
<MudItem xs="2">
<MudText Typo="Typo.body1">Bug Tracker</MudText>
</MudItem>
<MudItem xs="10">
<MudLink Href="https://bugs.qbittorrent.org" Target="https://bugs.qbittorrent.org">https://bugs.qbittorrent.org</MudLink>
</MudItem>
<MudItem xs="2">
<MudText Typo="Typo.body1">Forum</MudText>
</MudItem>
<MudItem xs="10">
<MudLink Href="https://forum.qbittorrent.org" Target="https://forum.qbittorrent.org">https://forum.qbittorrent.org</MudLink>
</MudItem>
</MudGrid>
</div>
</MudContainer>
</MudTabPanel>
<MudTabPanel Text="Authors">
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-3">
<MudText Typo="Typo.body1" Class="py-1">Current maintainer</MudText>
<MudText Typo="Typo.h5" Class="py-1">Current maintainer</MudText>
<MudGrid Class="mt-0 mb-4">
<MudItem xs="2">
<MudText Typo="Typo.body1">Name</MudText>
<MudItem xs="12" md="2">
<MudText Typo="Typo.h6">Name</MudText>
</MudItem>
<MudItem xs="10">
<MudItem xs="12" md="10">
<MudText Typo="Typo.body1">Sledgehammer999</MudText>
</MudItem>
<MudItem xs="2">
<MudText Typo="Typo.body1">Nationality</MudText>
<MudItem xs="12" md="2">
<MudText Typo="Typo.h6">Nationality</MudText>
</MudItem>
<MudItem xs="10">
<MudItem xs="12" md="10">
<MudText Typo="Typo.body1">Greece</MudText>
</MudItem>
<MudItem xs="2">
<MudText Typo="Typo.body1">E-mail</MudText>
<MudItem xs="12" md="2">
<MudText Typo="Typo.h6">E-mail</MudText>
</MudItem>
<MudItem xs="10">
<MudItem xs="12" md="10">
<MudLink Href="mailto:sledgehammer999@qbittorrent.org">sledgehammer999@qbittorrent.org</MudLink>
</MudItem>
</MudGrid>
<MudText Typo="Typo.body1" Class="py-1">Original author</MudText>
<MudText Typo="Typo.h5" Class="py-1">Original author</MudText>
<MudGrid Class="mt-0 mb-4">
<MudItem xs="2">
<MudText Typo="Typo.body1">Name</MudText>
<MudItem xs="12" md="2">
<MudText Typo="Typo.h6">Name</MudText>
</MudItem>
<MudItem xs="10">
<MudItem xs="12" md="10">
<MudText Typo="Typo.body1">Christophe Dumez</MudText>
</MudItem>
<MudItem xs="2">
<MudText Typo="Typo.body1">Nationality</MudText>
<MudItem xs="12" md="2">
<MudText Typo="Typo.h6">Nationality</MudText>
</MudItem>
<MudItem xs="10">
<MudItem xs="12" md="10">
<MudText Typo="Typo.body1">France</MudText>
</MudItem>
<MudItem xs="2">
<MudText Typo="Typo.body1">E-mail</MudText>
<MudItem xs="12" md="2">
<MudText Typo="Typo.h6">E-mail</MudText>
</MudItem>
<MudItem xs="10">
<MudItem xs="12" md="10">
<MudLink Href="mailto:chris@qbittorrent.org">chris@qbittorrent.org</MudLink>
</MudItem>
</MudGrid>
@@ -118,7 +125,7 @@
(the list might not be up to date)
</MudText>
<MudList T="string" ReadOnly>
<MudListItem Icon="@Icons.Material.Filled.Circle" IconColor="Color.Info"><u>Arabic:</u> SDERAWI (abz8868@msn.com), sn51234 (nesseyan@gmail.com) and Ibrahim Saed ibraheem_alex(Transifex)</MudListItem>
<MudListItem Icon="@Icons.Material.Filled.Circle"><u>Arabic:</u> SDERAWI (abz8868@msn.com), sn51234 (nesseyan@gmail.com) and Ibrahim Saed ibraheem_alex(Transifex)</MudListItem>
<MudListItem Icon="@Icons.Material.Filled.Circle"><u>Armenian:</u> Hrant Ohanyan (hrantohanyan@mail.am)</MudListItem>
<MudListItem Icon="@Icons.Material.Filled.Circle"><u>Basque:</u> Xabier Aramendi (azpidatziak@gmail.com)</MudListItem>
<MudListItem Icon="@Icons.Material.Filled.Circle"><u>Belarusian:</u> Mihas Varantsou (meequz@gmail.com)</MudListItem>
@@ -1058,38 +1065,38 @@
<MudText Typo="Typo.body1" Class="py-1">qBittorrent was built with the following libraries:</MudText>
<MudGrid Class="mt-1 mb-4">
<MudItem xs="2">
<MudItem xs="3">
<MudText Typo="Typo.body1">Qt</MudText>
</MudItem>
<MudItem xs="10">
<MudItem xs="9">
<MudText Typo="Typo.body1">@QtVersion</MudText>
</MudItem>
<MudItem xs="2">
<MudItem xs="3">
<MudText Typo="Typo.body1">Libtorrent</MudText>
</MudItem>
<MudItem xs="10">
<MudItem xs="9">
<MudText Typo="Typo.body1">@LibtorrentVersion</MudText>
</MudItem>
<MudItem xs="2">
<MudItem xs="3">
<MudText Typo="Typo.body1">Boost</MudText>
</MudItem>
<MudItem xs="10">
<MudItem xs="9">
<MudText Typo="Typo.body1">@BoostVersion</MudText>
</MudItem>
<MudItem xs="2">
<MudItem xs="3">
<MudText Typo="Typo.body1">OpenSSL</MudText>
</MudItem>
<MudItem xs="10">
<MudItem xs="9">
<MudText Typo="Typo.body1">@OpensslVersion</MudText>
</MudItem>
<MudItem xs="2">
<MudItem xs="3">
<MudText Typo="Typo.body1">zlib</MudText>
</MudItem>
<MudItem xs="10">
<MudItem xs="9">
<MudText Typo="Typo.body1">@ZlibVersion</MudText>
</MudItem>
</MudGrid>

View File

@@ -1,6 +1,7 @@
@page "/details/{hash}"
@layout DetailsLayout
<div style="overflow-x: auto; white-space: nowrap; width: 100%;">
<MudToolBar Gutters="false" Dense="true">
@if (!DrawerOpen)
{
@@ -14,6 +15,7 @@
<MudDivider Vertical="true" />
<MudText Class="pl-5 no-wrap">@Name</MudText>
</MudToolBar>
</div>
@if (ShowTabs)
{

View File

@@ -49,7 +49,7 @@ namespace Lantean.QBTMud.Pages
protected override Task OnInitializedAsync()
{
return DoLogin("admin", "eBGJzbjkJ");
return DoLogin("admin", "5FUM5pATq");
}
#endif

View File

@@ -18,7 +18,7 @@
<MudItem xs="12" md="4">
<MudTextField T="string" Label="Criteria" @bind-Value="Model.SearchText" Variant="Variant.Outlined" />
</MudItem>
<MudItem xs="2" md="3">
<MudItem xs="12" md="3">
<MudSelect T="string" Label="Categories" @bind-Value="Model.SelectedCategory" Variant="Variant.Outlined">
@foreach (var (value, name) in Categories)
{
@@ -30,17 +30,21 @@
}
</MudSelect>
</MudItem>
<MudItem xs="2" md="3">
<MudItem xs="12" md="3">
<MudSelect T="string" Label="Plugins" @bind-Value="Model.SelectedPlugin" Variant="Variant.Outlined">
<MudSelectItem Value="@("all")">All</MudSelectItem>
<MudDivider />
@if (Plugins.Count > 0)
{
<MudDivider />
}
@foreach (var (value, name) in Plugins)
{
<MudSelectItem Value="value">@name</MudSelectItem>
}
</MudSelect>
</MudItem>
<MudItem xs="2" md="2">
<MudItem xs="12" md="2">
<MudButton ButtonType="ButtonType.Submit" FullWidth="true" Color="Color.Primary" EndIcon="@Icons.Material.Filled.Search" Variant="Variant.Filled" Class="mt-6">@(_searchId is null ? "Search" : "Stop")</MudButton>
</MudItem>

View File

@@ -1,12 +1,13 @@
@page "/"
@layout ListLayout
<ContextMenu @ref="ContextMenu" Dense="true" AdjustmentX="@(DrawerOpen ? -235 : 0)">
<ContextMenu @ref="ContextMenu" Dense="true" RelativeWidth="DropdownWidth.Ignore" AdjustmentX="-242" AdjustmentY="0">
<MudMenuItem Icon="@Icons.Material.Outlined.Info" IconColor="Color.Inherit" OnClick="ShowTorrentContextMenu">View torrent details</MudMenuItem>
<MudDivider />
<TorrentActions RenderType="RenderType.MenuItems" Hashes="GetContextMenuTargetHashes()" PrimaryHash="@(ContextMenuItem?.Hash)" Torrents="MainData.Torrents" Preferences="Preferences" />
</ContextMenu>
<div style="overflow-x: auto; white-space: nowrap; width: 100%;">
<MudToolBar Gutters="false" Dense="true">
<MudIconButton Icon="@Icons.Material.Outlined.AddLink" OnClick="AddTorrentLink" title="Add torrent link" />
<MudIconButton Icon="@Icons.Material.Outlined.AddCircle" OnClick="AddTorrentFile" title="Add torrent file" />
@@ -18,6 +19,7 @@
<MudSpacer />
<MudTextField Value="SearchText" TextChanged="SearchTextChanged" Immediate="true" DebounceInterval="1000" Placeholder="Filter torrent list" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"></MudTextField>
</MudToolBar>
</div>
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="ma-0 pa-0">
<DynamicTable

View File

@@ -193,7 +193,7 @@ namespace Lantean.QBTMud.Pages
public static List<ColumnDefinition<Torrent>> ColumnsDefinitions { get; } =
[
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("#", t => t.Priority),
ColumnDefinitionHelper.CreateColumnDefinition("Icon", t => t.State, IconColumn, iconOnly: true, width: 25),
ColumnDefinitionHelper.CreateColumnDefinition("Icon", t => t.State, IconColumn, iconOnly: true, width: 25, tdClass: "table-icon"),
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Name", t => t.Name, width: 400),
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Size", t => t.Size, t => DisplayHelpers.Size(t.Size)),
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Total Size", t => t.TotalSize, t => DisplayHelpers.Size(t.TotalSize), enabled: false),

View File

@@ -5,7 +5,7 @@ namespace Lantean.QBTMud.Services
{
public class DataManager : IDataManager
{
private static readonly Status[] _statuses = Enum.GetValues<Status>();
private static Status[]? _statusArray = null;
public PeerList CreatePeerList(QBitTorrentClient.Models.TorrentPeers torrentPeers)
{
@@ -25,8 +25,9 @@ namespace Lantean.QBTMud.Services
return peerList;
}
public MainData CreateMainData(QBitTorrentClient.Models.MainData mainData)
public MainData CreateMainData(QBitTorrentClient.Models.MainData mainData, string version)
{
var majorVersion = VersionHelper.GetMajorVersion(version);
var torrents = new Dictionary<string, Torrent>(mainData.Torrents?.Count ?? 0);
if (mainData.Torrents is not null)
{
@@ -38,12 +39,19 @@ namespace Lantean.QBTMud.Services
}
}
var tags = new List<string>(mainData.Tags?.Count ?? 0);
var tags = new List<string>();
if (mainData.Tags is not null)
{
var seenTags = new HashSet<string>(StringComparer.Ordinal);
foreach (var tag in mainData.Tags)
{
tags.Add(tag);
var normalizedTag = NormalizeTag(tag);
if (string.IsNullOrEmpty(normalizedTag) || !seenTags.Add(normalizedTag))
{
continue;
}
tags.Add(normalizedTag);
}
}
@@ -87,8 +95,9 @@ namespace Lantean.QBTMud.Services
categoriesState.Add(category, torrents.Values.Where(t => FilterHelper.FilterCategory(t, category, serverState.UseSubcategories)).ToHashesHashSet());
}
var statusState = new Dictionary<string, HashSet<string>>(_statuses.Length + 2);
foreach (var status in _statuses)
var statuses = GetStatuses(majorVersion).ToArray();
var statusState = new Dictionary<string, HashSet<string>>(statuses.Length + 2);
foreach (var status in statuses)
{
statusState.Add(status.ToString(), torrents.Values.Where(t => FilterHelper.FilterStatus(t, status)).ToHashesHashSet());
}
@@ -101,7 +110,7 @@ namespace Lantean.QBTMud.Services
trackersState.Add(tracker, torrents.Values.Where(t => FilterHelper.FilterTracker(t, tracker)).ToHashesHashSet());
}
var torrentList = new MainData(torrents, tags, categories, trackers, serverState, tagState, categoriesState, statusState, trackersState);
var torrentList = new MainData(torrents, tags, categories, trackers, serverState, tagState, categoriesState, statusState, trackersState, majorVersion);
return torrentList;
}
@@ -155,8 +164,14 @@ namespace Lantean.QBTMud.Services
{
foreach (var tag in mainData.TagsRemoved)
{
torrentList.Tags.Remove(tag);
torrentList.TagState.Remove(tag);
var normalizedTag = NormalizeTag(tag);
if (string.IsNullOrEmpty(normalizedTag))
{
continue;
}
torrentList.Tags.Remove(normalizedTag);
torrentList.TagState.Remove(normalizedTag);
}
}
@@ -198,7 +213,18 @@ namespace Lantean.QBTMud.Services
{
foreach (var tag in mainData.Tags)
{
torrentList.Tags.Add(tag);
var normalizedTag = NormalizeTag(tag);
if (string.IsNullOrEmpty(normalizedTag))
{
continue;
}
torrentList.Tags.Add(normalizedTag);
var matchingHashes = torrentList.Torrents
.Where(pair => FilterHelper.FilterTag(pair.Value, normalizedTag))
.Select(pair => pair.Key)
.ToHashSet();
torrentList.TagState[normalizedTag] = matchingHashes;
}
}
@@ -206,7 +232,7 @@ namespace Lantean.QBTMud.Services
{
foreach (var (url, hashes) in mainData.Trackers)
{
if (!torrentList.Trackers.TryGetValue(url, out var existingHashes))
if (!torrentList.Trackers.TryGetValue(url, out _))
{
torrentList.Trackers.Add(url, hashes);
}
@@ -225,7 +251,7 @@ namespace Lantean.QBTMud.Services
{
var newTorrent = CreateTorrent(hash, torrent);
torrentList.Torrents.Add(hash, newTorrent);
AddTorrentToStates(torrentList, hash);
AddTorrentToStates(torrentList, hash, torrentList.MajorVersion);
}
else
{
@@ -241,7 +267,7 @@ namespace Lantean.QBTMud.Services
}
}
private static void AddTorrentToStates(MainData torrentList, string hash)
private static void AddTorrentToStates(MainData torrentList, string hash, int version)
{
var torrent = torrentList.Torrents[hash];
@@ -271,7 +297,7 @@ namespace Lantean.QBTMud.Services
value.AddIfTrue(hash, FilterHelper.FilterCategory(torrent, category, torrentList.ServerState.UseSubcategories));
}
foreach (var status in _statuses)
foreach (var status in GetStatuses(version))
{
torrentList.StatusState[status.ToString()].AddIfTrue(hash, FilterHelper.FilterStatus(torrent, status));
}
@@ -289,6 +315,25 @@ namespace Lantean.QBTMud.Services
}
}
private static Status[] GetStatuses(int version)
{
if (_statusArray is not null)
{
return _statusArray;
}
if (version == 5)
{
_statusArray = Enum.GetValues<Status>().Where(s => s != Status.Paused).ToArray();
}
else
{
_statusArray = Enum.GetValues<Status>().Where(s => s != Status.Stopped).ToArray();
}
return _statusArray;
}
private static void UpdateTorrentStates(MainData torrentList, string hash)
{
var torrent = torrentList.Torrents[hash];
@@ -317,7 +362,7 @@ namespace Lantean.QBTMud.Services
value.AddIfTrueOrRemove(hash, FilterHelper.FilterCategory(torrent, category, torrentList.ServerState.UseSubcategories));
}
foreach (var status in _statuses)
foreach (var status in GetStatuses(torrentList.MajorVersion))
{
torrentList.StatusState[status.ToString()].AddIfTrueOrRemove(hash, FilterHelper.FilterStatus(torrent, status));
}
@@ -361,7 +406,7 @@ namespace Lantean.QBTMud.Services
categoryState.RemoveIfTrue(hash, FilterHelper.FilterCategory(torrent, category, torrentList.ServerState.UseSubcategories));
}
foreach (var status in _statuses)
foreach (var status in GetStatuses(torrentList.MajorVersion))
{
if (!torrentList.StatusState.TryGetValue(status.ToString(), out var statusState))
{
@@ -487,6 +532,12 @@ namespace Lantean.QBTMud.Services
public Torrent CreateTorrent(string hash, QBitTorrentClient.Models.Torrent torrent)
{
var normalizedTags = torrent.Tags?
.Select(NormalizeTag)
.Where(static tag => !string.IsNullOrEmpty(tag))
.ToList()
?? new List<string>();
return new Torrent(
hash,
torrent.AddedOn.GetValueOrDefault(),
@@ -527,7 +578,7 @@ namespace Lantean.QBTMud.Services
torrent.Size.GetValueOrDefault(),
torrent.State!,
torrent.SuperSeeding.GetValueOrDefault(),
torrent.Tags!,
normalizedTags,
torrent.TimeActive.GetValueOrDefault(),
torrent.TotalSize.GetValueOrDefault(),
torrent.Tracker!,
@@ -540,6 +591,19 @@ namespace Lantean.QBTMud.Services
torrent.MaxInactiveSeedingTime.GetValueOrDefault());
}
private static string NormalizeTag(string? tag)
{
if (string.IsNullOrEmpty(tag))
{
return string.Empty;
}
var separatorIndex = tag.IndexOf('\t');
var normalized = (separatorIndex >= 0) ? tag[..separatorIndex] : tag;
return normalized.Trim();
}
private static void UpdateCategory(Category existingCategory, QBitTorrentClient.Models.Category category)
{
existingCategory.SavePath = category.SavePath ?? existingCategory.SavePath;
@@ -588,7 +652,14 @@ namespace Lantean.QBTMud.Services
if (torrent.Tags is not null)
{
existingTorrent.Tags.Clear();
existingTorrent.Tags.AddRange(torrent.Tags);
foreach (var tag in torrent.Tags)
{
var normalizedTag = NormalizeTag(tag);
if (!string.IsNullOrEmpty(normalizedTag))
{
existingTorrent.Tags.Add(normalizedTag);
}
}
}
existingTorrent.TimeActive = torrent.TimeActive ?? existingTorrent.TimeActive;
existingTorrent.TotalSize = torrent.TotalSize ?? existingTorrent.TotalSize;
@@ -689,13 +760,18 @@ namespace Lantean.QBTMud.Services
original = new QBitTorrentClient.Models.UpdatePreferences
{
AddToTopOfQueue = changed.AddToTopOfQueue,
AddStoppedEnabled = changed.AddStoppedEnabled,
AddTrackers = changed.AddTrackers,
AddTrackersEnabled = changed.AddTrackersEnabled,
AddTrackersFromUrlEnabled = changed.AddTrackersFromUrlEnabled,
AddTrackersUrl = changed.AddTrackersUrl,
AddTrackersUrlList = changed.AddTrackersUrlList,
AltDlLimit = changed.AltDlLimit,
AltUpLimit = changed.AltUpLimit,
AlternativeWebuiEnabled = changed.AlternativeWebuiEnabled,
AlternativeWebuiPath = changed.AlternativeWebuiPath,
AnnounceIp = changed.AnnounceIp,
AnnouncePort = changed.AnnouncePort,
AnnounceToAllTiers = changed.AnnounceToAllTiers,
AnnounceToAllTrackers = changed.AnnounceToAllTrackers,
AnonymousMode = changed.AnonymousMode,
@@ -715,11 +791,14 @@ namespace Lantean.QBTMud.Services
BypassAuthSubnetWhitelistEnabled = changed.BypassAuthSubnetWhitelistEnabled,
BypassLocalAuth = changed.BypassLocalAuth,
CategoryChangedTmmEnabled = changed.CategoryChangedTmmEnabled,
ConfirmTorrentDeletion = changed.ConfirmTorrentDeletion,
ConfirmTorrentRecheck = changed.ConfirmTorrentRecheck,
CheckingMemoryUse = changed.CheckingMemoryUse,
ConnectionSpeed = changed.ConnectionSpeed,
CurrentInterfaceAddress = changed.CurrentInterfaceAddress,
CurrentInterfaceName = changed.CurrentInterfaceName,
CurrentNetworkInterface = changed.CurrentNetworkInterface,
DeleteTorrentContentFiles = changed.DeleteTorrentContentFiles,
Dht = changed.Dht,
DiskCache = changed.DiskCache,
DiskCacheTtl = changed.DiskCacheTtl,
@@ -755,6 +834,7 @@ namespace Lantean.QBTMud.Services
FileLogPath = changed.FileLogPath,
FilePoolSize = changed.FilePoolSize,
HashingThreads = changed.HashingThreads,
HostnameCacheTtl = changed.HostnameCacheTtl,
I2pAddress = changed.I2pAddress,
I2pEnabled = changed.I2pEnabled,
I2pInboundLength = changed.I2pInboundLength,
@@ -764,6 +844,7 @@ namespace Lantean.QBTMud.Services
I2pOutboundQuantity = changed.I2pOutboundQuantity,
I2pPort = changed.I2pPort,
IdnSupportEnabled = changed.IdnSupportEnabled,
IgnoreSslErrors = changed.IgnoreSslErrors,
IncompleteFilesExt = changed.IncompleteFilesExt,
IpFilterEnabled = changed.IpFilterEnabled,
IpFilterPath = changed.IpFilterPath,
@@ -837,6 +918,7 @@ namespace Lantean.QBTMud.Services
SavePath = changed.SavePath,
SavePathChangedTmmEnabled = changed.SavePathChangedTmmEnabled,
SaveResumeDataInterval = changed.SaveResumeDataInterval,
SaveStatisticsInterval = changed.SaveStatisticsInterval,
ScanDirs = changed.ScanDirs,
ScheduleFromHour = changed.ScheduleFromHour,
ScheduleFromMin = changed.ScheduleFromMin,
@@ -853,13 +935,17 @@ namespace Lantean.QBTMud.Services
SocketBacklogSize = changed.SocketBacklogSize,
SocketReceiveBufferSize = changed.SocketReceiveBufferSize,
SocketSendBufferSize = changed.SocketSendBufferSize,
SslEnabled = changed.SslEnabled,
SslListenPort = changed.SslListenPort,
SsrfMitigation = changed.SsrfMitigation,
StartPausedEnabled = changed.StartPausedEnabled,
StatusBarExternalIp = changed.StatusBarExternalIp,
StopTrackerTimeout = changed.StopTrackerTimeout,
TempPath = changed.TempPath,
TempPathEnabled = changed.TempPathEnabled,
TorrentChangedTmmEnabled = changed.TorrentChangedTmmEnabled,
TorrentContentLayout = changed.TorrentContentLayout,
TorrentContentRemoveOption = changed.TorrentContentRemoveOption,
TorrentFileSizeLimit = changed.TorrentFileSizeLimit,
TorrentStopCondition = changed.TorrentStopCondition,
UpLimit = changed.UpLimit,
@@ -870,9 +956,11 @@ namespace Lantean.QBTMud.Services
UseCategoryPathsInManualMode = changed.UseCategoryPathsInManualMode,
UseHttps = changed.UseHttps,
UseSubcategories = changed.UseSubcategories,
UseUnwantedFolder = changed.UseUnwantedFolder,
UtpTcpMixedMode = changed.UtpTcpMixedMode,
ValidateHttpsTrackerCertificate = changed.ValidateHttpsTrackerCertificate,
WebUiAddress = changed.WebUiAddress,
WebUiApiKey = changed.WebUiApiKey,
WebUiBanDuration = changed.WebUiBanDuration,
WebUiClickjackingProtectionEnabled = changed.WebUiClickjackingProtectionEnabled,
WebUiCsrfProtectionEnabled = changed.WebUiCsrfProtectionEnabled,
@@ -895,13 +983,18 @@ namespace Lantean.QBTMud.Services
else
{
original.AddToTopOfQueue = changed.AddToTopOfQueue ?? original.AddToTopOfQueue;
original.AddStoppedEnabled = changed.AddStoppedEnabled ?? original.AddStoppedEnabled;
original.AddTrackers = changed.AddTrackers ?? original.AddTrackers;
original.AddTrackersEnabled = changed.AddTrackersEnabled ?? original.AddTrackersEnabled;
original.AddTrackersFromUrlEnabled = changed.AddTrackersFromUrlEnabled ?? original.AddTrackersFromUrlEnabled;
original.AddTrackersUrl = changed.AddTrackersUrl ?? original.AddTrackersUrl;
original.AddTrackersUrlList = changed.AddTrackersUrlList ?? original.AddTrackersUrlList;
original.AltDlLimit = changed.AltDlLimit ?? original.AltDlLimit;
original.AltUpLimit = changed.AltUpLimit ?? original.AltUpLimit;
original.AlternativeWebuiEnabled = changed.AlternativeWebuiEnabled ?? original.AlternativeWebuiEnabled;
original.AlternativeWebuiPath = changed.AlternativeWebuiPath ?? original.AlternativeWebuiPath;
original.AnnounceIp = changed.AnnounceIp ?? original.AnnounceIp;
original.AnnouncePort = changed.AnnouncePort ?? original.AnnouncePort;
original.AnnounceToAllTiers = changed.AnnounceToAllTiers ?? original.AnnounceToAllTiers;
original.AnnounceToAllTrackers = changed.AnnounceToAllTrackers ?? original.AnnounceToAllTrackers;
original.AnonymousMode = changed.AnonymousMode ?? original.AnonymousMode;
@@ -921,11 +1014,14 @@ namespace Lantean.QBTMud.Services
original.BypassAuthSubnetWhitelistEnabled = changed.BypassAuthSubnetWhitelistEnabled ?? original.BypassAuthSubnetWhitelistEnabled;
original.BypassLocalAuth = changed.BypassLocalAuth ?? original.BypassLocalAuth;
original.CategoryChangedTmmEnabled = changed.CategoryChangedTmmEnabled ?? original.CategoryChangedTmmEnabled;
original.ConfirmTorrentDeletion = changed.ConfirmTorrentDeletion ?? original.ConfirmTorrentDeletion;
original.ConfirmTorrentRecheck = changed.ConfirmTorrentRecheck ?? original.ConfirmTorrentRecheck;
original.CheckingMemoryUse = changed.CheckingMemoryUse ?? original.CheckingMemoryUse;
original.ConnectionSpeed = changed.ConnectionSpeed ?? original.ConnectionSpeed;
original.CurrentInterfaceAddress = changed.CurrentInterfaceAddress ?? original.CurrentInterfaceAddress;
original.CurrentInterfaceName = changed.CurrentInterfaceName ?? original.CurrentInterfaceName;
original.CurrentNetworkInterface = changed.CurrentNetworkInterface ?? original.CurrentNetworkInterface;
original.DeleteTorrentContentFiles = changed.DeleteTorrentContentFiles ?? original.DeleteTorrentContentFiles;
original.Dht = changed.Dht ?? original.Dht;
original.DiskCache = changed.DiskCache ?? original.DiskCache;
original.DiskCacheTtl = changed.DiskCacheTtl ?? original.DiskCacheTtl;
@@ -961,6 +1057,7 @@ namespace Lantean.QBTMud.Services
original.FileLogPath = changed.FileLogPath ?? original.FileLogPath;
original.FilePoolSize = changed.FilePoolSize ?? original.FilePoolSize;
original.HashingThreads = changed.HashingThreads ?? original.HashingThreads;
original.HostnameCacheTtl = changed.HostnameCacheTtl ?? original.HostnameCacheTtl;
original.I2pAddress = changed.I2pAddress ?? original.I2pAddress;
original.I2pEnabled = changed.I2pEnabled ?? original.I2pEnabled;
original.I2pInboundLength = changed.I2pInboundLength ?? original.I2pInboundLength;
@@ -970,6 +1067,7 @@ namespace Lantean.QBTMud.Services
original.I2pOutboundQuantity = changed.I2pOutboundQuantity ?? original.I2pOutboundQuantity;
original.I2pPort = changed.I2pPort ?? original.I2pPort;
original.IdnSupportEnabled = changed.IdnSupportEnabled ?? original.IdnSupportEnabled;
original.IgnoreSslErrors = changed.IgnoreSslErrors ?? original.IgnoreSslErrors;
original.IncompleteFilesExt = changed.IncompleteFilesExt ?? original.IncompleteFilesExt;
original.IpFilterEnabled = changed.IpFilterEnabled ?? original.IpFilterEnabled;
original.IpFilterPath = changed.IpFilterPath ?? original.IpFilterPath;
@@ -1043,6 +1141,7 @@ namespace Lantean.QBTMud.Services
original.SavePath = changed.SavePath ?? original.SavePath;
original.SavePathChangedTmmEnabled = changed.SavePathChangedTmmEnabled ?? original.SavePathChangedTmmEnabled;
original.SaveResumeDataInterval = changed.SaveResumeDataInterval ?? original.SaveResumeDataInterval;
original.SaveStatisticsInterval = changed.SaveStatisticsInterval ?? original.SaveStatisticsInterval;
original.ScanDirs = changed.ScanDirs ?? original.ScanDirs;
original.ScheduleFromHour = changed.ScheduleFromHour ?? original.ScheduleFromHour;
original.ScheduleFromMin = changed.ScheduleFromMin ?? original.ScheduleFromMin;
@@ -1059,13 +1158,17 @@ namespace Lantean.QBTMud.Services
original.SocketBacklogSize = changed.SocketBacklogSize ?? original.SocketBacklogSize;
original.SocketReceiveBufferSize = changed.SocketReceiveBufferSize ?? original.SocketReceiveBufferSize;
original.SocketSendBufferSize = changed.SocketSendBufferSize ?? original.SocketSendBufferSize;
original.SslEnabled = changed.SslEnabled ?? original.SslEnabled;
original.SslListenPort = changed.SslListenPort ?? original.SslListenPort;
original.SsrfMitigation = changed.SsrfMitigation ?? original.SsrfMitigation;
original.StartPausedEnabled = changed.StartPausedEnabled ?? original.StartPausedEnabled;
original.StatusBarExternalIp = changed.StatusBarExternalIp ?? original.StatusBarExternalIp;
original.StopTrackerTimeout = changed.StopTrackerTimeout ?? original.StopTrackerTimeout;
original.TempPath = changed.TempPath ?? original.TempPath;
original.TempPathEnabled = changed.TempPathEnabled ?? original.TempPathEnabled;
original.TorrentChangedTmmEnabled = changed.TorrentChangedTmmEnabled ?? original.TorrentChangedTmmEnabled;
original.TorrentContentLayout = changed.TorrentContentLayout ?? original.TorrentContentLayout;
original.TorrentContentRemoveOption = changed.TorrentContentRemoveOption ?? original.TorrentContentRemoveOption;
original.TorrentFileSizeLimit = changed.TorrentFileSizeLimit ?? original.TorrentFileSizeLimit;
original.TorrentStopCondition = changed.TorrentStopCondition ?? original.TorrentStopCondition;
original.UpLimit = changed.UpLimit ?? original.UpLimit;
@@ -1076,9 +1179,11 @@ namespace Lantean.QBTMud.Services
original.UseCategoryPathsInManualMode = changed.UseCategoryPathsInManualMode ?? original.UseCategoryPathsInManualMode;
original.UseHttps = changed.UseHttps ?? original.UseHttps;
original.UseSubcategories = changed.UseSubcategories ?? original.UseSubcategories;
original.UseUnwantedFolder = changed.UseUnwantedFolder ?? original.UseUnwantedFolder;
original.UtpTcpMixedMode = changed.UtpTcpMixedMode ?? original.UtpTcpMixedMode;
original.ValidateHttpsTrackerCertificate = changed.ValidateHttpsTrackerCertificate ?? original.ValidateHttpsTrackerCertificate;
original.WebUiAddress = changed.WebUiAddress ?? original.WebUiAddress;
original.WebUiApiKey = changed.WebUiApiKey ?? original.WebUiApiKey;
original.WebUiBanDuration = changed.WebUiBanDuration ?? original.WebUiBanDuration;
original.WebUiClickjackingProtectionEnabled = changed.WebUiClickjackingProtectionEnabled ?? original.WebUiClickjackingProtectionEnabled;
original.WebUiCsrfProtectionEnabled = changed.WebUiCsrfProtectionEnabled ?? original.WebUiCsrfProtectionEnabled;
@@ -1154,4 +1259,4 @@ namespace Lantean.QBTMud.Services
return new RssList(feeds, articles);
}
}
}
}

View File

@@ -4,7 +4,7 @@ namespace Lantean.QBTMud.Services
{
public interface IDataManager
{
MainData CreateMainData(QBitTorrentClient.Models.MainData mainData);
MainData CreateMainData(QBitTorrentClient.Models.MainData mainData, string version);
Torrent CreateTorrent(string hash, QBitTorrentClient.Models.Torrent torrent);

View File

@@ -155,7 +155,7 @@ code {
}
.torrent-list .mud-table-container {
height: calc(100vh - 149px);
height: calc(100vh - 160px);
}
.file-list .mud-table-container {
@@ -240,4 +240,19 @@ td .folder-button {
.mud-dialog .mud-dialog-content {
padding-top: 4px !important;
}
.icon-menu-dense {
padding-top: 2px;
padding-bottom: 2px;
}
.table-icon {
width: 25px;
max-width: 25px;
padding: 0 8px !important;
}
.mud-popover .mud-divider:last-child {
display: none;
}

View File

@@ -9,12 +9,12 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:ital,opsz,wght@0,6..12,200..1000;1,6..12,200..1000&display=swap" rel="stylesheet">
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
<link rel="stylesheet" href="css/app.css" />
<link href="./_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
<link rel="stylesheet" href="./css/app.css" />
<link rel="icon" type="image/png" href="images/qbittorrent32.png" />
<link rel="icon" href="images/qbittorrent-tray.svg">
<link rel="mask-icon" href="images/qbittorrent-tray.svg" color="#000000">
<link rel="apple-touch-icon" href="images/qbittorrent32.png">
<link rel="icon" href="./images/qbittorrent-tray.svg">
<link rel="mask-icon" href="./images/qbittorrent-tray.svg" color="#000000">
<link rel="apple-touch-icon" href="./images/qbittorrent32.png">
</head>
<body>
@@ -31,10 +31,10 @@
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webassembly.js"></script>
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
<script src="js/piecesbar.js"></script>
<script src="js/interop.js"></script>
<script src="./_framework/blazor.webassembly.js"></script>
<script src="./_content/MudBlazor/MudBlazor.min.js"></script>
<script src="./js/piecesbar.js"></script>
<script src="./js/interop.js"></script>
</body>
</html>

View File

@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>

View File

@@ -112,7 +112,7 @@ namespace Lantean.QBitTorrentClient.Models
int maxConnecPerTorrent,
int maxInactiveSeedingTime,
bool maxInactiveSeedingTimeEnabled,
int maxRatio,
float maxRatio,
int maxRatioAct,
bool maxRatioEnabled,
int maxSeedingTime,
@@ -429,12 +429,24 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("add_to_top_of_queue")]
public bool AddToTopOfQueue { get; }
[JsonPropertyName("add_stopped_enabled")]
public bool AddStoppedEnabled { get; init; }
[JsonPropertyName("add_trackers")]
public string AddTrackers { get; }
[JsonPropertyName("add_trackers_enabled")]
public bool AddTrackersEnabled { get; }
[JsonPropertyName("add_trackers_from_url_enabled")]
public bool AddTrackersFromUrlEnabled { get; init; }
[JsonPropertyName("add_trackers_url")]
public string? AddTrackersUrl { get; init; }
[JsonPropertyName("add_trackers_url_list")]
public string? AddTrackersUrlList { get; init; }
[JsonPropertyName("alt_dl_limit")]
public int AltDlLimit { get; }
@@ -450,6 +462,9 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("announce_ip")]
public string AnnounceIp { get; }
[JsonPropertyName("announce_port")]
public int AnnouncePort { get; init; }
[JsonPropertyName("announce_to_all_tiers")]
public bool AnnounceToAllTiers { get; }
@@ -510,6 +525,12 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("category_changed_tmm_enabled")]
public bool CategoryChangedTmmEnabled { get; }
[JsonPropertyName("confirm_torrent_deletion")]
public bool ConfirmTorrentDeletion { get; init; }
[JsonPropertyName("confirm_torrent_recheck")]
public bool ConfirmTorrentRecheck { get; init; }
[JsonPropertyName("checking_memory_use")]
public int CheckingMemoryUse { get; }
@@ -525,6 +546,9 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("current_network_interface")]
public string CurrentNetworkInterface { get; }
[JsonPropertyName("delete_torrent_content_files")]
public bool DeleteTorrentContentFiles { get; init; }
[JsonPropertyName("dht")]
public bool Dht { get; }
@@ -633,6 +657,9 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("hashing_threads")]
public int HashingThreads { get; }
[JsonPropertyName("hostname_cache_ttl")]
public int HostnameCacheTtl { get; init; }
[JsonPropertyName("i2p_address")]
public string I2pAddress { get; }
@@ -660,6 +687,9 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("idn_support_enabled")]
public bool IdnSupportEnabled { get; }
[JsonPropertyName("ignore_ssl_errors")]
public bool IgnoreSslErrors { get; init; }
[JsonPropertyName("incomplete_files_ext")]
public bool IncompleteFilesExt { get; }
@@ -745,7 +775,7 @@ namespace Lantean.QBitTorrentClient.Models
public bool MaxInactiveSeedingTimeEnabled { get; }
[JsonPropertyName("max_ratio")]
public int MaxRatio { get; }
public float MaxRatio { get; }
[JsonPropertyName("max_ratio_act")]
public int MaxRatioAct { get; }
@@ -888,6 +918,9 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("save_resume_data_interval")]
public int SaveResumeDataInterval { get; }
[JsonPropertyName("save_statistics_interval")]
public int SaveStatisticsInterval { get; init; }
[JsonPropertyName("scan_dirs")]
public Dictionary<string, SaveLocation> ScanDirs { get; }
@@ -936,12 +969,21 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("socket_send_buffer_size")]
public int SocketSendBufferSize { get; }
[JsonPropertyName("ssl_enabled")]
public bool SslEnabled { get; init; }
[JsonPropertyName("ssl_listen_port")]
public int SslListenPort { get; init; }
[JsonPropertyName("ssrf_mitigation")]
public bool SsrfMitigation { get; }
[JsonPropertyName("start_paused_enabled")]
public bool StartPausedEnabled { get; }
[JsonPropertyName("status_bar_external_ip")]
public bool StatusBarExternalIp { get; init; }
[JsonPropertyName("stop_tracker_timeout")]
public int StopTrackerTimeout { get; }
@@ -957,6 +999,9 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("torrent_content_layout")]
public string TorrentContentLayout { get; }
[JsonPropertyName("torrent_content_remove_option")]
public string? TorrentContentRemoveOption { get; init; }
[JsonPropertyName("torrent_file_size_limit")]
public int TorrentFileSizeLimit { get; }
@@ -987,6 +1032,9 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("use_subcategories")]
public bool UseSubcategories { get; }
[JsonPropertyName("use_unwanted_folder")]
public bool UseUnwantedFolder { get; init; }
[JsonPropertyName("utp_tcp_mixed_mode")]
public int UtpTcpMixedMode { get; }
@@ -996,6 +1044,9 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("web_ui_address")]
public string WebUiAddress { get; }
[JsonPropertyName("web_ui_api_key")]
public string? WebUiApiKey { get; init; }
[JsonPropertyName("web_ui_ban_duration")]
public int WebUiBanDuration { get; }
@@ -1050,4 +1101,4 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("web_ui_password")]
public string WebUiPassword { get; }
}
}
}

View File

@@ -7,12 +7,24 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("add_to_top_of_queue")]
public bool? AddToTopOfQueue { get; set; }
[JsonPropertyName("add_stopped_enabled")]
public bool? AddStoppedEnabled { get; set; }
[JsonPropertyName("add_trackers")]
public string? AddTrackers { get; set; }
[JsonPropertyName("add_trackers_enabled")]
public bool? AddTrackersEnabled { get; set; }
[JsonPropertyName("add_trackers_from_url_enabled")]
public bool? AddTrackersFromUrlEnabled { get; set; }
[JsonPropertyName("add_trackers_url")]
public string? AddTrackersUrl { get; set; }
[JsonPropertyName("add_trackers_url_list")]
public string? AddTrackersUrlList { get; set; }
[JsonPropertyName("alt_dl_limit")]
public int? AltDlLimit { get; set; }
@@ -28,6 +40,9 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("announce_ip")]
public string? AnnounceIp { get; set; }
[JsonPropertyName("announce_port")]
public int? AnnouncePort { get; set; }
[JsonPropertyName("announce_to_all_tiers")]
public bool? AnnounceToAllTiers { get; set; }
@@ -88,6 +103,12 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("category_changed_tmm_enabled")]
public bool? CategoryChangedTmmEnabled { get; set; }
[JsonPropertyName("confirm_torrent_deletion")]
public bool? ConfirmTorrentDeletion { get; set; }
[JsonPropertyName("confirm_torrent_recheck")]
public bool? ConfirmTorrentRecheck { get; set; }
[JsonPropertyName("checking_memory_use")]
public int? CheckingMemoryUse { get; set; }
@@ -103,6 +124,9 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("current_network_interface")]
public string? CurrentNetworkInterface { get; set; }
[JsonPropertyName("delete_torrent_content_files")]
public bool? DeleteTorrentContentFiles { get; set; }
[JsonPropertyName("dht")]
public bool? Dht { get; set; }
@@ -211,6 +235,9 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("hashing_threads")]
public int? HashingThreads { get; set; }
[JsonPropertyName("hostname_cache_ttl")]
public int? HostnameCacheTtl { get; set; }
[JsonPropertyName("i2p_address")]
public string? I2pAddress { get; set; }
@@ -238,6 +265,9 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("idn_support_enabled")]
public bool? IdnSupportEnabled { get; set; }
[JsonPropertyName("ignore_ssl_errors")]
public bool? IgnoreSslErrors { get; set; }
[JsonPropertyName("incomplete_files_ext")]
public bool? IncompleteFilesExt { get; set; }
@@ -323,7 +353,7 @@ namespace Lantean.QBitTorrentClient.Models
public bool? MaxInactiveSeedingTimeEnabled { get; set; }
[JsonPropertyName("max_ratio")]
public int? MaxRatio { get; set; }
public float? MaxRatio { get; set; }
[JsonPropertyName("max_ratio_act")]
public int? MaxRatioAct { get; set; }
@@ -466,6 +496,9 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("save_resume_data_interval")]
public int? SaveResumeDataInterval { get; set; }
[JsonPropertyName("save_statistics_interval")]
public int? SaveStatisticsInterval { get; set; }
[JsonPropertyName("scan_dirs")]
public Dictionary<string, SaveLocation>? ScanDirs { get; set; }
@@ -514,12 +547,21 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("socket_send_buffer_size")]
public int? SocketSendBufferSize { get; set; }
[JsonPropertyName("ssl_enabled")]
public bool? SslEnabled { get; set; }
[JsonPropertyName("ssl_listen_port")]
public int? SslListenPort { get; set; }
[JsonPropertyName("ssrf_mitigation")]
public bool? SsrfMitigation { get; set; }
[JsonPropertyName("start_paused_enabled")]
public bool? StartPausedEnabled { get; set; }
[JsonPropertyName("status_bar_external_ip")]
public bool? StatusBarExternalIp { get; set; }
[JsonPropertyName("stop_tracker_timeout")]
public int? StopTrackerTimeout { get; set; }
@@ -535,6 +577,9 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("torrent_content_layout")]
public string? TorrentContentLayout { get; set; }
[JsonPropertyName("torrent_content_remove_option")]
public string? TorrentContentRemoveOption { get; set; }
[JsonPropertyName("torrent_file_size_limit")]
public int? TorrentFileSizeLimit { get; set; }
@@ -565,6 +610,9 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("use_subcategories")]
public bool? UseSubcategories { get; set; }
[JsonPropertyName("use_unwanted_folder")]
public bool? UseUnwantedFolder { get; set; }
[JsonPropertyName("utp_tcp_mixed_mode")]
public int? UtpTcpMixedMode { get; set; }
@@ -574,6 +622,9 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("web_ui_address")]
public string? WebUiAddress { get; set; }
[JsonPropertyName("web_ui_api_key")]
public string? WebUiApiKey { get; set; }
[JsonPropertyName("web_ui_ban_duration")]
public int? WebUiBanDuration { get; set; }
@@ -628,4 +679,4 @@ namespace Lantean.QBitTorrentClient.Models
[JsonPropertyName("web_ui_password")]
public string? WebUiPassword { get; set; }
}
}
}

11
nuget.config Normal file
View File

@@ -0,0 +1,11 @@
<configuration>
<packageSources>
<!-- Define package sources here -->
</packageSources>
<packageSourceMapping>
<!-- Optional source mapping -->
</packageSourceMapping>
<packageVersionOverride>
<package id="FluentAssertions" allowedVersions="[7.0.0,8.0.0)" />
</packageVersionOverride>
</configuration>

View File

@@ -1,14 +1,84 @@
# qbt-mud
# qbtmud
## To-Do
qbtmud is a drop-in replacement for qBittorrent's default WebUI, implementing all of its functionality with a modern and user-friendly interface.
- Rename multiple files dialog
- ~~RSS feeds and dialogs~~
- ~~About~~
- ~~Context menu for files list/trackers list/peers list~~
- ~~Tag management page~~
- ~~Category management page~~
- ~~Update all tables to use DynamicTable~~
- ~~Log~~
- ~~Blocks~~
- ~~Search~~
## Features
qbtmud replicates all core features of the qBittorrent WebUI, including:
- **Torrent Management** Add, remove, and control torrents.
- **Tracker Control** View and manage trackers.
- **Peer Management** Monitor and manage peers connected to torrents.
- **File Prioritization** Select and prioritize specific files within a torrent.
- **Speed Limits** Set global and per-torrent speed limits.
- **RSS Integration** Subscribe to RSS feeds for automated torrent downloads.
- **Search Functionality** Integrated torrent search.
- **Sequential Downloading** Download files in order for media streaming.
- **Super Seeding Mode** Efficiently distribute torrents as an initial seeder.
- **IP Filtering** Improve security by filtering specific IP addresses.
- **IPv6 Support** Full support for IPv6 networks.
- **Bandwidth Scheduler** Schedule bandwidth limits.
- **WebUI Access** Remotely manage torrents through the WebUI.
![image](https://github.com/user-attachments/assets/c4e383fd-bff0-4367-b6de-79e19a632f11)
![image](https://github.com/user-attachments/assets/4ff56ed6-cc11-42cd-a070-23f086fd8821)
![image](https://github.com/user-attachments/assets/e321c5a2-ccf1-4205-828d-7ed7adade7dd)
For a detailed explanation of these features, refer to the [qBittorrent Options Guide](https://github.com/qbittorrent/qBittorrent/wiki/Explanation-of-Options-in-qBittorrent).
---
## Installation
To install qbtmud without building from source:
### 1. Download the Latest Release
- Go to the [qbtmud Releases](https://github.com/lantean-code/qbtmud/releases) page.
- Download the latest release archive for your operating system.
### 2. Extract the Archive
- Extract the contents of the downloaded archive to a directory of your choice.
### 3. Configure qBittorrent to Use qbtmud
- Open qBittorrent and navigate to `Tools` > `Options` > `Web UI`.
- Enable the option **"Use alternative WebUI"**.
- Set the **"Root Folder"** to the directory where you extracted qbtmud.
- Click **OK** to save the settings.
### 4. Access qbtmud
- Open your web browser and go to `http://localhost:8080` (or the port configured in qBittorrent).
For more detailed instructions, refer to the [Alternate WebUI Usage Guide](https://github.com/qbittorrent/qBittorrent/wiki/Alternate-WebUI-usage).
---
## Building from Source
To build qbtmud from source, you need to have the **.NET 9.0 SDK** installed on your system.
### 1. Clone the Repository
```sh
git clone https://github.com/lantean-code/qbtmud.git
cd qbtmud
```
### 2. Restore Dependencies
```sh
dotnet restore
```
### 3. Build and Publish the Application
```sh
dotnet publish --configuration Release
```
This will output the Web UI files to `Lantean.QBTMud\bin\Release\net9.0\publish\wwwroot`.
### 4. Configure qBittorrent to Use qbtmud
Follow the same steps as in the **Installation** section to set qbtmud as your WebUI.
### 5. Run qbtmud
Navigate to the directory containing the built files and run the application using the appropriate command for your OS.
By following these steps, you can set up qbtmud to manage your qBittorrent server with an improved web interface, offering better functionality and usability.