mirror of
https://github.com/lantean-code/qbtmud.git
synced 2025-10-23 04:52:22 +00:00
Compare commits
11 Commits
1.2.0
...
feature/v5
Author | SHA1 | Date | |
---|---|---|---|
|
b8412bb232 | ||
|
e64a13c7c9 | ||
|
e4ea79a8ed | ||
|
0976b72411 | ||
|
965fbcd010 | ||
|
3d0dbde9f4 | ||
|
5b4fbde7b2 | ||
|
0db0ad4374 | ||
|
c390d83e4d | ||
|
8dd29c238d | ||
|
fca17edfd1 |
@@ -15,6 +15,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
readme.md = readme.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lantean.QBitTorrentClient.Test", "Lantean.QBitTorrentClient.Test\Lantean.QBitTorrentClient.Test.csproj", "{796E865C-7AA6-4BD9-B12F-394801199A75}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -33,6 +35,10 @@ Global
|
||||
{83BC76CC-D51B-42AF-A6EE-FA400C300098}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{83BC76CC-D51B-42AF-A6EE-FA400C300098}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{83BC76CC-D51B-42AF-A6EE-FA400C300098}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{796E865C-7AA6-4BD9-B12F-394801199A75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{796E865C-7AA6-4BD9-B12F-394801199A75}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{796E865C-7AA6-4BD9-B12F-394801199A75}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{796E865C-7AA6-4BD9-B12F-394801199A75}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@@ -1,33 +1,50 @@
|
||||
<MudGrid>
|
||||
@using Lantean.QBitTorrentClient.Models
|
||||
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<MudSwitch Label="Additional Options" @bind-Value="Expanded" LabelPlacement="Placement.End" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<MudCollapse Expanded="Expanded">
|
||||
<MudGrid>
|
||||
<MudGrid Class="mt-2">
|
||||
<MudItem xs="12">
|
||||
<MudSelect Label="Torrent Management Mode" @bind-Value="TorrentManagementMode" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="false">Manual</MudSelectItem>
|
||||
<MudSelectItem Value="true">Automatic</MudSelectItem>
|
||||
<MudSelect T="bool" Label="Torrent management mode" Value="@TorrentManagementMode" ValueChanged="@SetTorrentManagementMode" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@false">Manual</MudSelectItem>
|
||||
<MudSelectItem Value="@true">Automatic</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6">
|
||||
<MudTextField T="string" Label="Save files to location" Value="@SavePath" ValueChanged="@SavePathChanged" Variant="Variant.Outlined" Disabled="@TorrentManagementMode" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6">
|
||||
<FieldSwitch Label="Use incomplete save path" Value="@UseDownloadPath" ValueChanged="@SetUseDownloadPath" Disabled="@TorrentManagementMode" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField Label="Save files to location" @bind-Value="SavePath" Variant="Variant.Outlined"></MudTextField>
|
||||
<MudTextField T="string" Label="Incomplete save path" Value="@DownloadPath" ValueChanged="@DownloadPathChanged" Variant="Variant.Outlined" Disabled="@DownloadPathDisabled" />
|
||||
</MudItem>
|
||||
@if (ShowCookieOption)
|
||||
{
|
||||
<MudItem xs="12">
|
||||
<MudTextField Label="Cookie" @bind-Value="Cookie" Variant="Variant.Outlined"></MudTextField>
|
||||
<MudTextField Label="Cookie" @bind-Value="Cookie" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
}
|
||||
<MudItem xs="12">
|
||||
<MudTextField Label="Rename" @bind-Value="RenameTorrent" Variant="Variant.Outlined"></MudTextField>
|
||||
<MudTextField Label="Rename" @bind-Value="RenameTorrent" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect Label="Category" @bind-Value="Category" Variant="Variant.Outlined">
|
||||
@foreach (var category in Categories)
|
||||
<MudSelect T="string" Label="Category" Value="@Category" ValueChanged="@CategoryChanged" Variant="Variant.Outlined" Clearable="true">
|
||||
<MudSelectItem Value="@string.Empty">None</MudSelectItem>
|
||||
@foreach (var category in CategoryOptions)
|
||||
{
|
||||
<MudSelectItem Value="category">@category</MudSelectItem>
|
||||
<MudSelectItem Value="@category.Name">@category.Name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string" Label="Tags" Variant="Variant.Outlined" MultiSelection="true" SelectedValues="@SelectedTags" SelectedValuesChanged="@SelectedTagsChanged" Disabled="@(AvailableTags.Count == 0)">
|
||||
@foreach (var tag in AvailableTags)
|
||||
{
|
||||
<MudSelectItem Value="@tag">@tag</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
@@ -38,7 +55,7 @@
|
||||
<FieldSwitch Label="Add to top of queue" @bind-Value="AddToTopOfQueue" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect Label="Stop condition" @bind-Value="StopCondition" Variant="Variant.Outlined">
|
||||
<MudSelect T="string" Label="Stop condition" Value="@StopCondition" ValueChanged="@StopConditionChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@("None")">None</MudSelectItem>
|
||||
<MudSelectItem Value="@("MetadataReceived")">Metadata received</MudSelectItem>
|
||||
<MudSelectItem Value="@("FilesChecked")">Files checked</MudSelectItem>
|
||||
@@ -47,22 +64,58 @@
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Skip hash check" @bind-Value="SkipHashCheck" />
|
||||
</MudItem>
|
||||
<MudSelect Label="Content layout" @bind-Value="ContentLayout" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@("Original")">Original</MudSelectItem>
|
||||
<MudSelectItem Value="@("Subfolder")">Create subfolder</MudSelectItem>
|
||||
<MudSelectItem Value="@("NoSubfolder")">Don't create subfolder'</MudSelectItem>
|
||||
</MudSelect>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Download in sequentual order" @bind-Value="DownloadInSequentialOrder" />
|
||||
<MudSelect T="string" Label="Content layout" Value="@ContentLayout" ValueChanged="@ContentLayoutChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@("Original")">Original</MudSelectItem>
|
||||
<MudSelectItem Value="@("Subfolder")">Create subfolder</MudSelectItem>
|
||||
<MudSelectItem Value="@("NoSubfolder")">Don't create subfolder</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Download in sequential order" @bind-Value="DownloadInSequentialOrder" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Download first and last pieces first" @bind-Value="DownloadFirstAndLastPiecesFirst" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudItem xs="12" sm="6">
|
||||
<MudNumericField Label="Limit download rate" @bind-Value="DownloadLimit" Variant="Variant.Outlined" Min="0" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudItem xs="12" sm="6">
|
||||
<MudNumericField Label="Limit upload rate" @bind-Value="UploadLimit" Variant="Variant.Outlined" Min="0" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="ShareLimitMode" Label="Share limit preset" Value="@SelectedShareLimitMode" ValueChanged="@ShareLimitModeChanged" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@ShareLimitMode.Global">Use global share limit</MudSelectItem>
|
||||
<MudSelectItem Value="@ShareLimitMode.NoLimit">Set no share limit</MudSelectItem>
|
||||
<MudSelectItem Value="@ShareLimitMode.Custom">Set custom share limit</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="4">
|
||||
<FieldSwitch Label="Ratio" Value="@RatioLimitEnabled" ValueChanged="@RatioLimitEnabledChanged" Disabled="@(!IsCustomShareLimit)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="8">
|
||||
<MudNumericField T="float" Label="Ratio limit" Value="@RatioLimit" ValueChanged="@RatioLimitChanged" Disabled="@(!RatioLimitEnabled || !IsCustomShareLimit)" Min="0" Step="0.1f" Format="F2" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="4">
|
||||
<FieldSwitch Label="Total minutes" Value="@SeedingTimeLimitEnabled" ValueChanged="@SeedingTimeLimitEnabledChanged" Disabled="@(!IsCustomShareLimit)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="8">
|
||||
<MudNumericField T="int" Label="Total minutes" Value="@SeedingTimeLimit" ValueChanged="@SeedingTimeLimitChanged" Disabled="@(!SeedingTimeLimitEnabled || !IsCustomShareLimit)" Min="1" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="4">
|
||||
<FieldSwitch Label="Inactive minutes" Value="@InactiveSeedingTimeLimitEnabled" ValueChanged="@InactiveSeedingTimeLimitEnabledChanged" Disabled="@(!IsCustomShareLimit)" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="8">
|
||||
<MudNumericField T="int" Label="Inactive minutes" Value="@InactiveSeedingTimeLimit" ValueChanged="@InactiveSeedingTimeLimitChanged" Disabled="@(!InactiveSeedingTimeLimitEnabled || !IsCustomShareLimit)" Min="1" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="ShareLimitAction" Label="Action when limit is reached" Value="@SelectedShareLimitAction" ValueChanged="@ShareLimitActionChanged" Disabled="@(!IsCustomShareLimit)" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@ShareLimitAction.Default">Default</MudSelectItem>
|
||||
<MudSelectItem Value="@ShareLimitAction.Stop">Stop torrent</MudSelectItem>
|
||||
<MudSelectItem Value="@ShareLimitAction.Remove">Remove torrent</MudSelectItem>
|
||||
<MudSelectItem Value="@ShareLimitAction.RemoveWithContent">Remove torrent and data</MudSelectItem>
|
||||
<MudSelectItem Value="@ShareLimitAction.EnableSuperSeeding">Enable super seeding</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCollapse>
|
||||
</MudCollapse>
|
||||
|
@@ -1,4 +1,10 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
@@ -6,6 +12,15 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
{
|
||||
public partial class AddTorrentOptions
|
||||
{
|
||||
private readonly List<CategoryOption> _categoryOptions = new();
|
||||
private readonly Dictionary<string, CategoryOption> _categoryLookup = new(StringComparer.Ordinal);
|
||||
private string _manualSavePath = string.Empty;
|
||||
private bool _manualUseDownloadPath;
|
||||
private string _manualDownloadPath = string.Empty;
|
||||
private string _defaultSavePath = string.Empty;
|
||||
private string _defaultDownloadPath = string.Empty;
|
||||
private bool _defaultDownloadPathEnabled;
|
||||
|
||||
[Inject]
|
||||
protected IApiClient ApiClient { get; set; } = default!;
|
||||
|
||||
@@ -16,15 +31,25 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
|
||||
protected bool TorrentManagementMode { get; set; }
|
||||
|
||||
protected string SavePath { get; set; } = default!;
|
||||
protected string SavePath { get; set; } = string.Empty;
|
||||
|
||||
protected string DownloadPath { get; set; } = string.Empty;
|
||||
|
||||
protected bool UseDownloadPath { get; set; }
|
||||
|
||||
protected bool DownloadPathDisabled => TorrentManagementMode || !UseDownloadPath;
|
||||
|
||||
protected string? Cookie { get; set; }
|
||||
|
||||
protected string? RenameTorrent { get; set; }
|
||||
|
||||
protected IEnumerable<string> Categories { get; set; } = [];
|
||||
protected IReadOnlyList<CategoryOption> CategoryOptions => _categoryOptions;
|
||||
|
||||
protected string? Category { get; set; }
|
||||
protected string? Category { get; set; } = string.Empty;
|
||||
|
||||
protected List<string> AvailableTags { get; private set; } = [];
|
||||
|
||||
protected HashSet<string> SelectedTags { get; private set; } = new(StringComparer.Ordinal);
|
||||
|
||||
protected bool StartTorrent { get; set; } = true;
|
||||
|
||||
@@ -32,41 +57,264 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
|
||||
protected string StopCondition { get; set; } = "None";
|
||||
|
||||
protected bool SkipHashCheck { get; set; } = false;
|
||||
protected bool SkipHashCheck { get; set; }
|
||||
|
||||
protected string ContentLayout { get; set; } = "Original";
|
||||
|
||||
protected bool DownloadInSequentialOrder { get; set; } = false;
|
||||
protected bool DownloadInSequentialOrder { get; set; }
|
||||
|
||||
protected bool DownloadFirstAndLastPiecesFirst { get; set; } = false;
|
||||
protected bool DownloadFirstAndLastPiecesFirst { get; set; }
|
||||
|
||||
protected long DownloadLimit { get; set; }
|
||||
|
||||
protected long UploadLimit { get; set; }
|
||||
|
||||
protected ShareLimitMode SelectedShareLimitMode { get; set; } = ShareLimitMode.Global;
|
||||
|
||||
protected bool RatioLimitEnabled { get; set; }
|
||||
|
||||
protected float RatioLimit { get; set; } = 1.0f;
|
||||
|
||||
protected bool SeedingTimeLimitEnabled { get; set; }
|
||||
|
||||
protected int SeedingTimeLimit { get; set; } = 1440;
|
||||
|
||||
protected bool InactiveSeedingTimeLimitEnabled { get; set; }
|
||||
|
||||
protected int InactiveSeedingTimeLimit { get; set; } = 1440;
|
||||
|
||||
protected ShareLimitAction SelectedShareLimitAction { get; set; } = ShareLimitAction.Default;
|
||||
|
||||
protected bool IsCustomShareLimit => SelectedShareLimitMode == ShareLimitMode.Custom;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var categories = await ApiClient.GetAllCategories();
|
||||
Categories = categories.Select(c => c.Key).ToList();
|
||||
foreach (var (name, value) in categories.OrderBy(kvp => kvp.Key, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
var option = new CategoryOption(name, value.SavePath, value.DownloadPath);
|
||||
_categoryOptions.Add(option);
|
||||
_categoryLookup[name] = option;
|
||||
}
|
||||
|
||||
var tags = await ApiClient.GetAllTags();
|
||||
AvailableTags = tags.OrderBy(t => t, StringComparer.OrdinalIgnoreCase).ToList();
|
||||
|
||||
var preferences = await ApiClient.GetApplicationPreferences();
|
||||
|
||||
TorrentManagementMode = preferences.AutoTmmEnabled;
|
||||
SavePath = preferences.SavePath;
|
||||
StartTorrent = !preferences.StartPausedEnabled;
|
||||
|
||||
_defaultSavePath = preferences.SavePath ?? string.Empty;
|
||||
_manualSavePath = _defaultSavePath;
|
||||
SavePath = _defaultSavePath;
|
||||
|
||||
_defaultDownloadPath = preferences.TempPath ?? string.Empty;
|
||||
_defaultDownloadPathEnabled = preferences.TempPathEnabled;
|
||||
_manualDownloadPath = _defaultDownloadPath;
|
||||
_manualUseDownloadPath = preferences.TempPathEnabled;
|
||||
UseDownloadPath = _manualUseDownloadPath;
|
||||
DownloadPath = UseDownloadPath ? _manualDownloadPath : string.Empty;
|
||||
|
||||
StartTorrent = !preferences.AddStoppedEnabled;
|
||||
AddToTopOfQueue = preferences.AddToTopOfQueue;
|
||||
StopCondition = preferences.TorrentStopCondition;
|
||||
ContentLayout = preferences.TorrentContentLayout;
|
||||
|
||||
RatioLimitEnabled = preferences.MaxRatioEnabled;
|
||||
RatioLimit = preferences.MaxRatio;
|
||||
SeedingTimeLimitEnabled = preferences.MaxSeedingTimeEnabled;
|
||||
if (preferences.MaxSeedingTimeEnabled)
|
||||
{
|
||||
SeedingTimeLimit = preferences.MaxSeedingTime;
|
||||
}
|
||||
InactiveSeedingTimeLimitEnabled = preferences.MaxInactiveSeedingTimeEnabled;
|
||||
if (preferences.MaxInactiveSeedingTimeEnabled)
|
||||
{
|
||||
InactiveSeedingTimeLimit = preferences.MaxInactiveSeedingTime;
|
||||
}
|
||||
SelectedShareLimitAction = MapShareLimitAction(preferences.MaxRatioAct);
|
||||
|
||||
if (TorrentManagementMode)
|
||||
{
|
||||
ApplyAutomaticPaths();
|
||||
}
|
||||
}
|
||||
|
||||
protected Task SetTorrentManagementMode(bool value)
|
||||
{
|
||||
if (TorrentManagementMode == value)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
TorrentManagementMode = value;
|
||||
if (TorrentManagementMode)
|
||||
{
|
||||
ApplyAutomaticPaths();
|
||||
}
|
||||
else
|
||||
{
|
||||
RestoreManualPaths();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task SavePathChanged(string value)
|
||||
{
|
||||
SavePath = value;
|
||||
if (!TorrentManagementMode)
|
||||
{
|
||||
_manualSavePath = value;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task SetUseDownloadPath(bool value)
|
||||
{
|
||||
if (TorrentManagementMode)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
_manualUseDownloadPath = value;
|
||||
UseDownloadPath = value;
|
||||
|
||||
if (value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_manualDownloadPath))
|
||||
{
|
||||
_manualDownloadPath = string.IsNullOrWhiteSpace(_defaultDownloadPath) ? string.Empty : _defaultDownloadPath;
|
||||
}
|
||||
|
||||
DownloadPath = _manualDownloadPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
_manualDownloadPath = DownloadPath;
|
||||
DownloadPath = string.Empty;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task DownloadPathChanged(string value)
|
||||
{
|
||||
DownloadPath = value;
|
||||
if (!TorrentManagementMode && UseDownloadPath)
|
||||
{
|
||||
_manualDownloadPath = value;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task CategoryChanged(string? value)
|
||||
{
|
||||
Category = string.IsNullOrWhiteSpace(value) ? null : value;
|
||||
if (TorrentManagementMode)
|
||||
{
|
||||
ApplyAutomaticPaths();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task SelectedTagsChanged(IEnumerable<string> tags)
|
||||
{
|
||||
SelectedTags = tags is null
|
||||
? new HashSet<string>(StringComparer.Ordinal)
|
||||
: new HashSet<string>(tags, StringComparer.Ordinal);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task StopConditionChanged(string value)
|
||||
{
|
||||
StopCondition = value;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task ContentLayoutChanged(string value)
|
||||
{
|
||||
ContentLayout = value;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task ShareLimitModeChanged(ShareLimitMode mode)
|
||||
{
|
||||
SelectedShareLimitMode = mode;
|
||||
if (mode != ShareLimitMode.Custom)
|
||||
{
|
||||
RatioLimitEnabled = false;
|
||||
SeedingTimeLimitEnabled = false;
|
||||
InactiveSeedingTimeLimitEnabled = false;
|
||||
SelectedShareLimitAction = ShareLimitAction.Default;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task RatioLimitEnabledChanged(bool value)
|
||||
{
|
||||
RatioLimitEnabled = value;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task RatioLimitChanged(float value)
|
||||
{
|
||||
RatioLimit = value;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task SeedingTimeLimitEnabledChanged(bool value)
|
||||
{
|
||||
SeedingTimeLimitEnabled = value;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task SeedingTimeLimitChanged(int value)
|
||||
{
|
||||
SeedingTimeLimit = value;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task InactiveSeedingTimeLimitEnabledChanged(bool value)
|
||||
{
|
||||
InactiveSeedingTimeLimitEnabled = value;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task InactiveSeedingTimeLimitChanged(int value)
|
||||
{
|
||||
InactiveSeedingTimeLimit = value;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected Task ShareLimitActionChanged(ShareLimitAction value)
|
||||
{
|
||||
SelectedShareLimitAction = value;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public TorrentOptions GetTorrentOptions()
|
||||
{
|
||||
return new TorrentOptions(
|
||||
var options = new TorrentOptions(
|
||||
TorrentManagementMode,
|
||||
SavePath,
|
||||
_manualSavePath,
|
||||
Cookie,
|
||||
RenameTorrent,
|
||||
Category,
|
||||
string.IsNullOrWhiteSpace(Category) ? null : Category,
|
||||
StartTorrent,
|
||||
AddToTopOfQueue,
|
||||
StopCondition,
|
||||
@@ -76,6 +324,152 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
DownloadFirstAndLastPiecesFirst,
|
||||
DownloadLimit,
|
||||
UploadLimit);
|
||||
|
||||
options.UseDownloadPath = TorrentManagementMode ? null : UseDownloadPath;
|
||||
options.DownloadPath = (!TorrentManagementMode && UseDownloadPath) ? DownloadPath : null;
|
||||
options.Tags = SelectedTags.Count > 0 ? SelectedTags.ToArray() : null;
|
||||
|
||||
switch (SelectedShareLimitMode)
|
||||
{
|
||||
case ShareLimitMode.Global:
|
||||
options.RatioLimit = Limits.GlobalLimit;
|
||||
options.SeedingTimeLimit = Limits.GlobalLimit;
|
||||
options.InactiveSeedingTimeLimit = Limits.GlobalLimit;
|
||||
options.ShareLimitAction = ShareLimitAction.Default.ToString();
|
||||
break;
|
||||
case ShareLimitMode.NoLimit:
|
||||
options.RatioLimit = Limits.NoLimit;
|
||||
options.SeedingTimeLimit = Limits.NoLimit;
|
||||
options.InactiveSeedingTimeLimit = Limits.NoLimit;
|
||||
options.ShareLimitAction = ShareLimitAction.Default.ToString();
|
||||
break;
|
||||
case ShareLimitMode.Custom:
|
||||
options.RatioLimit = RatioLimitEnabled ? RatioLimit : Limits.NoLimit;
|
||||
options.SeedingTimeLimit = SeedingTimeLimitEnabled ? SeedingTimeLimit : Limits.NoLimit;
|
||||
options.InactiveSeedingTimeLimit = InactiveSeedingTimeLimitEnabled ? InactiveSeedingTimeLimit : Limits.NoLimit;
|
||||
options.ShareLimitAction = SelectedShareLimitAction.ToString();
|
||||
break;
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private void ApplyAutomaticPaths()
|
||||
{
|
||||
SavePath = ResolveAutomaticSavePath();
|
||||
var (enabled, path) = ResolveAutomaticDownloadPath();
|
||||
UseDownloadPath = enabled;
|
||||
DownloadPath = enabled ? path ?? string.Empty : string.Empty;
|
||||
}
|
||||
|
||||
private void RestoreManualPaths()
|
||||
{
|
||||
SavePath = _manualSavePath;
|
||||
UseDownloadPath = _manualUseDownloadPath;
|
||||
DownloadPath = _manualUseDownloadPath ? _manualDownloadPath : string.Empty;
|
||||
}
|
||||
|
||||
private string ResolveAutomaticSavePath()
|
||||
{
|
||||
var category = GetSelectedCategory();
|
||||
if (category is null)
|
||||
{
|
||||
return _defaultSavePath;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(category.SavePath))
|
||||
{
|
||||
return category.SavePath!;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_defaultSavePath) && !string.IsNullOrWhiteSpace(category.Name))
|
||||
{
|
||||
return Path.Combine(_defaultSavePath, category.Name);
|
||||
}
|
||||
|
||||
return _defaultSavePath;
|
||||
}
|
||||
|
||||
private (bool Enabled, string? Path) ResolveAutomaticDownloadPath()
|
||||
{
|
||||
var category = GetSelectedCategory();
|
||||
if (category is null)
|
||||
{
|
||||
if (!_defaultDownloadPathEnabled)
|
||||
{
|
||||
return (false, string.Empty);
|
||||
}
|
||||
|
||||
return (true, _defaultDownloadPath);
|
||||
}
|
||||
|
||||
if (category.DownloadPath is null)
|
||||
{
|
||||
if (!_defaultDownloadPathEnabled)
|
||||
{
|
||||
return (false, string.Empty);
|
||||
}
|
||||
|
||||
return (true, ComposeDefaultDownloadPath(category.Name));
|
||||
}
|
||||
|
||||
if (!category.DownloadPath.Enabled)
|
||||
{
|
||||
return (false, string.Empty);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(category.DownloadPath.Path))
|
||||
{
|
||||
return (true, category.DownloadPath.Path);
|
||||
}
|
||||
|
||||
return (true, ComposeDefaultDownloadPath(category.Name));
|
||||
}
|
||||
|
||||
private string ComposeDefaultDownloadPath(string categoryName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_defaultDownloadPath))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(categoryName))
|
||||
{
|
||||
return _defaultDownloadPath;
|
||||
}
|
||||
|
||||
return Path.Combine(_defaultDownloadPath, categoryName);
|
||||
}
|
||||
|
||||
private CategoryOption? GetSelectedCategory()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Category))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _categoryLookup.TryGetValue(Category, out var option) ? option : null;
|
||||
}
|
||||
|
||||
private static ShareLimitAction MapShareLimitAction(int preferenceValue)
|
||||
{
|
||||
return preferenceValue switch
|
||||
{
|
||||
0 => ShareLimitAction.Stop,
|
||||
1 => ShareLimitAction.Remove,
|
||||
2 => ShareLimitAction.RemoveWithContent,
|
||||
3 => ShareLimitAction.EnableSuperSeeding,
|
||||
_ => ShareLimitAction.Default
|
||||
};
|
||||
}
|
||||
|
||||
protected enum ShareLimitMode
|
||||
{
|
||||
Global,
|
||||
NoLimit,
|
||||
Custom
|
||||
}
|
||||
|
||||
protected sealed record CategoryOption(string Name, string? SavePath, DownloadPathOption? DownloadPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -53,7 +53,7 @@
|
||||
<MudNumericField T="int" Label="Ignore Subsequent Matches for (0 to Disable)" Value="IgnoreDays" ValueChanged="IgnoreDaysChanged" Disabled="@(SelectedRuleName is null)" Variant="Variant.Outlined" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string" Label="Add paused" Value="AddPaused" ValueChanged="AddPausedChanged" Disabled="@(SelectedRuleName is null)" Variant="Variant.Outlined">
|
||||
<MudSelect T="string" Label="Add stopped" Value="AddStopped" ValueChanged="AddStoppedChanged" Disabled="@(SelectedRuleName is null)" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="@("default")">Use global settings</MudSelectItem>
|
||||
<MudSelectItem Value="@("always")">Always</MudSelectItem>
|
||||
<MudSelectItem Value="@("never")">Never</MudSelectItem>
|
||||
@@ -103,4 +103,4 @@
|
||||
<MudButton OnClick="Cancel">Close</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
</MudDialog>
|
||||
|
@@ -114,11 +114,11 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
SelectedRule.IgnoreDays = value;
|
||||
}
|
||||
|
||||
protected string? AddPaused { get; set; }
|
||||
protected string? AddStopped { get; set; }
|
||||
|
||||
protected void AddPausedChanged(string value)
|
||||
protected void AddStoppedChanged(string value)
|
||||
{
|
||||
AddPaused = value;
|
||||
AddStopped = value;
|
||||
switch (value)
|
||||
{
|
||||
case "default":
|
||||
@@ -273,15 +273,15 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
switch (SelectedRule.TorrentParams.Stopped)
|
||||
{
|
||||
case null:
|
||||
AddPaused = "default";
|
||||
AddStopped = "default";
|
||||
break;
|
||||
|
||||
case true:
|
||||
AddPaused = "always";
|
||||
AddStopped = "always";
|
||||
break;
|
||||
|
||||
case false:
|
||||
AddPaused = "never";
|
||||
AddStopped = "never";
|
||||
break;
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
@inherits SubmittableDialog
|
||||
@inherits SubmittableDialog
|
||||
@using Lantean.QBitTorrentClient.Models
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
@@ -34,10 +35,19 @@
|
||||
<MudItem xs="9">
|
||||
<MudNumericField T="int" Value="InactiveMinutes" ValueChanged="InactiveMinutesChanged" Disabled="@(!(CustomEnabled && InactiveMinutesEnabled))" Min="1" Max="1024000" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentText="minutes" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="ShareLimitAction" Label="Action when limit is reached" Value="SelectedShareLimitAction" ValueChanged="ShareLimitActionChanged" Disabled="@(!CustomEnabled)" Variant="Variant.Outlined">
|
||||
<MudSelectItem Value="ShareLimitAction.Default">Default</MudSelectItem>
|
||||
<MudSelectItem Value="ShareLimitAction.Stop">Stop torrent</MudSelectItem>
|
||||
<MudSelectItem Value="ShareLimitAction.Remove">Remove torrent</MudSelectItem>
|
||||
<MudSelectItem Value="ShareLimitAction.RemoveWithContent">Remove torrent and data</MudSelectItem>
|
||||
<MudSelectItem Value="ShareLimitAction.EnableSuperSeeding">Enable super seeding</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" OnClick="Submit">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
</MudDialog>
|
||||
|
@@ -1,4 +1,7 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
@@ -16,6 +19,9 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
[Parameter]
|
||||
public ShareRatioMax? Value { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public ShareRatioMax? CurrentValue { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
@@ -33,6 +39,8 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
|
||||
protected int InactiveMinutes { get; set; }
|
||||
|
||||
protected ShareLimitAction SelectedShareLimitAction { get; set; } = ShareLimitAction.Default;
|
||||
|
||||
protected bool CustomEnabled => ShareRatioType == 0;
|
||||
|
||||
protected void RatioEnabledChanged(bool value)
|
||||
@@ -65,40 +73,75 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
InactiveMinutes = value;
|
||||
}
|
||||
|
||||
protected void ShareLimitActionChanged(ShareLimitAction value)
|
||||
{
|
||||
SelectedShareLimitAction = value;
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (Value is null || Value.RatioLimit == Limits.GlobalLimit && Value.SeedingTimeLimit == Limits.GlobalLimit && Value.InactiveSeedingTimeLimit == Limits.GlobalLimit)
|
||||
RatioEnabled = false;
|
||||
TotalMinutesEnabled = false;
|
||||
InactiveMinutesEnabled = false;
|
||||
|
||||
var baseline = Value ?? CurrentValue;
|
||||
SelectedShareLimitAction = baseline?.ShareLimitAction ?? ShareLimitAction.Default;
|
||||
|
||||
if (baseline is null || baseline.RatioLimit == Limits.GlobalLimit && baseline.SeedingTimeLimit == Limits.GlobalLimit && baseline.InactiveSeedingTimeLimit == Limits.GlobalLimit)
|
||||
{
|
||||
ShareRatioType = Limits.GlobalLimit;
|
||||
return;
|
||||
}
|
||||
else if (Value.MaxRatio == Limits.NoLimit && Value.MaxSeedingTime == Limits.NoLimit && Value.MaxInactiveSeedingTime == Limits.NoLimit)
|
||||
|
||||
if (baseline.MaxRatio == Limits.NoLimit && baseline.MaxSeedingTime == Limits.NoLimit && baseline.MaxInactiveSeedingTime == Limits.NoLimit)
|
||||
{
|
||||
ShareRatioType = Limits.NoLimit;
|
||||
return;
|
||||
}
|
||||
|
||||
ShareRatioType = 0;
|
||||
|
||||
if (baseline.RatioLimit >= 0)
|
||||
{
|
||||
RatioEnabled = true;
|
||||
Ratio = baseline.RatioLimit;
|
||||
}
|
||||
else
|
||||
{
|
||||
ShareRatioType = 0;
|
||||
if (Value.RatioLimit >= 0)
|
||||
{
|
||||
RatioEnabled = true;
|
||||
Ratio = Value.RatioLimit;
|
||||
}
|
||||
if (Value.SeedingTimeLimit >= 0)
|
||||
{
|
||||
TotalMinutesEnabled = true;
|
||||
TotalMinutes = (int)Value.SeedingTimeLimit;
|
||||
}
|
||||
if (Value.InactiveSeedingTimeLimit >= 0)
|
||||
{
|
||||
InactiveMinutesEnabled = true;
|
||||
InactiveMinutes = (int)Value.InactiveSeedingTimeLimit;
|
||||
}
|
||||
Ratio = 0;
|
||||
}
|
||||
|
||||
if (baseline.SeedingTimeLimit >= 0)
|
||||
{
|
||||
TotalMinutesEnabled = true;
|
||||
TotalMinutes = (int)baseline.SeedingTimeLimit;
|
||||
}
|
||||
else
|
||||
{
|
||||
TotalMinutes = 0;
|
||||
}
|
||||
|
||||
if (baseline.InactiveSeedingTimeLimit >= 0)
|
||||
{
|
||||
InactiveMinutesEnabled = true;
|
||||
InactiveMinutes = (int)baseline.InactiveSeedingTimeLimit;
|
||||
}
|
||||
else
|
||||
{
|
||||
InactiveMinutes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected void ShareRatioTypeChanged(int value)
|
||||
{
|
||||
ShareRatioType = value;
|
||||
if (!CustomEnabled)
|
||||
{
|
||||
RatioEnabled = false;
|
||||
TotalMinutesEnabled = false;
|
||||
InactiveMinutesEnabled = false;
|
||||
SelectedShareLimitAction = ShareLimitAction.Default;
|
||||
}
|
||||
}
|
||||
|
||||
protected void Cancel()
|
||||
@@ -112,16 +155,19 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
if (ShareRatioType == Limits.GlobalLimit)
|
||||
{
|
||||
result.RatioLimit = result.SeedingTimeLimit = result.InactiveSeedingTimeLimit = Limits.GlobalLimit;
|
||||
result.ShareLimitAction = ShareLimitAction.Default;
|
||||
}
|
||||
else if (ShareRatioType == Limits.NoLimit)
|
||||
{
|
||||
result.RatioLimit = result.SeedingTimeLimit = result.InactiveSeedingTimeLimit = Limits.NoLimit;
|
||||
result.ShareLimitAction = ShareLimitAction.Default;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.RatioLimit = RatioEnabled ? Ratio : Limits.NoLimit;
|
||||
result.SeedingTimeLimit = TotalMinutesEnabled ? TotalMinutes : Limits.NoLimit;
|
||||
result.InactiveSeedingTimeLimit = InactiveMinutesEnabled ? InactiveMinutes : Limits.NoLimit;
|
||||
result.ShareLimitAction = SelectedShareLimitAction;
|
||||
}
|
||||
MudDialog.Close(DialogResult.Ok(result));
|
||||
}
|
||||
@@ -133,4 +179,4 @@ namespace Lantean.QBTMud.Components.Dialogs
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -65,9 +65,9 @@
|
||||
{
|
||||
return __builder =>
|
||||
{
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.PlayArrow" IconColor="Color.Success" OnClick="@(e => ResumeTorrents(type))">Resume torrents</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Pause" IconColor="Color.Warning" OnClick="@(e => PauseTorrents(type))">Pause torrents</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.PlayArrow" IconColor="Color.Success" OnClick="@(e => StartTorrents(type))">Start torrents</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Stop" IconColor="Color.Warning" OnClick="@(e => StopTorrents(type))">Stop torrents</MudMenuItem>
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="@(e => RemoveTorrents(type))">Remove torrents</MudMenuItem>
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ using Lantean.QBTMud.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using MudBlazor;
|
||||
using System.Linq;
|
||||
|
||||
namespace Lantean.QBTMud.Components
|
||||
{
|
||||
@@ -352,25 +353,25 @@ namespace Lantean.QBTMud.Components
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task ResumeTorrents(string type)
|
||||
protected async Task StartTorrents(string type)
|
||||
{
|
||||
var torrents = GetAffectedTorrentHashes(type);
|
||||
|
||||
await ApiClient.ResumeTorrents(torrents);
|
||||
await ApiClient.StartTorrents(hashes: torrents.ToArray());
|
||||
}
|
||||
|
||||
protected async Task PauseTorrents(string type)
|
||||
protected async Task StopTorrents(string type)
|
||||
{
|
||||
var torrents = GetAffectedTorrentHashes(type);
|
||||
|
||||
await ApiClient.PauseTorrents(torrents);
|
||||
await ApiClient.StopTorrents(hashes: torrents.ToArray());
|
||||
}
|
||||
|
||||
protected async Task RemoveTorrents(string type)
|
||||
{
|
||||
var torrents = GetAffectedTorrentHashes(type);
|
||||
|
||||
await DialogService.InvokeDeleteTorrentDialog(ApiClient, [.. torrents]);
|
||||
await DialogService.InvokeDeleteTorrentDialog(ApiClient, Preferences?.ConfirmTorrentDeletion == true, [.. torrents]);
|
||||
}
|
||||
|
||||
private Dictionary<string, int> GetTags()
|
||||
@@ -477,4 +478,4 @@ namespace Lantean.QBTMud.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -68,6 +68,21 @@
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Confirmation</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Confirm torrent recheck" Value="ConfirmTorrentRecheck" ValueChanged="ConfirmTorrentRecheckChanged" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4 mt-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
@@ -240,4 +255,4 @@
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudCard>
|
||||
|
@@ -16,6 +16,8 @@ namespace Lantean.QBTMud.Components.Options
|
||||
protected int SaveResumeDataInterval { get; private set; }
|
||||
protected int TorrentFileSizeLimit { get; private set; }
|
||||
protected bool RecheckCompletedTorrents { get; private set; }
|
||||
|
||||
protected bool ConfirmTorrentRecheck { get; private set; }
|
||||
protected string? AppInstanceName { get; private set; }
|
||||
protected int RefreshInterval { get; private set; }
|
||||
protected bool ResolvePeerCountries { get; private set; }
|
||||
@@ -97,6 +99,7 @@ namespace Lantean.QBTMud.Components.Options
|
||||
SaveResumeDataInterval = Preferences.SaveResumeDataInterval;
|
||||
TorrentFileSizeLimit = Preferences.TorrentFileSizeLimit / 1024 / 1024;
|
||||
RecheckCompletedTorrents = Preferences.RecheckCompletedTorrents;
|
||||
ConfirmTorrentRecheck = Preferences.ConfirmTorrentRecheck;
|
||||
AppInstanceName = Preferences.AppInstanceName;
|
||||
RefreshInterval = Preferences.RefreshInterval;
|
||||
ResolvePeerCountries = Preferences.ResolvePeerCountries;
|
||||
@@ -209,6 +212,13 @@ namespace Lantean.QBTMud.Components.Options
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task ConfirmTorrentRecheckChanged(bool value)
|
||||
{
|
||||
ConfirmTorrentRecheck = value;
|
||||
UpdatePreferences.ConfirmTorrentRecheck = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task AppInstanceNameChanged(string value)
|
||||
{
|
||||
AppInstanceName = value;
|
||||
@@ -608,4 +618,4 @@ namespace Lantean.QBTMud.Components.Options
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -17,6 +17,24 @@
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.subtitle2">Transfer List</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent Class="pt-0">
|
||||
<MudGrid>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Confirm when deleting torrents" Value="ConfirmTorrentDeletion" ValueChanged="ConfirmTorrentDeletionChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Show external IP in status bar" Value="StatusBarExternalIp" ValueChanged="StatusBarExternalIpChanged" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Elevation="1" Class="ml-4 mr-4 mb-4">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
@@ -71,4 +89,4 @@
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudCard>
|
||||
|
@@ -4,6 +4,10 @@ namespace Lantean.QBTMud.Components.Options
|
||||
{
|
||||
public partial class BehaviourOptions : Options
|
||||
{
|
||||
protected bool ConfirmTorrentDeletion { get; set; }
|
||||
|
||||
protected bool StatusBarExternalIp { get; set; }
|
||||
|
||||
protected bool FileLogEnabled { get; set; }
|
||||
|
||||
protected string? FileLogPath { get; set; }
|
||||
@@ -27,6 +31,8 @@ namespace Lantean.QBTMud.Components.Options
|
||||
return false;
|
||||
}
|
||||
|
||||
ConfirmTorrentDeletion = Preferences.ConfirmTorrentDeletion;
|
||||
StatusBarExternalIp = Preferences.StatusBarExternalIp;
|
||||
FileLogEnabled = Preferences.FileLogEnabled;
|
||||
FileLogPath = Preferences.FileLogPath;
|
||||
FileLogBackupEnabled = Preferences.FileLogBackupEnabled;
|
||||
@@ -39,6 +45,20 @@ namespace Lantean.QBTMud.Components.Options
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async Task ConfirmTorrentDeletionChanged(bool value)
|
||||
{
|
||||
ConfirmTorrentDeletion = value;
|
||||
UpdatePreferences.ConfirmTorrentDeletion = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task StatusBarExternalIpChanged(bool value)
|
||||
{
|
||||
StatusBarExternalIp = value;
|
||||
UpdatePreferences.StatusBarExternalIp = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task FileLogEnabledChanged(bool value)
|
||||
{
|
||||
FileLogEnabled = value;
|
||||
@@ -96,4 +116,4 @@ namespace Lantean.QBTMud.Components.Options
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@
|
||||
<FieldSwitch Label="Add to top of queue" Value="AddToTopOfQueue" ValueChanged="AddToTopOfQueueChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<FieldSwitch Label="Do not start the download automatically" Value="StartPausedEnabled" ValueChanged="StartPausedEnabledChanged" />
|
||||
<FieldSwitch Label="Do not start the download automatically" Value="AddStoppedEnabled" ValueChanged="AddStoppedEnabledChanged" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string" Label="Torrent stop condition" Value="TorrentStopCondition" ValueChanged="TorrentStopConditionChanged" Variant="Variant.Outlined">
|
||||
@@ -306,4 +306,4 @@
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudCard>
|
||||
|
@@ -6,7 +6,7 @@ namespace Lantean.QBTMud.Components.Options
|
||||
{
|
||||
protected string? TorrentContentLayout { get; set; }
|
||||
protected bool AddToTopOfQueue { get; set; }
|
||||
protected bool StartPausedEnabled { get; set; }
|
||||
protected bool AddStoppedEnabled { get; set; }
|
||||
protected string? TorrentStopCondition { get; set; }
|
||||
protected bool AutoDeleteMode { get; set; }
|
||||
protected bool PreallocateAll { get; set; }
|
||||
@@ -51,7 +51,7 @@ namespace Lantean.QBTMud.Components.Options
|
||||
// when adding a torrent
|
||||
TorrentContentLayout = Preferences.TorrentContentLayout;
|
||||
AddToTopOfQueue = Preferences.AddToTopOfQueue;
|
||||
StartPausedEnabled = Preferences.StartPausedEnabled;
|
||||
AddStoppedEnabled = Preferences.AddStoppedEnabled;
|
||||
TorrentStopCondition = Preferences.TorrentStopCondition;
|
||||
AutoDeleteMode = Preferences.AutoDeleteMode == 1;
|
||||
PreallocateAll = Preferences.PreallocateAll;
|
||||
@@ -116,10 +116,10 @@ namespace Lantean.QBTMud.Components.Options
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
protected async Task StartPausedEnabledChanged(bool value)
|
||||
protected async Task AddStoppedEnabledChanged(bool value)
|
||||
{
|
||||
StartPausedEnabled = value;
|
||||
UpdatePreferences.StartPausedEnabled = value;
|
||||
AddStoppedEnabled = value;
|
||||
UpdatePreferences.AddStoppedEnabled = value;
|
||||
await PreferencesChanged.InvokeAsync(UpdatePreferences);
|
||||
}
|
||||
|
||||
@@ -410,4 +410,4 @@ namespace Lantean.QBTMud.Components.Options
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -37,9 +37,6 @@ namespace Lantean.QBTMud.Components
|
||||
[Inject]
|
||||
protected IKeyboardService KeyboardService { get; set; } = default!;
|
||||
|
||||
[CascadingParameter(Name = "Version")]
|
||||
public string? Version { get; set; }
|
||||
|
||||
[Parameter]
|
||||
[EditorRequired]
|
||||
public IEnumerable<string> Hashes { get; set; } = default!;
|
||||
@@ -71,14 +68,12 @@ namespace Lantean.QBTMud.Components
|
||||
|
||||
protected bool OverlayVisible { get; set; }
|
||||
|
||||
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", MajorVersion < 5 ? Icons.Material.Filled.Pause : Icons.Material.Filled.Stop, Color.Warning, CreateCallback(Pause)),
|
||||
new("start", "Start", Icons.Material.Filled.PlayArrow, Color.Success, CreateCallback(Start)),
|
||||
new("stop", "Stop", Icons.Material.Filled.Stop, Color.Warning, CreateCallback(Stop)),
|
||||
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),
|
||||
@@ -109,6 +104,8 @@ namespace Lantean.QBTMud.Components
|
||||
new("copyHashv2", "Info hash v2", Icons.Material.Filled.Tag, Color.Info, CreateCallback(() => Copy(t => t.InfoHashV2))),
|
||||
new("copyMagnet", "Magnet link", Icons.Material.Filled.TextFields, Color.Info, CreateCallback(() => Copy(t => t.MagnetUri))),
|
||||
new("copyId", "Torrent ID", Icons.Material.Filled.TextFields, Color.Info, CreateCallback(() => Copy(t => t.Hash))),
|
||||
new("copyComment", "Comment", Icons.Material.Filled.TextFields, Color.Info, CreateCallback(() => Copy(t => t.Comment))),
|
||||
new("copyContentPath", "Content path", Icons.Material.Filled.TextFields, Color.Info, CreateCallback(() => Copy(t => t.ContentPath))),
|
||||
]),
|
||||
new("export", "Export", Icons.Material.Filled.SaveAlt, Color.Info, CreateCallback(Export)),
|
||||
];
|
||||
@@ -146,32 +143,16 @@ namespace Lantean.QBTMud.Components
|
||||
OverlayVisible = value;
|
||||
}
|
||||
|
||||
protected async Task Pause()
|
||||
protected async Task Stop()
|
||||
{
|
||||
if (MajorVersion < 5)
|
||||
{
|
||||
await ApiClient.PauseTorrents(Hashes);
|
||||
Snackbar.Add("Torrent paused.");
|
||||
}
|
||||
else
|
||||
{
|
||||
await ApiClient.StopTorrents(Hashes);
|
||||
Snackbar.Add("Torrent stopped.");
|
||||
}
|
||||
await ApiClient.StopTorrents(hashes: Hashes.ToArray());
|
||||
Snackbar.Add("Torrent stopped.");
|
||||
}
|
||||
|
||||
protected async Task Resume()
|
||||
protected async Task Start()
|
||||
{
|
||||
if (MajorVersion < 5)
|
||||
{
|
||||
await ApiClient.ResumeTorrents(Hashes);
|
||||
Snackbar.Add("Torrent resumed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
await ApiClient.StartTorrents(Hashes);
|
||||
Snackbar.Add("Torrent started.");
|
||||
}
|
||||
await ApiClient.StartTorrents(hashes: Hashes.ToArray());
|
||||
Snackbar.Add("Torrent started.");
|
||||
}
|
||||
|
||||
protected async Task ForceStart()
|
||||
@@ -182,7 +163,7 @@ namespace Lantean.QBTMud.Components
|
||||
|
||||
protected async Task Remove()
|
||||
{
|
||||
var deleted = await DialogService.InvokeDeleteTorrentDialog(ApiClient, Hashes.ToArray());
|
||||
var deleted = await DialogService.InvokeDeleteTorrentDialog(ApiClient, Preferences?.ConfirmTorrentDeletion == true, Hashes.ToArray());
|
||||
|
||||
if (deleted)
|
||||
{
|
||||
@@ -278,7 +259,7 @@ namespace Lantean.QBTMud.Components
|
||||
|
||||
protected async Task ForceRecheck()
|
||||
{
|
||||
await ApiClient.RecheckTorrents(null, Hashes.ToArray());
|
||||
await DialogService.ForceRecheckAsync(ApiClient, Hashes, Preferences?.ConfirmTorrentRecheck == true);
|
||||
}
|
||||
|
||||
protected async Task ForceReannounce()
|
||||
@@ -385,8 +366,8 @@ namespace Lantean.QBTMud.Components
|
||||
var allAreFirstLastPiecePrio = true;
|
||||
var thereAreFirstLastPiecePrio = false;
|
||||
var allAreDownloaded = true;
|
||||
var allArePaused = true;
|
||||
var thereArePaused = false;
|
||||
var allAreStopped = true;
|
||||
var thereAreStopped = false;
|
||||
var allAreForceStart = true;
|
||||
var thereAreForceStart = false;
|
||||
var allAreSuperSeeding = true;
|
||||
@@ -424,27 +405,13 @@ namespace Lantean.QBTMud.Components
|
||||
allAreSuperSeeding = false;
|
||||
}
|
||||
|
||||
if (MajorVersion < 5)
|
||||
if (torrent.State != "stoppedUP" && torrent.State != "stoppedDL")
|
||||
{
|
||||
if (torrent.State != "pausedUP" && torrent.State != "pausedDL")
|
||||
{
|
||||
allArePaused = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
thereArePaused = true;
|
||||
}
|
||||
allAreStopped = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (torrent.State != "stoppedUP" && torrent.State != "stoppedDL")
|
||||
{
|
||||
allArePaused = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
thereArePaused = true;
|
||||
}
|
||||
thereAreStopped = true;
|
||||
}
|
||||
|
||||
if (!torrent.ForceStart)
|
||||
@@ -532,7 +499,7 @@ namespace Lantean.QBTMud.Components
|
||||
actionStates["superSeeding"] = ActionState.Hidden;
|
||||
}
|
||||
|
||||
if (allArePaused)
|
||||
if (allAreStopped)
|
||||
{
|
||||
actionStates["pause"] = ActionState.Hidden;
|
||||
}
|
||||
@@ -540,13 +507,11 @@ namespace Lantean.QBTMud.Components
|
||||
{
|
||||
actionStates["forceStart"] = ActionState.Hidden;
|
||||
}
|
||||
else if (!thereArePaused && !thereAreForceStart)
|
||||
else if (!thereAreStopped && !thereAreForceStart)
|
||||
{
|
||||
actionStates["start"] = ActionState.Hidden;
|
||||
}
|
||||
|
||||
if (MajorVersion >= 5)
|
||||
{
|
||||
if (actionStates.TryGetValue("start", out ActionState? startActionState))
|
||||
{
|
||||
startActionState.TextOverride = "Start";
|
||||
@@ -564,7 +529,6 @@ namespace Lantean.QBTMud.Components
|
||||
{
|
||||
actionStates["pause"] = new ActionState { TextOverride = "Stop" };
|
||||
}
|
||||
}
|
||||
|
||||
if (!allAreAutoTmm && thereAreAutoTmm)
|
||||
{
|
||||
@@ -706,4 +670,4 @@ namespace Lantean.QBTMud.Components
|
||||
|
||||
MenuItems,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -171,7 +171,7 @@ namespace Lantean.QBTMud.Components
|
||||
return;
|
||||
}
|
||||
|
||||
await ApiClient.AddTrackersToTorrent(Hash, trackers);
|
||||
await ApiClient.AddTrackersToTorrent(trackers, hashes: new[] { Hash });
|
||||
}
|
||||
|
||||
protected Task EditTrackerToolbar()
|
||||
@@ -211,7 +211,7 @@ namespace Lantean.QBTMud.Components
|
||||
return;
|
||||
}
|
||||
|
||||
await ApiClient.RemoveTrackers(Hash, [tracker.Url]);
|
||||
await ApiClient.RemoveTrackers([tracker.Url], hashes: new[] { Hash });
|
||||
}
|
||||
|
||||
protected Task CopyTrackerUrlToolbar()
|
||||
@@ -303,4 +303,4 @@ namespace Lantean.QBTMud.Components
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using Lantean.QBitTorrentClient;
|
||||
using Lantean.QBitTorrentClient;
|
||||
using ShareLimitAction = Lantean.QBitTorrentClient.Models.ShareLimitAction;
|
||||
using Lantean.QBTMud.Components.Dialogs;
|
||||
using Lantean.QBTMud.Filter;
|
||||
using Lantean.QBTMud.Models;
|
||||
@@ -56,7 +57,7 @@ namespace Lantean.QBTMud.Helpers
|
||||
var addTorrentParams = CreateAddTorrentParams(options);
|
||||
addTorrentParams.Torrents = files;
|
||||
|
||||
await apiClient.AddTorrent(addTorrentParams);
|
||||
_ = await apiClient.AddTorrent(addTorrentParams);
|
||||
|
||||
foreach (var stream in streams)
|
||||
{
|
||||
@@ -74,18 +75,19 @@ namespace Lantean.QBTMud.Helpers
|
||||
{
|
||||
addTorrentParams.ContentLayout = Enum.Parse<QBitTorrentClient.Models.TorrentContentLayout>(options.ContentLayout);
|
||||
}
|
||||
if (string.IsNullOrEmpty(options.Cookie))
|
||||
{
|
||||
addTorrentParams.Cookie = options.Cookie;
|
||||
}
|
||||
addTorrentParams.DownloadLimit = options.DownloadLimit;
|
||||
addTorrentParams.DownloadPath = options.DownloadPath;
|
||||
if (!string.IsNullOrWhiteSpace(options.DownloadPath))
|
||||
{
|
||||
addTorrentParams.DownloadPath = options.DownloadPath;
|
||||
}
|
||||
addTorrentParams.FirstLastPiecePriority = options.DownloadFirstAndLastPiecesFirst;
|
||||
addTorrentParams.InactiveSeedingTimeLimit = options.InactiveSeedingTimeLimit;
|
||||
addTorrentParams.Paused = !options.StartTorrent;
|
||||
addTorrentParams.RatioLimit = options.RatioLimit;
|
||||
addTorrentParams.RenameTorrent = options.RenameTorrent;
|
||||
addTorrentParams.SavePath = options.SavePath;
|
||||
if (!options.TorrentManagementMode)
|
||||
{
|
||||
addTorrentParams.SavePath = options.SavePath;
|
||||
}
|
||||
addTorrentParams.SeedingTimeLimit = options.SeedingTimeLimit;
|
||||
addTorrentParams.SequentialDownload = options.DownloadInSequentialOrder;
|
||||
if (!string.IsNullOrEmpty(options.ShareLimitAction))
|
||||
@@ -100,7 +102,10 @@ namespace Lantean.QBTMud.Helpers
|
||||
addTorrentParams.Stopped = !options.StartTorrent;
|
||||
addTorrentParams.Tags = options.Tags;
|
||||
addTorrentParams.UploadLimit = options.UploadLimit;
|
||||
addTorrentParams.UseDownloadPath = options.UseDownloadPath;
|
||||
if (options.UseDownloadPath.HasValue)
|
||||
{
|
||||
addTorrentParams.UseDownloadPath = options.UseDownloadPath;
|
||||
}
|
||||
return addTorrentParams;
|
||||
}
|
||||
|
||||
@@ -123,10 +128,10 @@ namespace Lantean.QBTMud.Helpers
|
||||
var addTorrentParams = CreateAddTorrentParams(options);
|
||||
addTorrentParams.Urls = options.Urls;
|
||||
|
||||
await apiClient.AddTorrent(addTorrentParams);
|
||||
_ = await apiClient.AddTorrent(addTorrentParams);
|
||||
}
|
||||
|
||||
public static async Task<bool> InvokeDeleteTorrentDialog(this IDialogService dialogService, IApiClient apiClient, params string[] hashes)
|
||||
public static async Task<bool> InvokeDeleteTorrentDialog(this IDialogService dialogService, IApiClient apiClient, bool confirmTorrentDeletion, params string[] hashes)
|
||||
{
|
||||
if (hashes.Length == 0)
|
||||
{
|
||||
@@ -138,6 +143,12 @@ namespace Lantean.QBTMud.Helpers
|
||||
{ nameof(DeleteDialog.Count), hashes.Length }
|
||||
};
|
||||
|
||||
if (!confirmTorrentDeletion)
|
||||
{
|
||||
await apiClient.DeleteTorrents(hashes: hashes, deleteFiles: false);
|
||||
return true;
|
||||
}
|
||||
|
||||
var reference = await dialogService.ShowAsync<DeleteDialog>($"Remove torrent{(hashes.Length == 1 ? "" : "s")}?", parameters, ConfirmDialogOptions);
|
||||
var dialogResult = await reference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
|
||||
@@ -150,6 +161,28 @@ namespace Lantean.QBTMud.Helpers
|
||||
return true;
|
||||
}
|
||||
|
||||
public static async Task ForceRecheckAsync(this IDialogService dialogService, IApiClient apiClient, IEnumerable<string> hashes, bool confirmTorrentRecheck)
|
||||
{
|
||||
var hashArray = hashes?.ToArray() ?? [];
|
||||
if (hashArray.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (confirmTorrentRecheck)
|
||||
{
|
||||
var content = $"Are you sure you want to recheck the selected torrent{(hashArray.Length == 1 ? "" : "s")}?";
|
||||
|
||||
var confirmed = await dialogService.ShowConfirmDialog("Force recheck", content);
|
||||
if (!confirmed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await apiClient.RecheckTorrents(null, hashArray);
|
||||
}
|
||||
|
||||
public static async Task InvokeDownloadRateDialog(this IDialogService dialogService, IApiClient apiClient, long rate, IEnumerable<string> hashes)
|
||||
{
|
||||
Func<long, string> valueDisplayFunc = v => v == Limits.NoLimit ? "∞" : v.ToString();
|
||||
@@ -217,21 +250,30 @@ namespace Lantean.QBTMud.Helpers
|
||||
|
||||
public static async Task InvokeShareRatioDialog(this IDialogService dialogService, IApiClient apiClient, IEnumerable<Torrent> torrents)
|
||||
{
|
||||
var torrentShareRatios = torrents.Select(t => new ShareRatioMax
|
||||
var torrentList = torrents.ToList();
|
||||
if (torrentList.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var shareRatioValues = torrentList.Select(t => new ShareRatioMax
|
||||
{
|
||||
InactiveSeedingTimeLimit = t.InactiveSeedingTimeLimit,
|
||||
MaxInactiveSeedingTime = t.InactiveSeedingTimeLimit,
|
||||
MaxInactiveSeedingTime = t.MaxInactiveSeedingTime,
|
||||
MaxRatio = t.MaxRatio,
|
||||
MaxSeedingTime = t.MaxSeedingTime,
|
||||
RatioLimit = t.RatioLimit,
|
||||
SeedingTimeLimit = t.SeedingTimeLimit,
|
||||
});
|
||||
ShareLimitAction = t.ShareLimitAction,
|
||||
}).ToList();
|
||||
|
||||
var torrentsHaveSameShareRatio = torrentShareRatios.Distinct().Count() == 1;
|
||||
var referenceValue = shareRatioValues[0];
|
||||
var torrentsHaveSameShareRatio = shareRatioValues.Distinct().Count() == 1;
|
||||
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
{ nameof(ShareRatioDialog.Value), torrentsHaveSameShareRatio ? torrentShareRatios.FirstOrDefault() : null },
|
||||
{ nameof(ShareRatioDialog.Value), torrentsHaveSameShareRatio ? referenceValue : null },
|
||||
{ nameof(ShareRatioDialog.CurrentValue), referenceValue },
|
||||
};
|
||||
var result = await dialogService.ShowAsync<ShareRatioDialog>("Share ratio", parameters, FormDialogOptions);
|
||||
|
||||
@@ -243,7 +285,7 @@ namespace Lantean.QBTMud.Helpers
|
||||
|
||||
var shareRatio = (ShareRatio)dialogResult.Data;
|
||||
|
||||
await apiClient.SetTorrentShareLimit(shareRatio.RatioLimit, shareRatio.SeedingTimeLimit, shareRatio.InactiveSeedingTimeLimit, null, torrents.Select(t => t.Hash).ToArray());
|
||||
await apiClient.SetTorrentShareLimit(shareRatio.RatioLimit, shareRatio.SeedingTimeLimit, shareRatio.InactiveSeedingTimeLimit, shareRatio.ShareLimitAction ?? ShareLimitAction.Default, hashes: torrentList.Select(t => t.Hash).ToArray());
|
||||
}
|
||||
|
||||
public static async Task InvokeStringFieldDialog(this IDialogService dialogService, string title, string label, string? value, Func<string, Task> onSuccess)
|
||||
@@ -436,4 +478,6 @@ namespace Lantean.QBTMud.Helpers
|
||||
await dialogService.ShowAsync<SubMenuDialog>(parent.Text, parameters, FormDialogOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using ByteSizeLib;
|
||||
using Lantean.QBTMud.Models;
|
||||
using Lantean.QBitTorrentClient;
|
||||
using MudBlazor;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
@@ -404,8 +405,6 @@ namespace Lantean.QBTMud.Helpers
|
||||
Status.Downloading => (Icons.Material.Filled.Downloading, Color.Success),
|
||||
Status.Seeding => (Icons.Material.Filled.Upload, Color.Info),
|
||||
Status.Completed => (Icons.Material.Filled.Check, Color.Default),
|
||||
Status.Resumed => (Icons.Material.Filled.PlayArrow, Color.Success),
|
||||
Status.Paused => (Icons.Material.Filled.Pause, Color.Default),
|
||||
Status.Stopped => (Icons.Material.Filled.Stop, Color.Default),
|
||||
Status.Active => (Icons.Material.Filled.Sort, Color.Success),
|
||||
Status.Inactive => (Icons.Material.Filled.Sort, Color.Error),
|
||||
@@ -417,5 +416,25 @@ namespace Lantean.QBTMud.Helpers
|
||||
_ => (Icons.Material.Filled.QuestionMark, Color.Inherit),
|
||||
};
|
||||
}
|
||||
|
||||
public static string Bool(bool value, string trueText = "Yes", string falseText = "No")
|
||||
{
|
||||
return value ? trueText : falseText;
|
||||
}
|
||||
|
||||
public static string RatioLimit(float value)
|
||||
{
|
||||
if (value == Limits.GlobalLimit)
|
||||
{
|
||||
return "Global";
|
||||
}
|
||||
|
||||
if (value <= Limits.NoLimit)
|
||||
{
|
||||
return "∞";
|
||||
}
|
||||
|
||||
return value.ToString("0.00");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -200,15 +200,8 @@ namespace Lantean.QBTMud.Helpers
|
||||
|
||||
break;
|
||||
|
||||
case Status.Resumed:
|
||||
if (!state.Contains("resumed"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case Status.Paused:
|
||||
if (!state.Contains("paused") && !state.Contains("stopped"))
|
||||
case Status.Stopped:
|
||||
if (state != "stoppedDL" && state != "stoppedUP")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -285,4 +278,4 @@ namespace Lantean.QBTMud.Helpers
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,33 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -33,6 +33,14 @@
|
||||
}
|
||||
<MudSpacer />
|
||||
<MudText Class="mx-2 mb-1 d-none d-sm-flex">@DisplayHelpers.Size(MainData?.ServerState.FreeSpaceOnDisk, "Free space: ")</MudText>
|
||||
@{
|
||||
var externalIpLabel = Preferences?.StatusBarExternalIp == true ? BuildExternalIpLabel(MainData?.ServerState) : null;
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(externalIpLabel))
|
||||
{
|
||||
<MudDivider Vertical="true" Class="d-none d-sm-flex" />
|
||||
<MudText Class="mx-2 mb-1 d-none d-sm-flex">@externalIpLabel</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" />
|
||||
@@ -70,4 +78,4 @@
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
|
@@ -97,7 +97,7 @@ namespace Lantean.QBTMud.Layout
|
||||
Preferences = await ApiClient.GetApplicationPreferences();
|
||||
Version = await ApiClient.GetApplicationVersion();
|
||||
var data = await ApiClient.GetMainData(_requestId);
|
||||
MainData = DataManager.CreateMainData(data, Version);
|
||||
MainData = DataManager.CreateMainData(data);
|
||||
MarkTorrentsDirty();
|
||||
|
||||
_requestId = data.ResponseId;
|
||||
@@ -145,7 +145,7 @@ namespace Lantean.QBTMud.Layout
|
||||
|
||||
if (MainData is null || data.FullUpdate)
|
||||
{
|
||||
MainData = DataManager.CreateMainData(data, Version);
|
||||
MainData = DataManager.CreateMainData(data);
|
||||
MarkTorrentsDirty();
|
||||
shouldRender = true;
|
||||
}
|
||||
@@ -201,6 +201,32 @@ namespace Lantean.QBTMud.Layout
|
||||
};
|
||||
}
|
||||
|
||||
private static string? BuildExternalIpLabel(ServerState? serverState)
|
||||
{
|
||||
if (serverState is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var v4 = serverState.LastExternalAddressV4;
|
||||
var v6 = serverState.LastExternalAddressV6;
|
||||
var hasV4 = !string.IsNullOrWhiteSpace(v4);
|
||||
var hasV6 = !string.IsNullOrWhiteSpace(v6);
|
||||
|
||||
if (!hasV4 && !hasV6)
|
||||
{
|
||||
return "External IP: N/A";
|
||||
}
|
||||
|
||||
if (hasV4 && hasV6)
|
||||
{
|
||||
return $"External IPs: {v4}, {v6}";
|
||||
}
|
||||
|
||||
var address = hasV4 ? v4 : v6;
|
||||
return $"External IP: {address}";
|
||||
}
|
||||
|
||||
private void OnCategoryChanged(string category)
|
||||
{
|
||||
if (Category == category)
|
||||
@@ -291,4 +317,4 @@ namespace Lantean.QBTMud.Layout
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -11,8 +11,7 @@
|
||||
Dictionary<string, HashSet<string>> tagState,
|
||||
Dictionary<string, HashSet<string>> categoriesState,
|
||||
Dictionary<string, HashSet<string>> statusState,
|
||||
Dictionary<string, HashSet<string>> trackersState,
|
||||
int majorVersion)
|
||||
Dictionary<string, HashSet<string>> trackersState)
|
||||
{
|
||||
Torrents = torrents.ToDictionary();
|
||||
Tags = tags.ToHashSet();
|
||||
@@ -23,7 +22,6 @@
|
||||
CategoriesState = categoriesState;
|
||||
StatusState = statusState;
|
||||
TrackersState = trackersState;
|
||||
MajorVersion = majorVersion;
|
||||
}
|
||||
|
||||
public Dictionary<string, Torrent> Torrents { get; }
|
||||
@@ -38,6 +36,5 @@
|
||||
public Dictionary<string, HashSet<string>> TrackersState { get; }
|
||||
public string? SelectedTorrentHash { get; set; }
|
||||
public bool LostConnection { get; set; }
|
||||
public int MajorVersion { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -27,7 +27,17 @@
|
||||
long uploadRateLimit,
|
||||
bool useAltSpeedLimits,
|
||||
bool useSubcategories,
|
||||
float writeCacheOverload) : base(connectionStatus, dHTNodes, downloadInfoData, downloadInfoSpeed, downloadRateLimit, uploadInfoData, uploadInfoSpeed, uploadRateLimit)
|
||||
float writeCacheOverload,
|
||||
string lastExternalAddressV4,
|
||||
string lastExternalAddressV6) : base(
|
||||
connectionStatus,
|
||||
dHTNodes,
|
||||
downloadInfoData,
|
||||
downloadInfoSpeed,
|
||||
downloadRateLimit,
|
||||
uploadInfoData,
|
||||
uploadInfoSpeed,
|
||||
uploadRateLimit)
|
||||
{
|
||||
AllTimeDownloaded = allTimeDownloaded;
|
||||
AllTimeUploaded = allTimeUploaded;
|
||||
@@ -46,6 +56,8 @@
|
||||
UseAltSpeedLimits = useAltSpeedLimits;
|
||||
UseSubcategories = useSubcategories;
|
||||
WriteCacheOverload = writeCacheOverload;
|
||||
LastExternalAddressV4 = lastExternalAddressV4;
|
||||
LastExternalAddressV6 = lastExternalAddressV6;
|
||||
}
|
||||
|
||||
public ServerState()
|
||||
@@ -85,5 +97,9 @@
|
||||
public bool UseSubcategories { get; set; }
|
||||
|
||||
public float WriteCacheOverload { get; set; }
|
||||
|
||||
public string LastExternalAddressV4 { get; set; } = string.Empty;
|
||||
|
||||
public string LastExternalAddressV6 { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,13 @@
|
||||
namespace Lantean.QBTMud.Models
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
|
||||
namespace Lantean.QBTMud.Models
|
||||
{
|
||||
public record ShareRatio
|
||||
{
|
||||
public float RatioLimit { get; set; }
|
||||
public float SeedingTimeLimit { get; set; }
|
||||
public float InactiveSeedingTimeLimit { get; set; }
|
||||
public ShareLimitAction? ShareLimitAction { get; set; }
|
||||
}
|
||||
|
||||
public record ShareRatioMax : ShareRatio
|
||||
@@ -13,4 +16,4 @@
|
||||
public float MaxSeedingTime { get; set; }
|
||||
public float MaxInactiveSeedingTime { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,8 +6,6 @@
|
||||
Downloading,
|
||||
Seeding,
|
||||
Completed,
|
||||
Resumed,
|
||||
Paused,
|
||||
Stopped,
|
||||
Active,
|
||||
Inactive,
|
||||
@@ -17,4 +15,4 @@
|
||||
Checking,
|
||||
Errored,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,9 @@
|
||||
namespace Lantean.QBTMud.Models
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
|
||||
namespace Lantean.QBTMud.Models
|
||||
{
|
||||
public class Torrent
|
||||
{
|
||||
@@ -52,7 +57,13 @@
|
||||
long uploadSpeed,
|
||||
long reannounce,
|
||||
float inactiveSeedingTimeLimit,
|
||||
float maxInactiveSeedingTime)
|
||||
float maxInactiveSeedingTime,
|
||||
float popularity,
|
||||
string downloadPath,
|
||||
string rootPath,
|
||||
bool isPrivate,
|
||||
ShareLimitAction shareLimitAction,
|
||||
string comment)
|
||||
{
|
||||
Hash = hash;
|
||||
AddedOn = addedOn;
|
||||
@@ -104,21 +115,31 @@
|
||||
Reannounce = reannounce;
|
||||
InactiveSeedingTimeLimit = inactiveSeedingTimeLimit;
|
||||
MaxInactiveSeedingTime = maxInactiveSeedingTime;
|
||||
Popularity = popularity;
|
||||
DownloadPath = downloadPath;
|
||||
RootPath = rootPath;
|
||||
IsPrivate = isPrivate;
|
||||
ShareLimitAction = shareLimitAction;
|
||||
Comment = comment;
|
||||
}
|
||||
|
||||
protected Torrent()
|
||||
{
|
||||
Hash = "";
|
||||
Category = "";
|
||||
ContentPath = "";
|
||||
InfoHashV1 = "";
|
||||
InfoHashV2 = "";
|
||||
MagnetUri = "";
|
||||
Name = "";
|
||||
SavePath = "";
|
||||
State = "";
|
||||
Tags = [];
|
||||
Tracker = "";
|
||||
Hash = string.Empty;
|
||||
Category = string.Empty;
|
||||
ContentPath = string.Empty;
|
||||
InfoHashV1 = string.Empty;
|
||||
InfoHashV2 = string.Empty;
|
||||
MagnetUri = string.Empty;
|
||||
Name = string.Empty;
|
||||
SavePath = string.Empty;
|
||||
DownloadPath = string.Empty;
|
||||
RootPath = string.Empty;
|
||||
State = string.Empty;
|
||||
Tags = new List<string>();
|
||||
Tracker = string.Empty;
|
||||
ShareLimitAction = ShareLimitAction.Default;
|
||||
Comment = string.Empty;
|
||||
}
|
||||
|
||||
public string Hash { get; }
|
||||
@@ -183,8 +204,14 @@
|
||||
|
||||
public float RatioLimit { get; set; }
|
||||
|
||||
public float Popularity { get; set; }
|
||||
|
||||
public string SavePath { get; set; }
|
||||
|
||||
public string DownloadPath { get; set; }
|
||||
|
||||
public string RootPath { get; set; }
|
||||
|
||||
public long SeedingTime { get; set; }
|
||||
|
||||
public int SeedingTimeLimit { get; set; }
|
||||
@@ -221,6 +248,12 @@
|
||||
|
||||
public float MaxInactiveSeedingTime { get; set; }
|
||||
|
||||
public bool IsPrivate { get; set; }
|
||||
|
||||
public ShareLimitAction ShareLimitAction { get; set; }
|
||||
|
||||
public string Comment { get; set; }
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is null)
|
||||
@@ -241,4 +274,4 @@
|
||||
return Hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -295,6 +295,7 @@ namespace Lantean.QBTMud.Pages
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Up Speed", t => t.UploadSpeed, t => DisplayHelpers.Speed(t.UploadSpeed)),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("ETA", t => t.EstimatedTimeOfArrival, t => DisplayHelpers.Duration(t.EstimatedTimeOfArrival)),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Ratio", t => t.Ratio, t => t.Ratio.ToString("0.00")),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Popularity", t => t.Popularity, t => t.Popularity.ToString("0.00")),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Category", t => t.Category),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Tags", t => t.Tags, t => string.Join(", ", t.Tags)),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Added On", t => t.AddedOn, t => DisplayHelpers.DateTime(t.AddedOn)),
|
||||
@@ -310,11 +311,15 @@ namespace Lantean.QBTMud.Pages
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Time Active", t => t.TimeActive, t => DisplayHelpers.Duration(t.TimeActive), enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Save path", t => t.SavePath, enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Completed", t => t.Completed, t => DisplayHelpers.Size(t.Completed), enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Ratio Limit", t => t.RatioLimit, t => t.Ratio.ToString("0.00"), enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Ratio Limit", t => t.RatioLimit, t => DisplayHelpers.RatioLimit(t.RatioLimit), enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Last Seen Complete", t => t.SeenComplete, t => DisplayHelpers.DateTime(t.SeenComplete), enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Last Activity", t => t.LastActivity, t => DisplayHelpers.DateTime(t.LastActivity), enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Availability", t => t.Availability, t => t.Availability.ToString("0.##"), enabled: false),
|
||||
//ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Reannounce In", t => t.Reannounce, enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Incomplete Save Path", t => t.DownloadPath, t => DisplayHelpers.EmptyIfNull(t.DownloadPath), enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Info Hash v1", t => t.InfoHashV1, t => DisplayHelpers.EmptyIfNull(t.InfoHashV1), enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Info Hash v2", t => t.InfoHashV2, t => DisplayHelpers.EmptyIfNull(t.InfoHashV2), enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Reannounce In", t => t.Reannounce, t => DisplayHelpers.Duration(t.Reannounce), enabled: false),
|
||||
ColumnDefinitionHelper.CreateColumnDefinition<Torrent>("Private", t => t.IsPrivate, t => DisplayHelpers.Bool(t.IsPrivate), enabled: false),
|
||||
];
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
@@ -338,4 +343,4 @@ namespace Lantean.QBTMud.Pages
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using Lantean.QBTMud.Helpers;
|
||||
using Lantean.QBTMud.Helpers;
|
||||
using Lantean.QBTMud.Models;
|
||||
using ShareLimitAction = Lantean.QBitTorrentClient.Models.ShareLimitAction;
|
||||
|
||||
namespace Lantean.QBTMud.Services
|
||||
{
|
||||
@@ -25,9 +26,8 @@ namespace Lantean.QBTMud.Services
|
||||
return peerList;
|
||||
}
|
||||
|
||||
public MainData CreateMainData(QBitTorrentClient.Models.MainData mainData, string version)
|
||||
public MainData CreateMainData(QBitTorrentClient.Models.MainData mainData)
|
||||
{
|
||||
var majorVersion = VersionHelper.GetMajorVersion(version);
|
||||
var torrents = new Dictionary<string, Torrent>(mainData.Torrents?.Count ?? 0);
|
||||
if (mainData.Torrents is not null)
|
||||
{
|
||||
@@ -95,7 +95,7 @@ namespace Lantean.QBTMud.Services
|
||||
categoriesState.Add(category, torrents.Values.Where(t => FilterHelper.FilterCategory(t, category, serverState.UseSubcategories)).ToHashesHashSet());
|
||||
}
|
||||
|
||||
var statuses = GetStatuses(majorVersion).ToArray();
|
||||
var statuses = GetStatuses().ToArray();
|
||||
var statusState = new Dictionary<string, HashSet<string>>(statuses.Length + 2);
|
||||
foreach (var status in statuses)
|
||||
{
|
||||
@@ -110,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, majorVersion);
|
||||
var torrentList = new MainData(torrents, tags, categories, trackers, serverState, tagState, categoriesState, statusState, trackersState);
|
||||
|
||||
return torrentList;
|
||||
}
|
||||
@@ -146,7 +146,9 @@ namespace Lantean.QBTMud.Services
|
||||
serverState.UploadRateLimit.GetValueOrDefault(),
|
||||
serverState.UseAltSpeedLimits.GetValueOrDefault(),
|
||||
serverState.UseSubcategories.GetValueOrDefault(),
|
||||
serverState.WriteCacheOverload.GetValueOrDefault());
|
||||
serverState.WriteCacheOverload.GetValueOrDefault(),
|
||||
serverState.LastExternalAddressV4 ?? string.Empty,
|
||||
serverState.LastExternalAddressV6 ?? string.Empty);
|
||||
}
|
||||
|
||||
public bool MergeMainData(QBitTorrentClient.Models.MainData mainData, MainData torrentList, out bool filterChanged)
|
||||
@@ -284,7 +286,7 @@ namespace Lantean.QBTMud.Services
|
||||
{
|
||||
var newTorrent = CreateTorrent(hash, torrent);
|
||||
torrentList.Torrents.Add(hash, newTorrent);
|
||||
AddTorrentToStates(torrentList, hash, torrentList.MajorVersion);
|
||||
AddTorrentToStates(torrentList, hash);
|
||||
dataChanged = true;
|
||||
filterChanged = true;
|
||||
}
|
||||
@@ -316,7 +318,7 @@ namespace Lantean.QBTMud.Services
|
||||
return dataChanged;
|
||||
}
|
||||
|
||||
private static void AddTorrentToStates(MainData torrentList, string hash, int version)
|
||||
private static void AddTorrentToStates(MainData torrentList, string hash)
|
||||
{
|
||||
if (!torrentList.Torrents.TryGetValue(hash, out var torrent))
|
||||
{
|
||||
@@ -329,7 +331,7 @@ namespace Lantean.QBTMud.Services
|
||||
torrentList.CategoriesState[FilterHelper.CATEGORY_ALL].Add(hash);
|
||||
UpdateCategoryState(torrentList, torrent, hash, previousCategory: null);
|
||||
|
||||
foreach (var status in GetStatuses(version))
|
||||
foreach (var status in GetStatuses())
|
||||
{
|
||||
if (!torrentList.StatusState.TryGetValue(status.ToString(), out var statusSet))
|
||||
{
|
||||
@@ -346,21 +348,14 @@ namespace Lantean.QBTMud.Services
|
||||
UpdateTrackerState(torrentList, torrent, hash, previousTracker: null);
|
||||
}
|
||||
|
||||
private static Status[] GetStatuses(int version)
|
||||
private static Status[] GetStatuses()
|
||||
{
|
||||
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();
|
||||
}
|
||||
_statusArray = Enum.GetValues<Status>();
|
||||
|
||||
return _statusArray;
|
||||
}
|
||||
@@ -388,7 +383,7 @@ namespace Lantean.QBTMud.Services
|
||||
torrentList.CategoriesState[FilterHelper.CATEGORY_ALL].Remove(hash);
|
||||
UpdateCategoryStateForRemoval(torrentList, hash, snapshot.Category);
|
||||
|
||||
foreach (var status in GetStatuses(torrentList.MajorVersion))
|
||||
foreach (var status in GetStatuses())
|
||||
{
|
||||
if (!torrentList.StatusState.TryGetValue(status.ToString(), out var statusState))
|
||||
{
|
||||
@@ -559,6 +554,18 @@ namespace Lantean.QBTMud.Services
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.LastExternalAddressV4 is not null && existingServerState.LastExternalAddressV4 != serverState.LastExternalAddressV4)
|
||||
{
|
||||
existingServerState.LastExternalAddressV4 = serverState.LastExternalAddressV4;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (serverState.LastExternalAddressV6 is not null && existingServerState.LastExternalAddressV6 != serverState.LastExternalAddressV6)
|
||||
{
|
||||
existingServerState.LastExternalAddressV6 = serverState.LastExternalAddressV6;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
@@ -694,7 +701,13 @@ namespace Lantean.QBTMud.Services
|
||||
torrent.UploadSpeed.GetValueOrDefault(),
|
||||
torrent.Reannounce ?? 0,
|
||||
torrent.InactiveSeedingTimeLimit.GetValueOrDefault(),
|
||||
torrent.MaxInactiveSeedingTime.GetValueOrDefault());
|
||||
torrent.MaxInactiveSeedingTime.GetValueOrDefault(),
|
||||
torrent.Popularity.GetValueOrDefault(),
|
||||
torrent.DownloadPath ?? string.Empty,
|
||||
torrent.RootPath ?? string.Empty,
|
||||
torrent.IsPrivate.GetValueOrDefault(),
|
||||
torrent.ShareLimitAction ?? ShareLimitAction.Default,
|
||||
torrent.Comment ?? string.Empty);
|
||||
}
|
||||
|
||||
private static string NormalizeTag(string? tag)
|
||||
@@ -852,7 +865,7 @@ namespace Lantean.QBTMud.Services
|
||||
|
||||
private static void UpdateStatusState(MainData torrentList, string hash, string previousState, long previousUploadSpeed, string newState, long newUploadSpeed)
|
||||
{
|
||||
foreach (var status in GetStatuses(torrentList.MajorVersion))
|
||||
foreach (var status in GetStatuses())
|
||||
{
|
||||
if (!torrentList.StatusState.TryGetValue(status.ToString(), out var statusSet))
|
||||
{
|
||||
@@ -1320,6 +1333,41 @@ namespace Lantean.QBTMud.Services
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.Popularity.HasValue && existingTorrent.Popularity != torrent.Popularity.Value)
|
||||
{
|
||||
existingTorrent.Popularity = torrent.Popularity.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.DownloadPath is not null && !string.Equals(existingTorrent.DownloadPath, torrent.DownloadPath, StringComparison.Ordinal))
|
||||
{
|
||||
existingTorrent.DownloadPath = torrent.DownloadPath;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.RootPath is not null && !string.Equals(existingTorrent.RootPath, torrent.RootPath, StringComparison.Ordinal))
|
||||
{
|
||||
existingTorrent.RootPath = torrent.RootPath;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.IsPrivate.HasValue && existingTorrent.IsPrivate != torrent.IsPrivate.Value)
|
||||
{
|
||||
existingTorrent.IsPrivate = torrent.IsPrivate.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
if (torrent.ShareLimitAction.HasValue && existingTorrent.ShareLimitAction != torrent.ShareLimitAction.Value)
|
||||
{
|
||||
existingTorrent.ShareLimitAction = torrent.ShareLimitAction.Value;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
if (torrent.Comment is not null && !string.Equals(existingTorrent.Comment, torrent.Comment, StringComparison.Ordinal))
|
||||
{
|
||||
existingTorrent.Comment = torrent.Comment;
|
||||
dataChanged = true;
|
||||
}
|
||||
|
||||
return new TorrentUpdateResult(dataChanged, filterChanged);
|
||||
}
|
||||
|
||||
@@ -1456,7 +1504,7 @@ namespace Lantean.QBTMud.Services
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (System.Math.Abs(destination.Progress - source.Progress) > floatTolerance)
|
||||
if (Math.Abs(destination.Progress - source.Progress) > floatTolerance)
|
||||
{
|
||||
destination.Progress = source.Progress;
|
||||
changed = true;
|
||||
@@ -1468,7 +1516,7 @@ namespace Lantean.QBTMud.Services
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (System.Math.Abs(destination.Availability - source.Availability) > floatTolerance)
|
||||
if (Math.Abs(destination.Availability - source.Availability) > floatTolerance)
|
||||
{
|
||||
destination.Availability = source.Availability;
|
||||
changed = true;
|
||||
@@ -1738,7 +1786,7 @@ namespace Lantean.QBTMud.Services
|
||||
SocketReceiveBufferSize = changed.SocketReceiveBufferSize,
|
||||
SocketSendBufferSize = changed.SocketSendBufferSize,
|
||||
SsrfMitigation = changed.SsrfMitigation,
|
||||
StartPausedEnabled = changed.StartPausedEnabled,
|
||||
AddStoppedEnabled = changed.AddStoppedEnabled,
|
||||
StopTrackerTimeout = changed.StopTrackerTimeout,
|
||||
TempPath = changed.TempPath,
|
||||
TempPathEnabled = changed.TempPathEnabled,
|
||||
@@ -1944,7 +1992,7 @@ namespace Lantean.QBTMud.Services
|
||||
original.SocketReceiveBufferSize = changed.SocketReceiveBufferSize ?? original.SocketReceiveBufferSize;
|
||||
original.SocketSendBufferSize = changed.SocketSendBufferSize ?? original.SocketSendBufferSize;
|
||||
original.SsrfMitigation = changed.SsrfMitigation ?? original.SsrfMitigation;
|
||||
original.StartPausedEnabled = changed.StartPausedEnabled ?? original.StartPausedEnabled;
|
||||
original.AddStoppedEnabled = changed.AddStoppedEnabled ?? original.AddStoppedEnabled;
|
||||
original.StopTrackerTimeout = changed.StopTrackerTimeout ?? original.StopTrackerTimeout;
|
||||
original.TempPath = changed.TempPath ?? original.TempPath;
|
||||
original.TempPathEnabled = changed.TempPathEnabled ?? original.TempPathEnabled;
|
||||
@@ -2006,7 +2054,7 @@ namespace Lantean.QBTMud.Services
|
||||
? int.MaxValue
|
||||
: contents.Values.Min(c => c.Index);
|
||||
var minFileIndex = files.Min(f => f.Index);
|
||||
var nextFolderIndex = System.Math.Min(minExistingIndex, minFileIndex) - 1;
|
||||
var nextFolderIndex = Math.Min(minExistingIndex, minFileIndex) - 1;
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
@@ -2134,4 +2182,4 @@ namespace Lantean.QBTMud.Services
|
||||
return new RssList(feeds, articles);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ namespace Lantean.QBTMud.Services
|
||||
{
|
||||
public interface IDataManager
|
||||
{
|
||||
MainData CreateMainData(QBitTorrentClient.Models.MainData mainData, string version);
|
||||
MainData CreateMainData(QBitTorrentClient.Models.MainData mainData);
|
||||
|
||||
Torrent CreateTorrent(string hash, QBitTorrentClient.Models.Torrent torrent);
|
||||
|
||||
@@ -22,4 +22,4 @@ namespace Lantean.QBTMud.Services
|
||||
|
||||
RssList CreateRssList(IReadOnlyDictionary<string, QBitTorrentClient.Models.RssItem> rssItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
11
Lantean.QBitTorrentClient.Test/UnitTest1.cs
Normal file
11
Lantean.QBitTorrentClient.Test/UnitTest1.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Lantean.QBitTorrentClient.Test
|
||||
{
|
||||
public class UnitTest1
|
||||
{
|
||||
[Fact]
|
||||
public void Test1()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,9 @@
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
@@ -105,6 +110,8 @@ namespace Lantean.QBitTorrentClient
|
||||
|
||||
public async Task SetApplicationPreferences(UpdatePreferences preferences)
|
||||
{
|
||||
preferences.Validate();
|
||||
|
||||
var json = JsonSerializer.Serialize(preferences, _options);
|
||||
|
||||
var content = new FormUrlEncodedBuilder()
|
||||
@@ -116,6 +123,49 @@ namespace Lantean.QBitTorrentClient
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<ApplicationCookie>> GetApplicationCookies()
|
||||
{
|
||||
var response = await _httpClient.GetAsync("app/cookies");
|
||||
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
|
||||
return await GetJsonList<ApplicationCookie>(response.Content);
|
||||
}
|
||||
|
||||
public async Task SetApplicationCookies(IEnumerable<ApplicationCookie> cookies)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(cookies, _options);
|
||||
|
||||
var content = new FormUrlEncodedBuilder()
|
||||
.Add("cookies", json)
|
||||
.ToFormUrlEncodedContent();
|
||||
|
||||
var response = await _httpClient.PostAsync("app/setCookies", content);
|
||||
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
}
|
||||
|
||||
public async Task<string> RotateApiKey()
|
||||
{
|
||||
var response = await _httpClient.PostAsync("app/rotateAPIKey", null);
|
||||
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync();
|
||||
if (string.IsNullOrWhiteSpace(payload))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var json = JsonSerializer.Deserialize<JsonElement>(payload, _options);
|
||||
if (json.ValueKind == JsonValueKind.Object && json.TryGetProperty("apiKey", out var apiKeyElement))
|
||||
{
|
||||
return apiKeyElement.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public async Task<string> GetDefaultSavePath()
|
||||
{
|
||||
var response = await _httpClient.GetAsync("app/defaultSavePath");
|
||||
@@ -145,6 +195,43 @@ namespace Lantean.QBitTorrentClient
|
||||
|
||||
#endregion Application
|
||||
|
||||
#region Client data
|
||||
|
||||
public async Task<IReadOnlyDictionary<string, JsonElement>> LoadClientData(IEnumerable<string>? keys = null)
|
||||
{
|
||||
HttpResponseMessage response;
|
||||
if (keys is null)
|
||||
{
|
||||
response = await _httpClient.GetAsync("clientdata/load");
|
||||
}
|
||||
else
|
||||
{
|
||||
var query = new QueryBuilder()
|
||||
.Add("keys", JsonSerializer.Serialize(keys, _options));
|
||||
|
||||
response = await _httpClient.GetAsync("clientdata/load", query);
|
||||
}
|
||||
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
|
||||
return await GetJsonDictionary<string, JsonElement>(response.Content);
|
||||
}
|
||||
|
||||
public async Task StoreClientData(IReadOnlyDictionary<string, JsonElement> data)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(data, _options);
|
||||
|
||||
var content = new FormUrlEncodedBuilder()
|
||||
.Add("data", json)
|
||||
.ToFormUrlEncodedContent();
|
||||
|
||||
var response = await _httpClient.PostAsync("clientdata/store", content);
|
||||
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
}
|
||||
|
||||
#endregion Client data
|
||||
|
||||
#region Log
|
||||
|
||||
public async Task<IReadOnlyList<Log>> GetLog(bool? normal = null, bool? info = null, bool? warning = null, bool? critical = null, int? lastKnownId = null)
|
||||
@@ -305,7 +392,7 @@ namespace Lantean.QBitTorrentClient
|
||||
|
||||
#region Torrent management
|
||||
|
||||
public async Task<IReadOnlyList<Torrent>> GetTorrentList(string? filter = null, string? category = null, string? tag = null, string? sort = null, bool? reverse = null, int? limit = null, int? offset = null, bool? isPrivate = null, params string[] hashes)
|
||||
public async Task<IReadOnlyList<Torrent>> GetTorrentList(string? filter = null, string? category = null, string? tag = null, string? sort = null, bool? reverse = null, int? limit = null, int? offset = null, bool? isPrivate = null, bool? includeFiles = null, params string[] hashes)
|
||||
{
|
||||
var query = new QueryBuilder();
|
||||
if (filter is not null)
|
||||
@@ -344,6 +431,10 @@ namespace Lantean.QBitTorrentClient
|
||||
{
|
||||
query.Add("private", isPrivate.Value ? "true" : "false");
|
||||
}
|
||||
if (includeFiles is not null)
|
||||
{
|
||||
query.Add("includeFiles", includeFiles.Value ? "true" : "false");
|
||||
}
|
||||
|
||||
var response = await _httpClient.GetAsync("torrents/info", query);
|
||||
|
||||
@@ -379,6 +470,43 @@ namespace Lantean.QBitTorrentClient
|
||||
return await GetJsonList<WebSeed>(response.Content);
|
||||
}
|
||||
|
||||
public async Task AddTorrentWebSeeds(string hash, IEnumerable<string> urls)
|
||||
{
|
||||
var content = new FormUrlEncodedBuilder()
|
||||
.Add("hash", hash)
|
||||
.Add("urls", string.Join('|', urls))
|
||||
.ToFormUrlEncodedContent();
|
||||
|
||||
var response = await _httpClient.PostAsync("torrents/addWebSeeds", content);
|
||||
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
}
|
||||
|
||||
public async Task EditTorrentWebSeed(string hash, string originalUrl, string newUrl)
|
||||
{
|
||||
var content = new FormUrlEncodedBuilder()
|
||||
.Add("hash", hash)
|
||||
.Add("origUrl", originalUrl)
|
||||
.Add("newUrl", newUrl)
|
||||
.ToFormUrlEncodedContent();
|
||||
|
||||
var response = await _httpClient.PostAsync("torrents/editWebSeed", content);
|
||||
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
}
|
||||
|
||||
public async Task RemoveTorrentWebSeeds(string hash, IEnumerable<string> urls)
|
||||
{
|
||||
var content = new FormUrlEncodedBuilder()
|
||||
.Add("hash", hash)
|
||||
.Add("urls", string.Join('|', urls))
|
||||
.ToFormUrlEncodedContent();
|
||||
|
||||
var response = await _httpClient.PostAsync("torrents/removeWebSeeds", content);
|
||||
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<FileData>> GetTorrentContents(string hash, params int[] indexes)
|
||||
{
|
||||
var query = new QueryBuilder();
|
||||
@@ -411,18 +539,6 @@ namespace Lantean.QBitTorrentClient
|
||||
|
||||
return await GetJsonList<string>(response.Content);
|
||||
}
|
||||
|
||||
public async Task PauseTorrents(bool? all = null, params string[] hashes)
|
||||
{
|
||||
var content = new FormUrlEncodedBuilder()
|
||||
.AddAllOrPipeSeparated("hashes", all, hashes)
|
||||
.ToFormUrlEncodedContent();
|
||||
|
||||
var response = await _httpClient.PostAsync("torrents/pause", content);
|
||||
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
}
|
||||
|
||||
public async Task StopTorrents(bool? all = null, params string[] hashes)
|
||||
{
|
||||
var content = new FormUrlEncodedBuilder()
|
||||
@@ -433,18 +549,6 @@ namespace Lantean.QBitTorrentClient
|
||||
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
}
|
||||
|
||||
public async Task ResumeTorrents(bool? all = null, params string[] hashes)
|
||||
{
|
||||
var content = new FormUrlEncodedBuilder()
|
||||
.AddAllOrPipeSeparated("hashes", all, hashes)
|
||||
.ToFormUrlEncodedContent();
|
||||
|
||||
var response = await _httpClient.PostAsync("torrents/resume", content);
|
||||
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
}
|
||||
|
||||
public async Task StartTorrents(bool? all = null, params string[] hashes)
|
||||
{
|
||||
var content = new FormUrlEncodedBuilder()
|
||||
@@ -479,10 +583,11 @@ namespace Lantean.QBitTorrentClient
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
}
|
||||
|
||||
public async Task ReannounceTorrents(bool? all = null, params string[] hashes)
|
||||
public async Task ReannounceTorrents(bool? all = null, IEnumerable<string>? trackers = null, params string[] hashes)
|
||||
{
|
||||
var content = new FormUrlEncodedBuilder()
|
||||
.AddAllOrPipeSeparated("hashes", all, hashes)
|
||||
.AddIfNotNullOrEmpty("urls", trackers is null ? null : string.Join('|', trackers))
|
||||
.ToFormUrlEncodedContent();
|
||||
|
||||
var response = await _httpClient.PostAsync("torrents/reannounce", content);
|
||||
@@ -490,13 +595,15 @@ namespace Lantean.QBitTorrentClient
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
}
|
||||
|
||||
public async Task AddTorrent(AddTorrentParams addTorrentParams)
|
||||
public async Task<AddTorrentResult> AddTorrent(AddTorrentParams addTorrentParams)
|
||||
{
|
||||
var content = new MultipartFormDataContent();
|
||||
if (addTorrentParams.Urls is not null)
|
||||
|
||||
if (addTorrentParams.Urls?.Any() == true)
|
||||
{
|
||||
content.AddString("urls", string.Join('\n', addTorrentParams.Urls));
|
||||
}
|
||||
|
||||
if (addTorrentParams.Torrents is not null)
|
||||
{
|
||||
foreach (var (name, stream) in addTorrentParams.Torrents)
|
||||
@@ -504,6 +611,7 @@ namespace Lantean.QBitTorrentClient
|
||||
content.Add(new StreamContent(stream), "torrents", name);
|
||||
}
|
||||
}
|
||||
|
||||
if (addTorrentParams.SkipChecking is not null)
|
||||
{
|
||||
content.AddString("skip_checking", addTorrentParams.SkipChecking.Value);
|
||||
@@ -520,12 +628,10 @@ namespace Lantean.QBitTorrentClient
|
||||
{
|
||||
content.AddString("addToTopOfQueue", addTorrentParams.AddToTopOfQueue.Value);
|
||||
}
|
||||
// v4
|
||||
if (addTorrentParams.Paused is not null)
|
||||
if (addTorrentParams.Forced is not null)
|
||||
{
|
||||
content.AddString("paused", addTorrentParams.Paused.Value);
|
||||
content.AddString("forced", addTorrentParams.Forced.Value);
|
||||
}
|
||||
// v5
|
||||
if (addTorrentParams.Stopped is not null)
|
||||
{
|
||||
content.AddString("stopped", addTorrentParams.Stopped.Value);
|
||||
@@ -590,21 +696,61 @@ namespace Lantean.QBitTorrentClient
|
||||
{
|
||||
content.AddString("contentLayout", addTorrentParams.ContentLayout.Value);
|
||||
}
|
||||
|
||||
if (addTorrentParams.Cookie is not null)
|
||||
if (addTorrentParams.Downloader is not null)
|
||||
{
|
||||
content.AddString("cookie", addTorrentParams.Cookie);
|
||||
content.AddString("downloader", addTorrentParams.Downloader);
|
||||
}
|
||||
if (addTorrentParams.FilePriorities is not null)
|
||||
{
|
||||
var priorities = string.Join(',', addTorrentParams.FilePriorities.Select(priority => ((int)priority).ToString(CultureInfo.InvariantCulture)));
|
||||
content.AddString("filePriorities", priorities);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(addTorrentParams.SslCertificate))
|
||||
{
|
||||
content.AddString("ssl_certificate", addTorrentParams.SslCertificate!);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(addTorrentParams.SslPrivateKey))
|
||||
{
|
||||
content.AddString("ssl_private_key", addTorrentParams.SslPrivateKey!);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(addTorrentParams.SslDhParams))
|
||||
{
|
||||
content.AddString("ssl_dh_params", addTorrentParams.SslDhParams!);
|
||||
}
|
||||
|
||||
var response = await _httpClient.PostAsync("torrents/add", content);
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.Conflict)
|
||||
{
|
||||
var conflictMessage = await response.Content.ReadAsStringAsync();
|
||||
if (string.IsNullOrWhiteSpace(conflictMessage))
|
||||
{
|
||||
conflictMessage = "All torrents failed to add.";
|
||||
}
|
||||
|
||||
throw new HttpRequestException(conflictMessage, null, response.StatusCode);
|
||||
}
|
||||
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync();
|
||||
if (string.IsNullOrWhiteSpace(payload))
|
||||
{
|
||||
return new AddTorrentResult(0, 0, 0, Array.Empty<string>());
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize<AddTorrentResult>(payload, _options) ?? new AddTorrentResult(0, 0, 0, Array.Empty<string>());
|
||||
}
|
||||
|
||||
public async Task AddTrackersToTorrent(string hash, IEnumerable<string> urls)
|
||||
public async Task AddTrackersToTorrent(IEnumerable<string> urls, bool? all = null, params string[] hashes)
|
||||
{
|
||||
if (all is not true && (hashes is null || hashes.Length == 0))
|
||||
{
|
||||
throw new ArgumentException("Specify at least one torrent hash or set all=true.", nameof(hashes));
|
||||
}
|
||||
|
||||
var content = new FormUrlEncodedBuilder()
|
||||
.Add("hash", hash)
|
||||
.AddAllOrPipeSeparated("hash", all, hashes ?? Array.Empty<string>())
|
||||
.Add("urls", string.Join('\n', urls))
|
||||
.ToFormUrlEncodedContent();
|
||||
|
||||
@@ -613,23 +759,42 @@ namespace Lantean.QBitTorrentClient
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
}
|
||||
|
||||
public async Task EditTracker(string hash, string originalUrl, string newUrl)
|
||||
public async Task EditTracker(string hash, string url, string? newUrl = null, int? tier = null)
|
||||
{
|
||||
if ((newUrl is null) && (tier is null))
|
||||
{
|
||||
throw new ArgumentException("Must specify at least one of newUrl or tier.");
|
||||
}
|
||||
|
||||
var content = new FormUrlEncodedBuilder()
|
||||
.Add("hash", hash)
|
||||
.Add("originalUrl", originalUrl)
|
||||
.Add("newUrl", newUrl)
|
||||
.ToFormUrlEncodedContent();
|
||||
.Add("url", url);
|
||||
|
||||
var response = await _httpClient.PostAsync("torrents/editTracker", content);
|
||||
if (!string.IsNullOrEmpty(newUrl))
|
||||
{
|
||||
content.Add("newUrl", newUrl!);
|
||||
}
|
||||
if (tier is not null)
|
||||
{
|
||||
content.Add("tier", tier.Value);
|
||||
}
|
||||
|
||||
var form = content.ToFormUrlEncodedContent();
|
||||
|
||||
var response = await _httpClient.PostAsync("torrents/editTracker", form);
|
||||
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
}
|
||||
|
||||
public async Task RemoveTrackers(string hash, IEnumerable<string> urls)
|
||||
public async Task RemoveTrackers(IEnumerable<string> urls, bool? all = null, params string[] hashes)
|
||||
{
|
||||
if (all is not true && (hashes is null || hashes.Length == 0))
|
||||
{
|
||||
throw new ArgumentException("Specify at least one torrent hash or set all=true.", nameof(hashes));
|
||||
}
|
||||
|
||||
var content = new FormUrlEncodedBuilder()
|
||||
.Add("hash", hash)
|
||||
.AddAllOrPipeSeparated("hash", all, hashes ?? Array.Empty<string>())
|
||||
.AddPipeSeparated("urls", urls)
|
||||
.ToFormUrlEncodedContent();
|
||||
|
||||
@@ -732,13 +897,14 @@ namespace Lantean.QBitTorrentClient
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
}
|
||||
|
||||
public async Task SetTorrentShareLimit(float ratioLimit, float seedingTimeLimit, float inactiveSeedingTimeLimit, bool? all = null, params string[] hashes)
|
||||
public async Task SetTorrentShareLimit(float ratioLimit, float seedingTimeLimit, float inactiveSeedingTimeLimit, ShareLimitAction? shareLimitAction = null, bool? all = null, params string[] hashes)
|
||||
{
|
||||
var content = new FormUrlEncodedBuilder()
|
||||
.AddAllOrPipeSeparated("hashes", all, hashes)
|
||||
.Add("ratioLimit", ratioLimit)
|
||||
.Add("seedingTimeLimit", seedingTimeLimit)
|
||||
.Add("inactiveSeedingTimeLimit", inactiveSeedingTimeLimit)
|
||||
.AddIfNotNullOrEmpty("shareLimitAction", shareLimitAction)
|
||||
.ToFormUrlEncodedContent();
|
||||
|
||||
var response = await _httpClient.PostAsync("torrents/setShareLimits", content);
|
||||
@@ -795,6 +961,18 @@ namespace Lantean.QBitTorrentClient
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
}
|
||||
|
||||
public async Task SetTorrentComment(IEnumerable<string> hashes, string comment)
|
||||
{
|
||||
var content = new FormUrlEncodedBuilder()
|
||||
.Add("hashes", string.Join('|', hashes))
|
||||
.Add("comment", comment)
|
||||
.ToFormUrlEncodedContent();
|
||||
|
||||
var response = await _httpClient.PostAsync("torrents/setComment", content);
|
||||
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
}
|
||||
|
||||
public async Task SetTorrentCategory(string category, bool? all = null, params string[] hashes)
|
||||
{
|
||||
var content = new FormUrlEncodedBuilder()
|
||||
@@ -995,8 +1173,180 @@ namespace Lantean.QBitTorrentClient
|
||||
return Task.FromResult($"{_httpClient.BaseAddress}torrents/export?hash={hash}");
|
||||
}
|
||||
|
||||
public async Task<TorrentMetadata?> FetchMetadata(string source, string? downloader = null)
|
||||
{
|
||||
var builder = new FormUrlEncodedBuilder()
|
||||
.Add("source", source);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(downloader))
|
||||
{
|
||||
builder.Add("downloader", downloader!);
|
||||
}
|
||||
|
||||
var response = await _httpClient.PostAsync("torrents/fetchMetadata", builder.ToFormUrlEncodedContent());
|
||||
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync();
|
||||
if (string.IsNullOrWhiteSpace(payload))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize<TorrentMetadata>(payload, _options);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<TorrentMetadata>> ParseMetadata(IEnumerable<(string FileName, Stream Content)> torrents)
|
||||
{
|
||||
var content = new MultipartFormDataContent();
|
||||
foreach (var (fileName, stream) in torrents)
|
||||
{
|
||||
content.Add(new StreamContent(stream), "torrents", fileName);
|
||||
}
|
||||
|
||||
var response = await _httpClient.PostAsync("torrents/parseMetadata", content);
|
||||
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
|
||||
return await GetJsonList<TorrentMetadata>(response.Content);
|
||||
}
|
||||
|
||||
public async Task<byte[]> SaveMetadata(string source)
|
||||
{
|
||||
var content = new FormUrlEncodedBuilder()
|
||||
.Add("source", source)
|
||||
.ToFormUrlEncodedContent();
|
||||
|
||||
var response = await _httpClient.PostAsync("torrents/saveMetadata", content);
|
||||
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
|
||||
return await response.Content.ReadAsByteArrayAsync();
|
||||
}
|
||||
|
||||
#endregion Torrent management
|
||||
|
||||
#region Torrent creator
|
||||
|
||||
public async Task<string> AddTorrentCreationTask(TorrentCreationTaskRequest request)
|
||||
{
|
||||
if (request is null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
if (string.IsNullOrWhiteSpace(request.SourcePath))
|
||||
throw new ArgumentException("SourcePath is required.", nameof(request));
|
||||
|
||||
var builder = new FormUrlEncodedBuilder()
|
||||
.Add("sourcePath", request.SourcePath);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.TorrentFilePath))
|
||||
{
|
||||
builder.Add("torrentFilePath", request.TorrentFilePath!);
|
||||
}
|
||||
if (request.PieceSize.HasValue)
|
||||
{
|
||||
builder.Add("pieceSize", request.PieceSize.Value);
|
||||
}
|
||||
if (request.Private.HasValue)
|
||||
{
|
||||
builder.Add("private", request.Private.Value);
|
||||
}
|
||||
if (request.StartSeeding.HasValue)
|
||||
{
|
||||
builder.Add("startSeeding", request.StartSeeding.Value);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(request.Comment))
|
||||
{
|
||||
builder.Add("comment", request.Comment!);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(request.Source))
|
||||
{
|
||||
builder.Add("source", request.Source!);
|
||||
}
|
||||
if (request.Trackers is not null)
|
||||
{
|
||||
builder.Add("trackers", string.Join('|', request.Trackers));
|
||||
}
|
||||
if (request.UrlSeeds is not null)
|
||||
{
|
||||
builder.Add("urlSeeds", string.Join('|', request.UrlSeeds));
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(request.Format))
|
||||
{
|
||||
builder.Add("format", request.Format!);
|
||||
}
|
||||
if (request.OptimizeAlignment.HasValue)
|
||||
{
|
||||
builder.Add("optimizeAlignment", request.OptimizeAlignment.Value);
|
||||
}
|
||||
if (request.PaddedFileSizeLimit.HasValue)
|
||||
{
|
||||
builder.Add("paddedFileSizeLimit", request.PaddedFileSizeLimit.Value);
|
||||
}
|
||||
|
||||
var response = await _httpClient.PostAsync("torrentcreator/addTask", builder.ToFormUrlEncodedContent());
|
||||
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync();
|
||||
if (string.IsNullOrWhiteSpace(payload))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var json = JsonSerializer.Deserialize<JsonElement>(payload, _options);
|
||||
if (json.ValueKind == JsonValueKind.Object && json.TryGetProperty("taskID", out var idElement))
|
||||
{
|
||||
return idElement.GetString() ?? string.Empty;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<TorrentCreationTaskStatus>> GetTorrentCreationTasks(string? taskId = null)
|
||||
{
|
||||
HttpResponseMessage response;
|
||||
if (string.IsNullOrWhiteSpace(taskId))
|
||||
{
|
||||
response = await _httpClient.GetAsync("torrentcreator/status");
|
||||
}
|
||||
else
|
||||
{
|
||||
var query = new QueryBuilder()
|
||||
.Add("taskID", taskId);
|
||||
|
||||
response = await _httpClient.GetAsync("torrentcreator/status", query);
|
||||
}
|
||||
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
|
||||
return await GetJsonList<TorrentCreationTaskStatus>(response.Content);
|
||||
}
|
||||
|
||||
public async Task<byte[]> GetTorrentCreationTaskFile(string taskId)
|
||||
{
|
||||
var query = new QueryBuilder()
|
||||
.Add("taskID", taskId);
|
||||
|
||||
var response = await _httpClient.GetAsync("torrentcreator/torrentFile", query);
|
||||
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
|
||||
return await response.Content.ReadAsByteArrayAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteTorrentCreationTask(string taskId)
|
||||
{
|
||||
var content = new FormUrlEncodedBuilder()
|
||||
.Add("taskID", taskId)
|
||||
.ToFormUrlEncodedContent();
|
||||
|
||||
var response = await _httpClient.PostAsync("torrentcreator/deleteTask", content);
|
||||
|
||||
await ThrowIfNotSuccessfulStatusCode(response);
|
||||
}
|
||||
|
||||
#endregion Torrent creator
|
||||
|
||||
#region RSS
|
||||
|
||||
public async Task AddRssFolder(string path)
|
||||
@@ -1334,4 +1684,4 @@ namespace Lantean.QBitTorrentClient
|
||||
throw new HttpRequestException(errorMessage, null, response.StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,24 +1,10 @@
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using System.Linq;
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
|
||||
namespace Lantean.QBitTorrentClient
|
||||
{
|
||||
public static class ApiClientExtensions
|
||||
{
|
||||
public static Task PauseTorrent(this IApiClient apiClient, string hash)
|
||||
{
|
||||
return apiClient.PauseTorrents(null, hash);
|
||||
}
|
||||
|
||||
public static Task PauseTorrents(this IApiClient apiClient, IEnumerable<string> hashes)
|
||||
{
|
||||
return apiClient.PauseTorrents(null, hashes.ToArray());
|
||||
}
|
||||
|
||||
public static Task PauseAllTorrents(this IApiClient apiClient)
|
||||
{
|
||||
return apiClient.PauseTorrents(true);
|
||||
}
|
||||
|
||||
public static Task StopTorrent(this IApiClient apiClient, string hash)
|
||||
{
|
||||
return apiClient.StopTorrents(null, hash);
|
||||
@@ -34,21 +20,6 @@ namespace Lantean.QBitTorrentClient
|
||||
return apiClient.StopTorrents(true);
|
||||
}
|
||||
|
||||
public static Task ResumeTorrent(this IApiClient apiClient, string hash)
|
||||
{
|
||||
return apiClient.ResumeTorrents(null, hash);
|
||||
}
|
||||
|
||||
public static Task ResumeTorrents(this IApiClient apiClient, IEnumerable<string> hashes)
|
||||
{
|
||||
return apiClient.ResumeTorrents(null, hashes.ToArray());
|
||||
}
|
||||
|
||||
public static Task ResumeAllTorrents(this IApiClient apiClient)
|
||||
{
|
||||
return apiClient.ResumeTorrents(true);
|
||||
}
|
||||
|
||||
public static Task StartTorrent(this IApiClient apiClient, string hash)
|
||||
{
|
||||
return apiClient.StartTorrents(null, hash);
|
||||
@@ -158,7 +129,7 @@ namespace Lantean.QBitTorrentClient
|
||||
|
||||
public static Task ReannounceTorrent(this IApiClient apiClient, string hash)
|
||||
{
|
||||
return apiClient.ReannounceTorrents(null, hash);
|
||||
return apiClient.ReannounceTorrents(null, null, hash);
|
||||
}
|
||||
|
||||
public static async Task<IEnumerable<string>> RemoveUnusedCategories(this IApiClient apiClient)
|
||||
@@ -189,4 +160,4 @@ namespace Lantean.QBitTorrentClient
|
||||
return unusedTags;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,44 @@
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Lantean.QBitTorrentClient.Converters
|
||||
{
|
||||
public sealed class DownloadPathOptionJsonConverter : JsonConverter<DownloadPathOption>
|
||||
{
|
||||
public override DownloadPathOption? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return reader.TokenType switch
|
||||
{
|
||||
JsonTokenType.Null => null,
|
||||
JsonTokenType.False => new DownloadPathOption(false, null),
|
||||
JsonTokenType.True => new DownloadPathOption(true, null),
|
||||
JsonTokenType.String => new DownloadPathOption(true, reader.GetString()),
|
||||
_ => throw new JsonException($"Unexpected token {reader.TokenType} when parsing download_path.")
|
||||
};
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, DownloadPathOption? value, JsonSerializerOptions options)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
writer.WriteNullValue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!value.Enabled)
|
||||
{
|
||||
writer.WriteBooleanValue(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(value.Path))
|
||||
{
|
||||
writer.WriteBooleanValue(true);
|
||||
return;
|
||||
}
|
||||
|
||||
writer.WriteStringValue(value.Path);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,8 @@
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Lantean.QBitTorrentClient.Models;
|
||||
|
||||
namespace Lantean.QBitTorrentClient
|
||||
{
|
||||
@@ -28,6 +32,12 @@ namespace Lantean.QBitTorrentClient
|
||||
|
||||
Task SetApplicationPreferences(UpdatePreferences preferences);
|
||||
|
||||
Task<IReadOnlyList<ApplicationCookie>> GetApplicationCookies();
|
||||
|
||||
Task SetApplicationCookies(IEnumerable<ApplicationCookie> cookies);
|
||||
|
||||
Task<string> RotateApiKey();
|
||||
|
||||
Task<string> GetDefaultSavePath();
|
||||
|
||||
Task<IReadOnlyList<NetworkInterface>> GetNetworkInterfaces();
|
||||
@@ -36,6 +46,14 @@ namespace Lantean.QBitTorrentClient
|
||||
|
||||
#endregion Application
|
||||
|
||||
#region Client data
|
||||
|
||||
Task<IReadOnlyDictionary<string, JsonElement>> LoadClientData(IEnumerable<string>? keys = null);
|
||||
|
||||
Task StoreClientData(IReadOnlyDictionary<string, JsonElement> data);
|
||||
|
||||
#endregion Client data
|
||||
|
||||
#region Log
|
||||
|
||||
Task<IReadOnlyList<Log>> GetLog(bool? normal = null, bool? info = null, bool? warning = null, bool? critical = null, int? lastKnownId = null);
|
||||
@@ -74,7 +92,7 @@ namespace Lantean.QBitTorrentClient
|
||||
|
||||
#region Torrent management
|
||||
|
||||
Task<IReadOnlyList<Torrent>> GetTorrentList(string? filter = null, string? category = null, string? tag = null, string? sort = null, bool? reverse = null, int? limit = null, int? offset = null, bool? isPrivate = null, params string[] hashes);
|
||||
Task<IReadOnlyList<Torrent>> GetTorrentList(string? filter = null, string? category = null, string? tag = null, string? sort = null, bool? reverse = null, int? limit = null, int? offset = null, bool? isPrivate = null, bool? includeFiles = null, params string[] hashes);
|
||||
|
||||
Task<TorrentProperties> GetTorrentProperties(string hash);
|
||||
|
||||
@@ -82,16 +100,18 @@ namespace Lantean.QBitTorrentClient
|
||||
|
||||
Task<IReadOnlyList<WebSeed>> GetTorrentWebSeeds(string hash);
|
||||
|
||||
Task AddTorrentWebSeeds(string hash, IEnumerable<string> urls);
|
||||
|
||||
Task EditTorrentWebSeed(string hash, string originalUrl, string newUrl);
|
||||
|
||||
Task RemoveTorrentWebSeeds(string hash, IEnumerable<string> urls);
|
||||
|
||||
Task<IReadOnlyList<FileData>> GetTorrentContents(string hash, params int[] indexes);
|
||||
|
||||
Task<IReadOnlyList<PieceState>> GetTorrentPieceStates(string hash);
|
||||
|
||||
Task<IReadOnlyList<string>> GetTorrentPieceHashes(string hash);
|
||||
|
||||
Task PauseTorrents(bool? all = null, params string[] hashes);
|
||||
|
||||
Task ResumeTorrents(bool? all = null, params string[] hashes);
|
||||
|
||||
Task StartTorrents(bool? all = null, params string[] hashes);
|
||||
|
||||
Task StopTorrents(bool? all = null, params string[] hashes);
|
||||
@@ -100,15 +120,15 @@ namespace Lantean.QBitTorrentClient
|
||||
|
||||
Task RecheckTorrents(bool? all = null, params string[] hashes);
|
||||
|
||||
Task ReannounceTorrents(bool? all = null, params string[] hashes);
|
||||
Task ReannounceTorrents(bool? all = null, IEnumerable<string>? trackers = null, params string[] hashes);
|
||||
|
||||
Task AddTorrent(AddTorrentParams addTorrentParams);
|
||||
Task<AddTorrentResult> AddTorrent(AddTorrentParams addTorrentParams);
|
||||
|
||||
Task AddTrackersToTorrent(string hash, IEnumerable<string> urls);
|
||||
Task AddTrackersToTorrent(IEnumerable<string> urls, bool? all = null, params string[] hashes);
|
||||
|
||||
Task EditTracker(string hash, string originalUrl, string newUrl);
|
||||
Task EditTracker(string hash, string url, string? newUrl = null, int? tier = null);
|
||||
|
||||
Task RemoveTrackers(string hash, IEnumerable<string> urls);
|
||||
Task RemoveTrackers(IEnumerable<string> urls, bool? all = null, params string[] hashes);
|
||||
|
||||
Task AddPeers(IEnumerable<string> hashes, IEnumerable<PeerId> peers);
|
||||
|
||||
@@ -126,7 +146,7 @@ namespace Lantean.QBitTorrentClient
|
||||
|
||||
Task SetTorrentDownloadLimit(long limit, bool? all = null, params string[] hashes);
|
||||
|
||||
Task SetTorrentShareLimit(float ratioLimit, float seedingTimeLimit, float inactiveSeedingTimeLimit, bool? all = null, params string[] hashes);
|
||||
Task SetTorrentShareLimit(float ratioLimit, float seedingTimeLimit, float inactiveSeedingTimeLimit, ShareLimitAction? shareLimitAction = null, bool? all = null, params string[] hashes);
|
||||
|
||||
Task<IReadOnlyDictionary<string, long>> GetTorrentUploadLimit(bool? all = null, params string[] hashes);
|
||||
|
||||
@@ -136,6 +156,8 @@ namespace Lantean.QBitTorrentClient
|
||||
|
||||
Task SetTorrentName(string name, string hash);
|
||||
|
||||
Task SetTorrentComment(IEnumerable<string> hashes, string comment);
|
||||
|
||||
Task SetTorrentCategory(string category, bool? all = null, params string[] hashes);
|
||||
|
||||
Task<IReadOnlyDictionary<string, Category>> GetAllCategories();
|
||||
@@ -172,8 +194,26 @@ namespace Lantean.QBitTorrentClient
|
||||
|
||||
Task<string> GetExportUrl(string hash);
|
||||
|
||||
Task<TorrentMetadata?> FetchMetadata(string source, string? downloader = null);
|
||||
|
||||
Task<IReadOnlyList<TorrentMetadata>> ParseMetadata(IEnumerable<(string FileName, Stream Content)> torrents);
|
||||
|
||||
Task<byte[]> SaveMetadata(string source);
|
||||
|
||||
#endregion Torrent management
|
||||
|
||||
#region Torrent creator
|
||||
|
||||
Task<string> AddTorrentCreationTask(TorrentCreationTaskRequest request);
|
||||
|
||||
Task<IReadOnlyList<TorrentCreationTaskStatus>> GetTorrentCreationTasks(string? taskId = null);
|
||||
|
||||
Task<byte[]> GetTorrentCreationTaskFile(string taskId);
|
||||
|
||||
Task DeleteTorrentCreationTask(string taskId);
|
||||
|
||||
#endregion Torrent creator
|
||||
|
||||
#region RSS
|
||||
|
||||
Task AddRssFolder(string path);
|
||||
@@ -230,4 +270,4 @@ namespace Lantean.QBitTorrentClient
|
||||
|
||||
#endregion Search
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -12,9 +12,8 @@
|
||||
|
||||
public bool? AddToTopOfQueue { get; set; }
|
||||
|
||||
// v4
|
||||
public bool? Paused { get; set; }
|
||||
// v5
|
||||
public bool? Forced { get; set; }
|
||||
|
||||
public bool? Stopped { get; set; }
|
||||
|
||||
public string? SavePath { get; set; }
|
||||
@@ -47,8 +46,16 @@
|
||||
|
||||
public TorrentContentLayout? ContentLayout { get; set; }
|
||||
|
||||
public string? Cookie { get; set; }
|
||||
public IEnumerable<Priority>? FilePriorities { get; set; }
|
||||
|
||||
public string? Downloader { get; set; }
|
||||
|
||||
public string? SslCertificate { get; set; }
|
||||
|
||||
public string? SslPrivateKey { get; set; }
|
||||
|
||||
public string? SslDhParams { get; set; }
|
||||
|
||||
public Dictionary<string, Stream>? Torrents { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
30
Lantean.QBitTorrentClient/Models/AddTorrentResult.cs
Normal file
30
Lantean.QBitTorrentClient/Models/AddTorrentResult.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Lantean.QBitTorrentClient.Models
|
||||
{
|
||||
public record AddTorrentResult
|
||||
{
|
||||
[JsonConstructor]
|
||||
public AddTorrentResult(int successCount, int failureCount, int pendingCount, IReadOnlyList<string>? addedTorrentIds)
|
||||
{
|
||||
SuccessCount = successCount;
|
||||
FailureCount = failureCount;
|
||||
PendingCount = pendingCount;
|
||||
AddedTorrentIds = addedTorrentIds ?? Array.Empty<string>();
|
||||
}
|
||||
|
||||
[JsonPropertyName("success_count")]
|
||||
public int SuccessCount { get; }
|
||||
|
||||
[JsonPropertyName("failure_count")]
|
||||
public int FailureCount { get; }
|
||||
|
||||
[JsonPropertyName("pending_count")]
|
||||
public int PendingCount { get; }
|
||||
|
||||
[JsonPropertyName("added_torrent_ids")]
|
||||
public IReadOnlyList<string> AddedTorrentIds { get; }
|
||||
}
|
||||
}
|
33
Lantean.QBitTorrentClient/Models/ApplicationCookie.cs
Normal file
33
Lantean.QBitTorrentClient/Models/ApplicationCookie.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Lantean.QBitTorrentClient.Models
|
||||
{
|
||||
public record ApplicationCookie
|
||||
{
|
||||
[JsonConstructor]
|
||||
public ApplicationCookie(string name, string? domain, string? path, string? value, long? expirationDate)
|
||||
{
|
||||
Name = name;
|
||||
Domain = domain;
|
||||
Path = path;
|
||||
Value = value;
|
||||
ExpirationDate = expirationDate;
|
||||
}
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; }
|
||||
|
||||
[JsonPropertyName("domain")]
|
||||
public string? Domain { get; }
|
||||
|
||||
[JsonPropertyName("path")]
|
||||
public string? Path { get; }
|
||||
|
||||
[JsonPropertyName("value")]
|
||||
public string? Value { get; }
|
||||
|
||||
[JsonPropertyName("expirationDate")]
|
||||
public long? ExpirationDate { get; }
|
||||
}
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Lantean.QBitTorrentClient.Converters;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Lantean.QBitTorrentClient.Models
|
||||
{
|
||||
@@ -7,10 +8,12 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
[JsonConstructor]
|
||||
public Category(
|
||||
string name,
|
||||
string? savePath)
|
||||
string? savePath,
|
||||
DownloadPathOption? downloadPath)
|
||||
{
|
||||
Name = name;
|
||||
SavePath = savePath;
|
||||
DownloadPath = downloadPath;
|
||||
}
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
@@ -18,5 +21,9 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
|
||||
[JsonPropertyName("savePath")]
|
||||
public string? SavePath { get; }
|
||||
|
||||
[JsonPropertyName("download_path")]
|
||||
[JsonConverter(typeof(DownloadPathOptionJsonConverter))]
|
||||
public DownloadPathOption? DownloadPath { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
15
Lantean.QBitTorrentClient/Models/DownloadPathOption.cs
Normal file
15
Lantean.QBitTorrentClient/Models/DownloadPathOption.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Lantean.QBitTorrentClient.Models
|
||||
{
|
||||
public record DownloadPathOption
|
||||
{
|
||||
public DownloadPathOption(bool enabled, string? path)
|
||||
{
|
||||
Enabled = enabled;
|
||||
Path = path;
|
||||
}
|
||||
|
||||
public bool Enabled { get; }
|
||||
|
||||
public string? Path { get; }
|
||||
}
|
||||
}
|
@@ -15,6 +15,7 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
IReadOnlyList<string>? tags,
|
||||
IReadOnlyList<string>? tagsRemoved,
|
||||
IReadOnlyDictionary<string, IReadOnlyList<string>> trackers,
|
||||
IReadOnlyList<string>? trackersRemoved,
|
||||
ServerState? serverState)
|
||||
{
|
||||
ResponseId = responseId;
|
||||
@@ -26,6 +27,7 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
Tags = tags;
|
||||
TagsRemoved = tagsRemoved;
|
||||
Trackers = trackers;
|
||||
TrackersRemoved = trackersRemoved;
|
||||
ServerState = serverState;
|
||||
}
|
||||
|
||||
@@ -62,4 +64,4 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
[JsonPropertyName("server_state")]
|
||||
public ServerState? ServerState { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -16,6 +16,7 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
string? flags,
|
||||
string? flagsDescription,
|
||||
string? iPAddress,
|
||||
string? i2pDestination,
|
||||
string? clientId,
|
||||
int? port,
|
||||
float? progress,
|
||||
@@ -33,6 +34,7 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
Flags = flags;
|
||||
FlagsDescription = flagsDescription;
|
||||
IPAddress = iPAddress;
|
||||
I2pDestination = i2pDestination;
|
||||
ClientId = clientId;
|
||||
Port = port;
|
||||
Progress = progress;
|
||||
@@ -71,6 +73,9 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
[JsonPropertyName("ip")]
|
||||
public string? IPAddress { get; }
|
||||
|
||||
[JsonPropertyName("i2p_dest")]
|
||||
public string? I2pDestination { get; }
|
||||
|
||||
[JsonPropertyName("peer_id_client")]
|
||||
public string? ClientId { get; }
|
||||
|
||||
@@ -89,4 +94,4 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
[JsonPropertyName("uploaded")]
|
||||
public long? Uploaded { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
[JsonConstructor]
|
||||
public Preferences(
|
||||
bool addToTopOfQueue,
|
||||
bool addStoppedEnabled,
|
||||
string addTrackers,
|
||||
bool addTrackersEnabled,
|
||||
int altDlLimit,
|
||||
@@ -14,6 +15,7 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
bool alternativeWebuiEnabled,
|
||||
string alternativeWebuiPath,
|
||||
string announceIp,
|
||||
int announcePort,
|
||||
bool announceToAllTiers,
|
||||
bool announceToAllTrackers,
|
||||
bool anonymousMode,
|
||||
@@ -85,6 +87,7 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
int i2pPort,
|
||||
bool idnSupportEnabled,
|
||||
bool incompleteFilesExt,
|
||||
bool useUnwantedFolder,
|
||||
bool ipFilterEnabled,
|
||||
string ipFilterPath,
|
||||
bool ipFilterTrackers,
|
||||
@@ -92,6 +95,8 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
bool limitTcpOverhead,
|
||||
bool limitUtpRate,
|
||||
int listenPort,
|
||||
bool sslEnabled,
|
||||
int sslListenPort,
|
||||
string locale,
|
||||
bool lsd,
|
||||
bool mailNotificationAuthEnabled,
|
||||
@@ -160,6 +165,7 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
string savePath,
|
||||
bool savePathChangedTmmEnabled,
|
||||
int saveResumeDataInterval,
|
||||
int saveStatisticsInterval,
|
||||
Dictionary<string, SaveLocation> scanDirs,
|
||||
int scheduleFromHour,
|
||||
int scheduleFromMin,
|
||||
@@ -177,12 +183,12 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
int socketReceiveBufferSize,
|
||||
int socketSendBufferSize,
|
||||
bool ssrfMitigation,
|
||||
bool startPausedEnabled,
|
||||
int stopTrackerTimeout,
|
||||
string tempPath,
|
||||
bool tempPathEnabled,
|
||||
bool torrentChangedTmmEnabled,
|
||||
string torrentContentLayout,
|
||||
string torrentContentRemoveOption,
|
||||
int torrentFileSizeLimit,
|
||||
string torrentStopCondition,
|
||||
int upLimit,
|
||||
@@ -192,10 +198,12 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
int upnpLeaseDuration,
|
||||
bool useCategoryPathsInManualMode,
|
||||
bool useHttps,
|
||||
bool ignoreSslErrors,
|
||||
bool useSubcategories,
|
||||
int utpTcpMixedMode,
|
||||
bool validateHttpsTrackerCertificate,
|
||||
string webUiAddress,
|
||||
string webUiApiKey,
|
||||
int webUiBanDuration,
|
||||
bool webUiClickjackingProtectionEnabled,
|
||||
bool webUiCsrfProtectionEnabled,
|
||||
@@ -213,10 +221,14 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
bool webUiUpnp,
|
||||
bool webUiUseCustomHttpHeadersEnabled,
|
||||
string webUiUsername,
|
||||
string webUiPassword
|
||||
string webUiPassword,
|
||||
bool confirmTorrentDeletion,
|
||||
bool confirmTorrentRecheck,
|
||||
bool statusBarExternalIp
|
||||
)
|
||||
{
|
||||
AddToTopOfQueue = addToTopOfQueue;
|
||||
AddStoppedEnabled = addStoppedEnabled;
|
||||
AddTrackers = addTrackers;
|
||||
AddTrackersEnabled = addTrackersEnabled;
|
||||
AltDlLimit = altDlLimit;
|
||||
@@ -224,6 +236,7 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
AlternativeWebuiEnabled = alternativeWebuiEnabled;
|
||||
AlternativeWebuiPath = alternativeWebuiPath;
|
||||
AnnounceIp = announceIp;
|
||||
AnnouncePort = announcePort;
|
||||
AnnounceToAllTiers = announceToAllTiers;
|
||||
AnnounceToAllTrackers = announceToAllTrackers;
|
||||
AnonymousMode = anonymousMode;
|
||||
@@ -295,6 +308,7 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
I2pPort = i2pPort;
|
||||
IdnSupportEnabled = idnSupportEnabled;
|
||||
IncompleteFilesExt = incompleteFilesExt;
|
||||
UseUnwantedFolder = useUnwantedFolder;
|
||||
IpFilterEnabled = ipFilterEnabled;
|
||||
IpFilterPath = ipFilterPath;
|
||||
IpFilterTrackers = ipFilterTrackers;
|
||||
@@ -302,6 +316,8 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
LimitTcpOverhead = limitTcpOverhead;
|
||||
LimitUtpRate = limitUtpRate;
|
||||
ListenPort = listenPort;
|
||||
SslEnabled = sslEnabled;
|
||||
SslListenPort = sslListenPort;
|
||||
Locale = locale;
|
||||
Lsd = lsd;
|
||||
MailNotificationAuthEnabled = mailNotificationAuthEnabled;
|
||||
@@ -370,6 +386,7 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
SavePath = savePath;
|
||||
SavePathChangedTmmEnabled = savePathChangedTmmEnabled;
|
||||
SaveResumeDataInterval = saveResumeDataInterval;
|
||||
SaveStatisticsInterval = saveStatisticsInterval;
|
||||
ScanDirs = scanDirs;
|
||||
ScheduleFromHour = scheduleFromHour;
|
||||
ScheduleFromMin = scheduleFromMin;
|
||||
@@ -387,12 +404,12 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
SocketReceiveBufferSize = socketReceiveBufferSize;
|
||||
SocketSendBufferSize = socketSendBufferSize;
|
||||
SsrfMitigation = ssrfMitigation;
|
||||
StartPausedEnabled = startPausedEnabled;
|
||||
StopTrackerTimeout = stopTrackerTimeout;
|
||||
TempPath = tempPath;
|
||||
TempPathEnabled = tempPathEnabled;
|
||||
TorrentChangedTmmEnabled = torrentChangedTmmEnabled;
|
||||
TorrentContentLayout = torrentContentLayout;
|
||||
TorrentContentRemoveOption = torrentContentRemoveOption;
|
||||
TorrentFileSizeLimit = torrentFileSizeLimit;
|
||||
TorrentStopCondition = torrentStopCondition;
|
||||
UpLimit = upLimit;
|
||||
@@ -402,10 +419,12 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
UpnpLeaseDuration = upnpLeaseDuration;
|
||||
UseCategoryPathsInManualMode = useCategoryPathsInManualMode;
|
||||
UseHttps = useHttps;
|
||||
IgnoreSslErrors = ignoreSslErrors;
|
||||
UseSubcategories = useSubcategories;
|
||||
UtpTcpMixedMode = utpTcpMixedMode;
|
||||
ValidateHttpsTrackerCertificate = validateHttpsTrackerCertificate;
|
||||
WebUiAddress = webUiAddress;
|
||||
WebUiApiKey = webUiApiKey;
|
||||
WebUiBanDuration = webUiBanDuration;
|
||||
WebUiClickjackingProtectionEnabled = webUiClickjackingProtectionEnabled;
|
||||
WebUiCsrfProtectionEnabled = webUiCsrfProtectionEnabled;
|
||||
@@ -424,11 +443,17 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
WebUiUseCustomHttpHeadersEnabled = webUiUseCustomHttpHeadersEnabled;
|
||||
WebUiUsername = webUiUsername;
|
||||
WebUiPassword = webUiPassword;
|
||||
ConfirmTorrentDeletion = confirmTorrentDeletion;
|
||||
ConfirmTorrentRecheck = confirmTorrentRecheck;
|
||||
StatusBarExternalIp = statusBarExternalIp;
|
||||
}
|
||||
|
||||
[JsonPropertyName("add_to_top_of_queue")]
|
||||
public bool AddToTopOfQueue { get; }
|
||||
|
||||
[JsonPropertyName("add_stopped_enabled")]
|
||||
public bool AddStoppedEnabled { get; }
|
||||
|
||||
[JsonPropertyName("add_trackers")]
|
||||
public string AddTrackers { get; }
|
||||
|
||||
@@ -450,6 +475,9 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
[JsonPropertyName("announce_ip")]
|
||||
public string AnnounceIp { get; }
|
||||
|
||||
[JsonPropertyName("announce_port")]
|
||||
public int AnnouncePort { get; }
|
||||
|
||||
[JsonPropertyName("announce_to_all_tiers")]
|
||||
public bool AnnounceToAllTiers { get; }
|
||||
|
||||
@@ -663,6 +691,9 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
[JsonPropertyName("incomplete_files_ext")]
|
||||
public bool IncompleteFilesExt { get; }
|
||||
|
||||
[JsonPropertyName("use_unwanted_folder")]
|
||||
public bool UseUnwantedFolder { get; }
|
||||
|
||||
[JsonPropertyName("ip_filter_enabled")]
|
||||
public bool IpFilterEnabled { get; }
|
||||
|
||||
@@ -684,6 +715,12 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
[JsonPropertyName("listen_port")]
|
||||
public int ListenPort { get; }
|
||||
|
||||
[JsonPropertyName("ssl_enabled")]
|
||||
public bool SslEnabled { get; }
|
||||
|
||||
[JsonPropertyName("ssl_listen_port")]
|
||||
public int SslListenPort { get; }
|
||||
|
||||
[JsonPropertyName("locale")]
|
||||
public string Locale { get; }
|
||||
|
||||
@@ -888,6 +925,9 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
[JsonPropertyName("save_resume_data_interval")]
|
||||
public int SaveResumeDataInterval { get; }
|
||||
|
||||
[JsonPropertyName("save_statistics_interval")]
|
||||
public int SaveStatisticsInterval { get; }
|
||||
|
||||
[JsonPropertyName("scan_dirs")]
|
||||
public Dictionary<string, SaveLocation> ScanDirs { get; }
|
||||
|
||||
@@ -939,9 +979,6 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
[JsonPropertyName("ssrf_mitigation")]
|
||||
public bool SsrfMitigation { get; }
|
||||
|
||||
[JsonPropertyName("start_paused_enabled")]
|
||||
public bool StartPausedEnabled { get; }
|
||||
|
||||
[JsonPropertyName("stop_tracker_timeout")]
|
||||
public int StopTrackerTimeout { get; }
|
||||
|
||||
@@ -957,6 +994,9 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
[JsonPropertyName("torrent_content_layout")]
|
||||
public string TorrentContentLayout { get; }
|
||||
|
||||
[JsonPropertyName("torrent_content_remove_option")]
|
||||
public string TorrentContentRemoveOption { get; }
|
||||
|
||||
[JsonPropertyName("torrent_file_size_limit")]
|
||||
public int TorrentFileSizeLimit { get; }
|
||||
|
||||
@@ -984,6 +1024,9 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
[JsonPropertyName("use_https")]
|
||||
public bool UseHttps { get; }
|
||||
|
||||
[JsonPropertyName("ignore_ssl_errors")]
|
||||
public bool IgnoreSslErrors { get; }
|
||||
|
||||
[JsonPropertyName("use_subcategories")]
|
||||
public bool UseSubcategories { get; }
|
||||
|
||||
@@ -996,6 +1039,9 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
[JsonPropertyName("web_ui_address")]
|
||||
public string WebUiAddress { get; }
|
||||
|
||||
[JsonPropertyName("web_ui_api_key")]
|
||||
public string WebUiApiKey { get; }
|
||||
|
||||
[JsonPropertyName("web_ui_ban_duration")]
|
||||
public int WebUiBanDuration { get; }
|
||||
|
||||
@@ -1049,5 +1095,14 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
|
||||
[JsonPropertyName("web_ui_password")]
|
||||
public string WebUiPassword { get; }
|
||||
|
||||
[JsonPropertyName("confirm_torrent_deletion")]
|
||||
public bool ConfirmTorrentDeletion { get; }
|
||||
|
||||
[JsonPropertyName("confirm_torrent_recheck")]
|
||||
public bool ConfirmTorrentRecheck { get; }
|
||||
|
||||
[JsonPropertyName("status_bar_external_ip")]
|
||||
public bool StatusBarExternalIp { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -30,7 +30,9 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
long? uploadRateLimit,
|
||||
bool? useAltSpeedLimits,
|
||||
bool? useSubcategories,
|
||||
float? writeCacheOverload) : base(connectionStatus, dHTNodes, downloadInfoData, downloadInfoSpeed, downloadRateLimit, uploadInfoData, uploadInfoSpeed, uploadRateLimit)
|
||||
float? writeCacheOverload,
|
||||
string? lastExternalAddressV4 = null,
|
||||
string? lastExternalAddressV6 = null) : base(connectionStatus, dHTNodes, downloadInfoData, downloadInfoSpeed, downloadRateLimit, uploadInfoData, uploadInfoSpeed, uploadRateLimit)
|
||||
{
|
||||
AllTimeDownloaded = allTimeDownloaded;
|
||||
AllTimeUploaded = allTimeUploaded;
|
||||
@@ -49,6 +51,8 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
UseAltSpeedLimits = useAltSpeedLimits;
|
||||
UseSubcategories = useSubcategories;
|
||||
WriteCacheOverload = writeCacheOverload;
|
||||
LastExternalAddressV4 = lastExternalAddressV4;
|
||||
LastExternalAddressV6 = lastExternalAddressV6;
|
||||
}
|
||||
|
||||
[JsonPropertyName("alltime_dl")]
|
||||
@@ -101,5 +105,11 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
|
||||
[JsonPropertyName("write_cache_overload")]
|
||||
public float? WriteCacheOverload { get; }
|
||||
|
||||
[JsonPropertyName("last_external_address_v4")]
|
||||
public string? LastExternalAddressV4 { get; }
|
||||
|
||||
[JsonPropertyName("last_external_address_v6")]
|
||||
public string? LastExternalAddressV6 { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,264 +1,219 @@
|
||||
using Lantean.QBitTorrentClient.Converters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Lantean.QBitTorrentClient.Models
|
||||
{
|
||||
public record Torrent
|
||||
{
|
||||
[JsonConstructor]
|
||||
public Torrent(
|
||||
long? addedOn,
|
||||
long? amountLeft,
|
||||
bool? automaticTorrentManagement,
|
||||
float? availability,
|
||||
string? category,
|
||||
long? completed,
|
||||
long? completionOn,
|
||||
string? contentPath,
|
||||
long? downloadLimit,
|
||||
long? downloadSpeed,
|
||||
long? downloaded,
|
||||
long? downloadedSession,
|
||||
long? estimatedTimeOfArrival,
|
||||
bool? firstLastPiecePriority,
|
||||
bool? forceStart,
|
||||
string hash,
|
||||
string? infoHashV1,
|
||||
string? infoHashV2,
|
||||
long? lastActivity,
|
||||
string? magnetUri,
|
||||
float? maxRatio,
|
||||
int? maxSeedingTime,
|
||||
string? name,
|
||||
int? numberComplete,
|
||||
int? numberIncomplete,
|
||||
int? numberLeeches,
|
||||
int? numberSeeds,
|
||||
int? priority,
|
||||
float? progress,
|
||||
float? ratio,
|
||||
float? ratioLimit,
|
||||
string? savePath,
|
||||
long? seedingTime,
|
||||
int? seedingTimeLimit,
|
||||
long? seenComplete,
|
||||
bool? sequentialDownload,
|
||||
long? size,
|
||||
string? state,
|
||||
bool? superSeeding,
|
||||
IReadOnlyList<string>? tags,
|
||||
int? timeActive,
|
||||
long? totalSize,
|
||||
string? tracker,
|
||||
long? uploadLimit,
|
||||
long? uploaded,
|
||||
long? uploadedSession,
|
||||
long? uploadSpeed,
|
||||
long? reannounce,
|
||||
float? inactiveSeedingTimeLimit,
|
||||
float? maxInactiveSeedingTime)
|
||||
{
|
||||
AddedOn = addedOn;
|
||||
AmountLeft = amountLeft;
|
||||
AutomaticTorrentManagement = automaticTorrentManagement;
|
||||
Availability = availability;
|
||||
Category = category;
|
||||
Completed = completed;
|
||||
CompletionOn = completionOn;
|
||||
ContentPath = contentPath;
|
||||
DownloadLimit = downloadLimit;
|
||||
DownloadSpeed = downloadSpeed;
|
||||
Downloaded = downloaded;
|
||||
DownloadedSession = downloadedSession;
|
||||
EstimatedTimeOfArrival = estimatedTimeOfArrival;
|
||||
FirstLastPiecePriority = firstLastPiecePriority;
|
||||
ForceStart = forceStart;
|
||||
Hash = hash;
|
||||
InfoHashV1 = infoHashV1;
|
||||
InfoHashV2 = infoHashV2;
|
||||
LastActivity = lastActivity;
|
||||
MagnetUri = magnetUri;
|
||||
MaxRatio = maxRatio;
|
||||
MaxSeedingTime = maxSeedingTime;
|
||||
Name = name;
|
||||
NumberComplete = numberComplete;
|
||||
NumberIncomplete = numberIncomplete;
|
||||
NumberLeeches = numberLeeches;
|
||||
NumberSeeds = numberSeeds;
|
||||
Priority = priority;
|
||||
Progress = progress;
|
||||
Ratio = ratio;
|
||||
RatioLimit = ratioLimit;
|
||||
SavePath = savePath;
|
||||
SeedingTime = seedingTime;
|
||||
SeedingTimeLimit = seedingTimeLimit;
|
||||
SeenComplete = seenComplete;
|
||||
SequentialDownload = sequentialDownload;
|
||||
Size = size;
|
||||
State = state;
|
||||
SuperSeeding = superSeeding;
|
||||
Tags = tags ?? [];
|
||||
TimeActive = timeActive;
|
||||
TotalSize = totalSize;
|
||||
Tracker = tracker;
|
||||
UploadLimit = uploadLimit;
|
||||
Uploaded = uploaded;
|
||||
UploadedSession = uploadedSession;
|
||||
UploadSpeed = uploadSpeed;
|
||||
Reannounce = reannounce;
|
||||
InactiveSeedingTimeLimit = inactiveSeedingTimeLimit;
|
||||
MaxInactiveSeedingTime = maxInactiveSeedingTime;
|
||||
}
|
||||
|
||||
[JsonPropertyName("added_on")]
|
||||
public long? AddedOn { get; }
|
||||
|
||||
[JsonPropertyName("amount_left")]
|
||||
public long? AmountLeft { get; }
|
||||
|
||||
[JsonPropertyName("auto_tmm")]
|
||||
public bool? AutomaticTorrentManagement { get; }
|
||||
|
||||
[JsonPropertyName("availability")]
|
||||
public float? Availability { get; }
|
||||
|
||||
[JsonPropertyName("category")]
|
||||
public string? Category { get; }
|
||||
|
||||
[JsonPropertyName("completed")]
|
||||
public long? Completed { get; }
|
||||
|
||||
[JsonPropertyName("completion_on")]
|
||||
public long? CompletionOn { get; }
|
||||
|
||||
[JsonPropertyName("content_path")]
|
||||
public string? ContentPath { get; }
|
||||
|
||||
[JsonPropertyName("dl_limit")]
|
||||
public long? DownloadLimit { get; }
|
||||
|
||||
[JsonPropertyName("dlspeed")]
|
||||
public long? DownloadSpeed { get; }
|
||||
|
||||
[JsonPropertyName("downloaded")]
|
||||
public long? Downloaded { get; }
|
||||
|
||||
[JsonPropertyName("downloaded_session")]
|
||||
public long? DownloadedSession { get; }
|
||||
|
||||
[JsonPropertyName("eta")]
|
||||
public long? EstimatedTimeOfArrival { get; }
|
||||
|
||||
[JsonPropertyName("f_l_piece_prio")]
|
||||
public bool? FirstLastPiecePriority { get; }
|
||||
|
||||
[JsonPropertyName("force_start")]
|
||||
public bool? ForceStart { get; }
|
||||
|
||||
[JsonPropertyName("hash")]
|
||||
public string Hash { get; }
|
||||
public string Hash { get; init; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("infohash_v1")]
|
||||
public string? InfoHashV1 { get; }
|
||||
public string? InfoHashV1 { get; init; }
|
||||
|
||||
[JsonPropertyName("infohash_v2")]
|
||||
public string? InfoHashV2 { get; }
|
||||
|
||||
[JsonPropertyName("last_activity")]
|
||||
public long? LastActivity { get; }
|
||||
|
||||
[JsonPropertyName("magnet_uri")]
|
||||
public string? MagnetUri { get; }
|
||||
|
||||
[JsonPropertyName("max_ratio")]
|
||||
public float? MaxRatio { get; }
|
||||
|
||||
[JsonPropertyName("max_seeding_time")]
|
||||
public int? MaxSeedingTime { get; }
|
||||
public string? InfoHashV2 { get; init; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; }
|
||||
public string? Name { get; init; }
|
||||
|
||||
[JsonPropertyName("num_complete")]
|
||||
public int? NumberComplete { get; }
|
||||
|
||||
[JsonPropertyName("num_incomplete")]
|
||||
public int? NumberIncomplete { get; }
|
||||
|
||||
[JsonPropertyName("num_leechs")]
|
||||
public int? NumberLeeches { get; }
|
||||
|
||||
[JsonPropertyName("num_seeds")]
|
||||
public int? NumberSeeds { get; }
|
||||
|
||||
[JsonPropertyName("priority")]
|
||||
public int? Priority { get; }
|
||||
|
||||
[JsonPropertyName("progress")]
|
||||
public float? Progress { get; }
|
||||
|
||||
[JsonPropertyName("ratio")]
|
||||
public float? Ratio { get; }
|
||||
|
||||
[JsonPropertyName("ratio_limit")]
|
||||
public float? RatioLimit { get; }
|
||||
|
||||
[JsonPropertyName("save_path")]
|
||||
public string? SavePath { get; }
|
||||
|
||||
[JsonPropertyName("seeding_time")]
|
||||
public long? SeedingTime { get; }
|
||||
|
||||
[JsonPropertyName("seeding_time_limit")]
|
||||
public int? SeedingTimeLimit { get; }
|
||||
|
||||
[JsonPropertyName("seen_complete")]
|
||||
public long? SeenComplete { get; }
|
||||
|
||||
[JsonPropertyName("seq_dl")]
|
||||
public bool? SequentialDownload { get; }
|
||||
[JsonPropertyName("magnet_uri")]
|
||||
public string? MagnetUri { get; init; }
|
||||
|
||||
[JsonPropertyName("size")]
|
||||
public long? Size { get; }
|
||||
public long? Size { get; init; }
|
||||
|
||||
[JsonPropertyName("progress")]
|
||||
public float? Progress { get; init; }
|
||||
|
||||
[JsonPropertyName("dlspeed")]
|
||||
public long? DownloadSpeed { get; init; }
|
||||
|
||||
[JsonPropertyName("upspeed")]
|
||||
public long? UploadSpeed { get; init; }
|
||||
|
||||
[JsonPropertyName("priority")]
|
||||
public int? Priority { get; init; }
|
||||
|
||||
[JsonPropertyName("num_seeds")]
|
||||
public int? NumberSeeds { get; init; }
|
||||
|
||||
[JsonPropertyName("num_complete")]
|
||||
public int? NumberComplete { get; init; }
|
||||
|
||||
[JsonPropertyName("num_leechs")]
|
||||
public int? NumberLeeches { get; init; }
|
||||
|
||||
[JsonPropertyName("num_incomplete")]
|
||||
public int? NumberIncomplete { get; init; }
|
||||
|
||||
[JsonPropertyName("ratio")]
|
||||
public float? Ratio { get; init; }
|
||||
|
||||
[JsonPropertyName("popularity")]
|
||||
public float? Popularity { get; init; }
|
||||
|
||||
[JsonPropertyName("eta")]
|
||||
public long? EstimatedTimeOfArrival { get; init; }
|
||||
|
||||
[JsonPropertyName("state")]
|
||||
public string? State { get; }
|
||||
public string? State { get; init; }
|
||||
|
||||
[JsonPropertyName("super_seeding")]
|
||||
public bool? SuperSeeding { get; }
|
||||
[JsonPropertyName("seq_dl")]
|
||||
public bool? SequentialDownload { get; init; }
|
||||
|
||||
[JsonPropertyName("f_l_piece_prio")]
|
||||
public bool? FirstLastPiecePriority { get; init; }
|
||||
|
||||
[JsonPropertyName("category")]
|
||||
public string? Category { get; init; }
|
||||
|
||||
[JsonPropertyName("tags")]
|
||||
[JsonConverter(typeof(CommaSeparatedJsonConverter))]
|
||||
public IReadOnlyList<string>? Tags { get; }
|
||||
public IReadOnlyList<string> Tags { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("time_active")]
|
||||
public int? TimeActive { get; }
|
||||
[JsonPropertyName("super_seeding")]
|
||||
public bool? SuperSeeding { get; init; }
|
||||
|
||||
[JsonPropertyName("total_size")]
|
||||
public long? TotalSize { get; }
|
||||
[JsonPropertyName("force_start")]
|
||||
public bool? ForceStart { get; init; }
|
||||
|
||||
[JsonPropertyName("save_path")]
|
||||
public string? SavePath { get; init; }
|
||||
|
||||
[JsonPropertyName("download_path")]
|
||||
public string? DownloadPath { get; init; }
|
||||
|
||||
[JsonPropertyName("content_path")]
|
||||
public string? ContentPath { get; init; }
|
||||
|
||||
[JsonPropertyName("root_path")]
|
||||
public string? RootPath { get; init; }
|
||||
|
||||
[JsonPropertyName("added_on")]
|
||||
public long? AddedOn { get; init; }
|
||||
|
||||
[JsonPropertyName("completion_on")]
|
||||
public long? CompletionOn { get; init; }
|
||||
|
||||
[JsonPropertyName("tracker")]
|
||||
public string? Tracker { get; }
|
||||
public string? Tracker { get; init; }
|
||||
|
||||
[JsonPropertyName("trackers_count")]
|
||||
public int? TrackersCount { get; init; }
|
||||
|
||||
[JsonPropertyName("dl_limit")]
|
||||
public long? DownloadLimit { get; init; }
|
||||
|
||||
[JsonPropertyName("up_limit")]
|
||||
public long? UploadLimit { get; }
|
||||
public long? UploadLimit { get; init; }
|
||||
|
||||
[JsonPropertyName("downloaded")]
|
||||
public long? Downloaded { get; init; }
|
||||
|
||||
[JsonPropertyName("uploaded")]
|
||||
public long? Uploaded { get; }
|
||||
public long? Uploaded { get; init; }
|
||||
|
||||
[JsonPropertyName("downloaded_session")]
|
||||
public long? DownloadedSession { get; init; }
|
||||
|
||||
[JsonPropertyName("uploaded_session")]
|
||||
public long? UploadedSession { get; }
|
||||
public long? UploadedSession { get; init; }
|
||||
|
||||
[JsonPropertyName("upspeed")]
|
||||
public long? UploadSpeed { get; }
|
||||
[JsonPropertyName("amount_left")]
|
||||
public long? AmountLeft { get; init; }
|
||||
|
||||
[JsonPropertyName("reannounce")]
|
||||
public long? Reannounce { get; }
|
||||
[JsonPropertyName("completed")]
|
||||
public long? Completed { get; init; }
|
||||
|
||||
[JsonPropertyName("inactive_seeding_time_limit")]
|
||||
public float? InactiveSeedingTimeLimit { get; }
|
||||
[JsonPropertyName("connections_count")]
|
||||
public int? ConnectionsCount { get; init; }
|
||||
|
||||
[JsonPropertyName("connections_limit")]
|
||||
public int? ConnectionsLimit { get; init; }
|
||||
|
||||
[JsonPropertyName("max_ratio")]
|
||||
public float? MaxRatio { get; init; }
|
||||
|
||||
[JsonPropertyName("max_seeding_time")]
|
||||
public int? MaxSeedingTime { get; init; }
|
||||
|
||||
[JsonPropertyName("max_inactive_seeding_time")]
|
||||
public float? MaxInactiveSeedingTime { get; }
|
||||
public float? MaxInactiveSeedingTime { get; init; }
|
||||
|
||||
[JsonPropertyName("ratio_limit")]
|
||||
public float? RatioLimit { get; init; }
|
||||
|
||||
[JsonPropertyName("seeding_time_limit")]
|
||||
public int? SeedingTimeLimit { get; init; }
|
||||
|
||||
[JsonPropertyName("inactive_seeding_time_limit")]
|
||||
public float? InactiveSeedingTimeLimit { get; init; }
|
||||
|
||||
[JsonPropertyName("share_limit_action")]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public ShareLimitAction? ShareLimitAction { get; init; }
|
||||
|
||||
[JsonPropertyName("seen_complete")]
|
||||
public long? SeenComplete { get; init; }
|
||||
|
||||
[JsonPropertyName("last_activity")]
|
||||
public long? LastActivity { get; init; }
|
||||
|
||||
[JsonPropertyName("total_size")]
|
||||
public long? TotalSize { get; init; }
|
||||
|
||||
[JsonPropertyName("auto_tmm")]
|
||||
public bool? AutomaticTorrentManagement { get; init; }
|
||||
|
||||
[JsonPropertyName("time_active")]
|
||||
public int? TimeActive { get; init; }
|
||||
|
||||
[JsonPropertyName("seeding_time")]
|
||||
public long? SeedingTime { get; init; }
|
||||
|
||||
[JsonPropertyName("availability")]
|
||||
public float? Availability { get; init; }
|
||||
|
||||
[JsonPropertyName("reannounce")]
|
||||
public long? Reannounce { get; init; }
|
||||
|
||||
[JsonPropertyName("comment")]
|
||||
public string? Comment { get; init; }
|
||||
|
||||
[JsonPropertyName("has_metadata")]
|
||||
public bool? HasMetadata { get; init; }
|
||||
|
||||
[JsonPropertyName("created_by")]
|
||||
public string? CreatedBy { get; init; }
|
||||
|
||||
[JsonPropertyName("creation_date")]
|
||||
public long? CreationDate { get; init; }
|
||||
|
||||
[JsonPropertyName("private")]
|
||||
public bool? IsPrivate { get; init; }
|
||||
|
||||
[JsonPropertyName("total_wasted")]
|
||||
public long? TotalWasted { get; init; }
|
||||
|
||||
[JsonPropertyName("pieces_num")]
|
||||
public int? PiecesCount { get; init; }
|
||||
|
||||
[JsonPropertyName("piece_size")]
|
||||
public long? PieceSize { get; init; }
|
||||
|
||||
[JsonPropertyName("pieces_have")]
|
||||
public int? PiecesHave { get; init; }
|
||||
|
||||
[JsonPropertyName("has_tracker_warning")]
|
||||
public bool? HasTrackerWarning { get; init; }
|
||||
|
||||
[JsonPropertyName("has_tracker_error")]
|
||||
public bool? HasTrackerError { get; init; }
|
||||
|
||||
[JsonPropertyName("has_other_announce_error")]
|
||||
public bool? HasOtherAnnounceError { get; init; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
131
Lantean.QBitTorrentClient/Models/TorrentCreationTask.cs
Normal file
131
Lantean.QBitTorrentClient/Models/TorrentCreationTask.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Lantean.QBitTorrentClient.Models
|
||||
{
|
||||
public class TorrentCreationTaskRequest
|
||||
{
|
||||
public string SourcePath { get; set; } = string.Empty;
|
||||
|
||||
public string? TorrentFilePath { get; set; }
|
||||
|
||||
public int? PieceSize { get; set; }
|
||||
|
||||
public bool? Private { get; set; }
|
||||
|
||||
public bool? StartSeeding { get; set; }
|
||||
|
||||
public string? Comment { get; set; }
|
||||
|
||||
public string? Source { get; set; }
|
||||
|
||||
public IEnumerable<string>? Trackers { get; set; }
|
||||
|
||||
public IEnumerable<string>? UrlSeeds { get; set; }
|
||||
|
||||
public string? Format { get; set; }
|
||||
|
||||
public bool? OptimizeAlignment { get; set; }
|
||||
|
||||
public int? PaddedFileSizeLimit { get; set; }
|
||||
}
|
||||
|
||||
public record TorrentCreationTaskStatus
|
||||
{
|
||||
[JsonConstructor]
|
||||
public TorrentCreationTaskStatus(
|
||||
string taskID,
|
||||
string? sourcePath,
|
||||
int? pieceSize,
|
||||
bool? @private,
|
||||
string? timeAdded,
|
||||
string? format,
|
||||
bool? optimizeAlignment,
|
||||
int? paddedFileSizeLimit,
|
||||
string? status,
|
||||
string? comment,
|
||||
string? torrentFilePath,
|
||||
string? source,
|
||||
IReadOnlyList<string>? trackers,
|
||||
IReadOnlyList<string>? urlSeeds,
|
||||
string? timeStarted,
|
||||
string? timeFinished,
|
||||
string? errorMessage,
|
||||
double? progress)
|
||||
{
|
||||
TaskId = taskID;
|
||||
SourcePath = sourcePath;
|
||||
PieceSize = pieceSize;
|
||||
Private = @private;
|
||||
TimeAdded = timeAdded;
|
||||
Format = format;
|
||||
OptimizeAlignment = optimizeAlignment;
|
||||
PaddedFileSizeLimit = paddedFileSizeLimit;
|
||||
Status = status;
|
||||
Comment = comment;
|
||||
TorrentFilePath = torrentFilePath;
|
||||
Source = source;
|
||||
Trackers = trackers ?? Array.Empty<string>();
|
||||
UrlSeeds = urlSeeds ?? Array.Empty<string>();
|
||||
TimeStarted = timeStarted;
|
||||
TimeFinished = timeFinished;
|
||||
ErrorMessage = errorMessage;
|
||||
Progress = progress;
|
||||
}
|
||||
|
||||
[JsonPropertyName("taskID")]
|
||||
public string TaskId { get; }
|
||||
|
||||
[JsonPropertyName("sourcePath")]
|
||||
public string? SourcePath { get; }
|
||||
|
||||
[JsonPropertyName("pieceSize")]
|
||||
public int? PieceSize { get; }
|
||||
|
||||
[JsonPropertyName("private")]
|
||||
public bool? Private { get; }
|
||||
|
||||
[JsonPropertyName("timeAdded")]
|
||||
public string? TimeAdded { get; }
|
||||
|
||||
[JsonPropertyName("format")]
|
||||
public string? Format { get; }
|
||||
|
||||
[JsonPropertyName("optimizeAlignment")]
|
||||
public bool? OptimizeAlignment { get; }
|
||||
|
||||
[JsonPropertyName("paddedFileSizeLimit")]
|
||||
public int? PaddedFileSizeLimit { get; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public string? Status { get; }
|
||||
|
||||
[JsonPropertyName("comment")]
|
||||
public string? Comment { get; }
|
||||
|
||||
[JsonPropertyName("torrentFilePath")]
|
||||
public string? TorrentFilePath { get; }
|
||||
|
||||
[JsonPropertyName("source")]
|
||||
public string? Source { get; }
|
||||
|
||||
[JsonPropertyName("trackers")]
|
||||
public IReadOnlyList<string> Trackers { get; }
|
||||
|
||||
[JsonPropertyName("urlSeeds")]
|
||||
public IReadOnlyList<string> UrlSeeds { get; }
|
||||
|
||||
[JsonPropertyName("timeStarted")]
|
||||
public string? TimeStarted { get; }
|
||||
|
||||
[JsonPropertyName("timeFinished")]
|
||||
public string? TimeFinished { get; }
|
||||
|
||||
[JsonPropertyName("errorMessage")]
|
||||
public string? ErrorMessage { get; }
|
||||
|
||||
[JsonPropertyName("progress")]
|
||||
public double? Progress { get; }
|
||||
}
|
||||
}
|
105
Lantean.QBitTorrentClient/Models/TorrentMetadata.cs
Normal file
105
Lantean.QBitTorrentClient/Models/TorrentMetadata.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Lantean.QBitTorrentClient.Models
|
||||
{
|
||||
public record TorrentMetadata
|
||||
{
|
||||
[JsonConstructor]
|
||||
public TorrentMetadata(
|
||||
string? infoHashV1,
|
||||
string? infoHashV2,
|
||||
string? hash,
|
||||
TorrentMetadataInfo? info,
|
||||
IReadOnlyList<TorrentMetadataTracker>? trackers,
|
||||
IReadOnlyList<string>? webSeeds,
|
||||
string? createdBy,
|
||||
long? creationDate,
|
||||
string? comment)
|
||||
{
|
||||
InfoHashV1 = infoHashV1;
|
||||
InfoHashV2 = infoHashV2;
|
||||
Hash = hash;
|
||||
Info = info;
|
||||
Trackers = trackers ?? Array.Empty<TorrentMetadataTracker>();
|
||||
WebSeeds = webSeeds ?? Array.Empty<string>();
|
||||
CreatedBy = createdBy;
|
||||
CreationDate = creationDate;
|
||||
Comment = comment;
|
||||
}
|
||||
|
||||
[JsonPropertyName("infohash_v1")]
|
||||
public string? InfoHashV1 { get; }
|
||||
|
||||
[JsonPropertyName("infohash_v2")]
|
||||
public string? InfoHashV2 { get; }
|
||||
|
||||
[JsonPropertyName("hash")]
|
||||
public string? Hash { get; }
|
||||
|
||||
[JsonPropertyName("info")]
|
||||
public TorrentMetadataInfo? Info { get; }
|
||||
|
||||
[JsonPropertyName("trackers")]
|
||||
public IReadOnlyList<TorrentMetadataTracker> Trackers { get; }
|
||||
|
||||
[JsonPropertyName("webseeds")]
|
||||
public IReadOnlyList<string> WebSeeds { get; }
|
||||
|
||||
[JsonPropertyName("created_by")]
|
||||
public string? CreatedBy { get; }
|
||||
|
||||
[JsonPropertyName("creation_date")]
|
||||
public long? CreationDate { get; }
|
||||
|
||||
[JsonPropertyName("comment")]
|
||||
public string? Comment { get; }
|
||||
}
|
||||
|
||||
public record TorrentMetadataInfo
|
||||
{
|
||||
[JsonConstructor]
|
||||
public TorrentMetadataInfo(
|
||||
IReadOnlyList<TorrentMetadataFile>? files,
|
||||
long? length,
|
||||
string? name,
|
||||
long? pieceLength,
|
||||
int? piecesCount,
|
||||
bool? @private)
|
||||
{
|
||||
Files = files ?? Array.Empty<TorrentMetadataFile>();
|
||||
Length = length;
|
||||
Name = name;
|
||||
PieceLength = pieceLength;
|
||||
PiecesCount = piecesCount;
|
||||
Private = @private;
|
||||
}
|
||||
|
||||
[JsonPropertyName("files")]
|
||||
public IReadOnlyList<TorrentMetadataFile> Files { get; }
|
||||
|
||||
[JsonPropertyName("length")]
|
||||
public long? Length { get; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; }
|
||||
|
||||
[JsonPropertyName("piece_length")]
|
||||
public long? PieceLength { get; }
|
||||
|
||||
[JsonPropertyName("pieces_num")]
|
||||
public int? PiecesCount { get; }
|
||||
|
||||
[JsonPropertyName("private")]
|
||||
public bool? Private { get; }
|
||||
}
|
||||
|
||||
public record TorrentMetadataFile(
|
||||
[property: JsonPropertyName("path")] string? Path,
|
||||
[property: JsonPropertyName("length")] long? Length);
|
||||
|
||||
public record TorrentMetadataTracker(
|
||||
[property: JsonPropertyName("url")] string? Url,
|
||||
[property: JsonPropertyName("tier")] int? Tier);
|
||||
}
|
@@ -1,4 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Lantean.QBitTorrentClient.Models
|
||||
{
|
||||
@@ -13,7 +15,10 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
int seeds,
|
||||
int leeches,
|
||||
int downloads,
|
||||
string message)
|
||||
string message,
|
||||
long? nextAnnounce,
|
||||
long? minAnnounce,
|
||||
IReadOnlyList<TrackerEndpoint>? endpoints)
|
||||
{
|
||||
Url = url;
|
||||
Status = status;
|
||||
@@ -23,6 +28,9 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
Leeches = leeches;
|
||||
Downloads = downloads;
|
||||
Message = message;
|
||||
NextAnnounce = nextAnnounce;
|
||||
MinAnnounce = minAnnounce;
|
||||
Endpoints = endpoints ?? Array.Empty<TrackerEndpoint>();
|
||||
}
|
||||
|
||||
[JsonPropertyName("url")]
|
||||
@@ -48,5 +56,27 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
|
||||
[JsonPropertyName("msg")]
|
||||
public string Message { get; }
|
||||
|
||||
[JsonPropertyName("next_announce")]
|
||||
public long? NextAnnounce { get; }
|
||||
|
||||
[JsonPropertyName("min_announce")]
|
||||
public long? MinAnnounce { get; }
|
||||
|
||||
[JsonPropertyName("endpoints")]
|
||||
public IReadOnlyList<TrackerEndpoint> Endpoints { get; }
|
||||
}
|
||||
}
|
||||
|
||||
public record TrackerEndpoint(
|
||||
[property: JsonPropertyName("name")] string? Name,
|
||||
[property: JsonPropertyName("updating")] bool? Updating,
|
||||
[property: JsonPropertyName("status")] TrackerStatus Status,
|
||||
[property: JsonPropertyName("msg")] string? Message,
|
||||
[property: JsonPropertyName("bt_version")] int? BitTorrentVersion,
|
||||
[property: JsonPropertyName("num_peers")] int? Peers,
|
||||
[property: JsonPropertyName("num_seeds")] int? Seeds,
|
||||
[property: JsonPropertyName("num_leeches")] int? Leeches,
|
||||
[property: JsonPropertyName("num_downloaded")] int? Downloads,
|
||||
[property: JsonPropertyName("next_announce")] long? NextAnnounce,
|
||||
[property: JsonPropertyName("min_announce")] long? MinAnnounce);
|
||||
}
|
||||
|
@@ -7,5 +7,7 @@
|
||||
Working = 2,
|
||||
Updating = 3,
|
||||
NotWorking = 4,
|
||||
Error = 5,
|
||||
Unreachable = 6
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Lantean.QBitTorrentClient.Models
|
||||
{
|
||||
@@ -7,6 +8,9 @@ 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; }
|
||||
|
||||
@@ -28,6 +32,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; }
|
||||
|
||||
@@ -241,6 +248,9 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
[JsonPropertyName("incomplete_files_ext")]
|
||||
public bool? IncompleteFilesExt { get; set; }
|
||||
|
||||
[JsonPropertyName("use_unwanted_folder")]
|
||||
public bool? UseUnwantedFolder { get; set; }
|
||||
|
||||
[JsonPropertyName("ip_filter_enabled")]
|
||||
public bool? IpFilterEnabled { get; set; }
|
||||
|
||||
@@ -262,6 +272,12 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
[JsonPropertyName("listen_port")]
|
||||
public int? ListenPort { get; set; }
|
||||
|
||||
[JsonPropertyName("ssl_enabled")]
|
||||
public bool? SslEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("ssl_listen_port")]
|
||||
public int? SslListenPort { get; set; }
|
||||
|
||||
[JsonPropertyName("locale")]
|
||||
public string? Locale { get; set; }
|
||||
|
||||
@@ -466,6 +482,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; }
|
||||
|
||||
@@ -517,9 +536,6 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
[JsonPropertyName("ssrf_mitigation")]
|
||||
public bool? SsrfMitigation { get; set; }
|
||||
|
||||
[JsonPropertyName("start_paused_enabled")]
|
||||
public bool? StartPausedEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("stop_tracker_timeout")]
|
||||
public int? StopTrackerTimeout { get; set; }
|
||||
|
||||
@@ -535,6 +551,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; }
|
||||
|
||||
@@ -562,6 +581,9 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
[JsonPropertyName("use_https")]
|
||||
public bool? UseHttps { get; set; }
|
||||
|
||||
[JsonPropertyName("ignore_ssl_errors")]
|
||||
public bool? IgnoreSslErrors { get; set; }
|
||||
|
||||
[JsonPropertyName("use_subcategories")]
|
||||
public bool? UseSubcategories { get; set; }
|
||||
|
||||
@@ -574,6 +596,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; }
|
||||
|
||||
@@ -627,5 +652,32 @@ namespace Lantean.QBitTorrentClient.Models
|
||||
|
||||
[JsonPropertyName("web_ui_password")]
|
||||
public string? WebUiPassword { get; set; }
|
||||
|
||||
[JsonPropertyName("confirm_torrent_deletion")]
|
||||
public bool? ConfirmTorrentDeletion { get; set; }
|
||||
|
||||
[JsonPropertyName("confirm_torrent_recheck")]
|
||||
public bool? ConfirmTorrentRecheck { get; set; }
|
||||
|
||||
[JsonPropertyName("status_bar_external_ip")]
|
||||
public bool? StatusBarExternalIp { get; set; }
|
||||
|
||||
public void Validate()
|
||||
{
|
||||
if (MaxRatio.HasValue && MaxRatioEnabled.HasValue)
|
||||
{
|
||||
throw new InvalidOperationException("Specify either max_ratio or max_ratio_enabled, not both.");
|
||||
}
|
||||
|
||||
if (MaxSeedingTime.HasValue && MaxSeedingTimeEnabled.HasValue)
|
||||
{
|
||||
throw new InvalidOperationException("Specify either max_seeding_time or max_seeding_time_enabled, not both.");
|
||||
}
|
||||
|
||||
if (MaxInactiveSeedingTime.HasValue && MaxInactiveSeedingTimeEnabled.HasValue)
|
||||
{
|
||||
throw new InvalidOperationException("Specify either max_inactive_seeding_time or max_inactive_seeding_time_enabled, not both.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
25
Upgrade-To-v5-Planning.md
Normal file
25
Upgrade-To-v5-Planning.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Upgrade to qBittorrent WebUI v5 – UI Alignment Plan
|
||||
|
||||
## Torrent List Filtering
|
||||
- **Regex toggle & field selector**: Introduce the regex checkbox and the "Filter by" (Name/Save path) select found in v5. Update `FilterState`/`LoggedInLayout` to carry both values, wire them to `TorrentList`’s toolbar, and validate invalid patterns gracefully.
|
||||
- **Filter helper parity**: Rework `FilterHelper.ContainsAllTerms/FilterTerms` to mirror `window.qBittorrent.Misc.containsAllTerms` (evaluate every term, respect `+`/`-` prefixes). Ensure filtering applies to the selected field, not just the torrent name.
|
||||
- **New status buckets**: Add `Running` and `Moving` to `Status` enum, update `FilterHelper.FilterStatus`, `DisplayHelpers`, and `FiltersNav` so counts/icons match upstream.
|
||||
|
||||
## Tracker Filters
|
||||
- **Special buckets**: Extend `FilterHelper`/`DataManager` to create sets for "Announce error", "Error", "Warning", and "Trackerless" in addition to "All". Store the required flags on the UI `Torrent` model (`HasTrackerError`, `HasTrackerWarning`, `HasOtherAnnounceError`, `TrackersCount`, etc.).
|
||||
- **Tracker grouping & removal**: When grouping trackers by host in `FiltersNav`, retain original URL entries so removal can target the right string. Replace the placeholder "Remove tracker" action with a real implementation and disable it for synthetic buckets.
|
||||
|
||||
## ~~Torrent Data Model & Columns~~
|
||||
- ~~**Model sync**: Bring `Lantean.QBTMud.Models.Torrent` into parity with v5 (`Popularity`, `DownloadPath`, `RootPath`, `InfoHashV1/2`, `IsPrivate`, share-limit action fields, tracker flags, etc.) and map them in `DataManager.CreateTorrent`.~~
|
||||
- ~~**Column set alignment**: Match the v5 table defaults—add missing columns (Popularity, Reannounce in, Info hashes, Download path, Private, etc.), fix "Ratio Limit" to display `RatioLimit`, and ensure column ordering/enabled state mirrors `DynamicTable.TorrentsTable`.~~
|
||||
- ~~**Helper updates**: Extend `DisplayHelpers` to format the new fields (popularity, private flag, info hashes, error state icons).~~
|
||||
|
||||
## Actions & Dialogs
|
||||
- ~~**Copy submenu**: Add "Copy comment" and "Copy content path" to the copy submenu in `TorrentActions`, keeping clipboard behaviour identical to v5.~~
|
||||
- ~~**Share ratio dialog**: Update `ShareRatioDialog`, `ShareRatio/ShareRatioMax`, and `DialogHelper.InvokeShareRatioDialog` to surface `ShareLimitAction`, fix the `MaxInactiveSeedingTime` mapping, and call `SetTorrentShareLimit` with the action.~~
|
||||
|
||||
## Add-Torrent Flow
|
||||
- Mirror the v5 add-torrent pane: add controls for incomplete save path, tags, auto-start, queue position, share-limit action, etc., in `AddTorrentOptions.razor`, and wire the new fields into the submission object.
|
||||
|
||||
## ~~Preferences & Local Settings~~
|
||||
- ~~Introduce new v5 toggles such as "Display full tracker URL" in `AdvancedOptions`, persist them via the preferences service, and respect the setting in the tracker column rendering.~~
|
Reference in New Issue
Block a user