Merge branch 'develop' into codex/find-and-fix-a-bug

This commit is contained in:
ahjephson
2025-10-20 10:14:47 +01:00
committed by GitHub
9 changed files with 98 additions and 43 deletions

View File

@@ -92,7 +92,9 @@
<FieldSwitch Label="When ratio reaches" Value="MaxRatioEnabled" ValueChanged="MaxRatioEnabledChanged" /> <FieldSwitch Label="When ratio reaches" Value="MaxRatioEnabled" ValueChanged="MaxRatioEnabledChanged" />
</MudItem> </MudItem>
<MudItem xs="9"> <MudItem xs="9">
<MudNumericField T="int" Label="" Value="MaxRatio" ValueChanged="MaxRatioChanged" Disabled="@(!MaxRatioEnabled)" Min="0" Max="9998" Variant="Variant.Outlined" Validation="MaxRatioValidation" /> <MudNumericField T="float" Label="" Value="MaxRatio" ValueChanged="MaxRatioChanged"
Disabled="@(!MaxRatioEnabled)" Min="0" Max="9998" Variant="Variant.Outlined"
Validation="MaxRatioValidation" />
</MudItem> </MudItem>
<MudItem xs="3"> <MudItem xs="3">
<FieldSwitch Label="When total seeding time reaches" Value="MaxSeedingTimeEnabled" ValueChanged="MaxSeedingTimeEnabledChanged" /> <FieldSwitch Label="When total seeding time reaches" Value="MaxSeedingTimeEnabled" ValueChanged="MaxSeedingTimeEnabledChanged" />

View File

@@ -17,7 +17,7 @@
protected int SlowTorrentUlRateThreshold { get; private set; } protected int SlowTorrentUlRateThreshold { get; private set; }
protected int SlowTorrentInactiveTimer { get; private set; } protected int SlowTorrentInactiveTimer { get; private set; }
protected bool MaxRatioEnabled { get; private set; } protected bool MaxRatioEnabled { get; private set; }
protected int MaxRatio { get; private set; } protected float MaxRatio { get; private set; }
protected bool MaxSeedingTimeEnabled { get; private set; } protected bool MaxSeedingTimeEnabled { get; private set; }
protected int MaxSeedingTime { get; private set; } protected int MaxSeedingTime { get; private set; }
protected int MaxRatioAct { get; private set; } protected int MaxRatioAct { get; private set; }
@@ -275,7 +275,7 @@
await PreferencesChanged.InvokeAsync(UpdatePreferences); await PreferencesChanged.InvokeAsync(UpdatePreferences);
} }
protected async Task MaxRatioChanged(int value) protected async Task MaxRatioChanged(float value)
{ {
MaxRatio = value; MaxRatio = value;
UpdatePreferences.MaxRatio = value; UpdatePreferences.MaxRatio = value;

View File

@@ -62,7 +62,7 @@
<MudCardContent Class="pt-0"> <MudCardContent Class="pt-0">
<MudGrid> <MudGrid>
<MudItem xs="12"> <MudItem xs="12">
<MudSelect T="bool" Label="Default Torrent Management Mode" Value="AutoTmmEnabled" ValueChanged="AutoDeleteModeChanged" Variant="Variant.Outlined"> <MudSelect T="bool" Label="Default Torrent Management Mode" Value="AutoTmmEnabled" ValueChanged="AutoTmmEnabledChanged" Variant="Variant.Outlined">
<MudSelectItem Value="false">Manual</MudSelectItem> <MudSelectItem Value="false">Manual</MudSelectItem>
<MudSelectItem Value="true">Automatic</MudSelectItem> <MudSelectItem Value="true">Automatic</MudSelectItem>
</MudSelect> </MudSelect>

View File

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

View File

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

View File

@@ -39,12 +39,19 @@ namespace Lantean.QBTMud.Services
} }
} }
var tags = new List<string>(mainData.Tags?.Count ?? 0); var tags = new List<string>();
if (mainData.Tags is not null) if (mainData.Tags is not null)
{ {
var seenTags = new HashSet<string>(StringComparer.Ordinal);
foreach (var tag in mainData.Tags) foreach (var tag in mainData.Tags)
{ {
tags.Add(tag); var normalizedTag = NormalizeTag(tag);
if (string.IsNullOrEmpty(normalizedTag) || !seenTags.Add(normalizedTag))
{
continue;
}
tags.Add(normalizedTag);
} }
} }
@@ -157,8 +164,14 @@ namespace Lantean.QBTMud.Services
{ {
foreach (var tag in mainData.TagsRemoved) foreach (var tag in mainData.TagsRemoved)
{ {
torrentList.Tags.Remove(tag); var normalizedTag = NormalizeTag(tag);
torrentList.TagState.Remove(tag); if (string.IsNullOrEmpty(normalizedTag))
{
continue;
}
torrentList.Tags.Remove(normalizedTag);
torrentList.TagState.Remove(normalizedTag);
} }
} }
@@ -200,7 +213,18 @@ namespace Lantean.QBTMud.Services
{ {
foreach (var tag in mainData.Tags) foreach (var tag in mainData.Tags)
{ {
torrentList.Tags.Add(tag); var normalizedTag = NormalizeTag(tag);
if (string.IsNullOrEmpty(normalizedTag))
{
continue;
}
torrentList.Tags.Add(normalizedTag);
var matchingHashes = torrentList.Torrents
.Where(pair => FilterHelper.FilterTag(pair.Value, normalizedTag))
.Select(pair => pair.Key)
.ToHashSet();
torrentList.TagState[normalizedTag] = matchingHashes;
} }
} }
@@ -508,6 +532,12 @@ namespace Lantean.QBTMud.Services
public Torrent CreateTorrent(string hash, QBitTorrentClient.Models.Torrent torrent) public Torrent CreateTorrent(string hash, QBitTorrentClient.Models.Torrent torrent)
{ {
var normalizedTags = torrent.Tags?
.Select(NormalizeTag)
.Where(static tag => !string.IsNullOrEmpty(tag))
.ToList()
?? new List<string>();
return new Torrent( return new Torrent(
hash, hash,
torrent.AddedOn.GetValueOrDefault(), torrent.AddedOn.GetValueOrDefault(),
@@ -548,7 +578,7 @@ namespace Lantean.QBTMud.Services
torrent.Size.GetValueOrDefault(), torrent.Size.GetValueOrDefault(),
torrent.State!, torrent.State!,
torrent.SuperSeeding.GetValueOrDefault(), torrent.SuperSeeding.GetValueOrDefault(),
torrent.Tags!, normalizedTags,
torrent.TimeActive.GetValueOrDefault(), torrent.TimeActive.GetValueOrDefault(),
torrent.TotalSize.GetValueOrDefault(), torrent.TotalSize.GetValueOrDefault(),
torrent.Tracker!, torrent.Tracker!,
@@ -561,6 +591,19 @@ namespace Lantean.QBTMud.Services
torrent.MaxInactiveSeedingTime.GetValueOrDefault()); torrent.MaxInactiveSeedingTime.GetValueOrDefault());
} }
private static string NormalizeTag(string? tag)
{
if (string.IsNullOrEmpty(tag))
{
return string.Empty;
}
var separatorIndex = tag.IndexOf('\t');
var normalized = (separatorIndex >= 0) ? tag[..separatorIndex] : tag;
return normalized.Trim();
}
private static void UpdateCategory(Category existingCategory, QBitTorrentClient.Models.Category category) private static void UpdateCategory(Category existingCategory, QBitTorrentClient.Models.Category category)
{ {
existingCategory.SavePath = category.SavePath ?? existingCategory.SavePath; existingCategory.SavePath = category.SavePath ?? existingCategory.SavePath;
@@ -609,7 +652,14 @@ namespace Lantean.QBTMud.Services
if (torrent.Tags is not null) if (torrent.Tags is not null)
{ {
existingTorrent.Tags.Clear(); existingTorrent.Tags.Clear();
existingTorrent.Tags.AddRange(torrent.Tags); foreach (var tag in torrent.Tags)
{
var normalizedTag = NormalizeTag(tag);
if (!string.IsNullOrEmpty(normalizedTag))
{
existingTorrent.Tags.Add(normalizedTag);
}
}
} }
existingTorrent.TimeActive = torrent.TimeActive ?? existingTorrent.TimeActive; existingTorrent.TimeActive = torrent.TimeActive ?? existingTorrent.TimeActive;
existingTorrent.TotalSize = torrent.TotalSize ?? existingTorrent.TotalSize; existingTorrent.TotalSize = torrent.TotalSize ?? existingTorrent.TotalSize;

View File

@@ -112,7 +112,7 @@ namespace Lantean.QBitTorrentClient.Models
int maxConnecPerTorrent, int maxConnecPerTorrent,
int maxInactiveSeedingTime, int maxInactiveSeedingTime,
bool maxInactiveSeedingTimeEnabled, bool maxInactiveSeedingTimeEnabled,
int maxRatio, float maxRatio,
int maxRatioAct, int maxRatioAct,
bool maxRatioEnabled, bool maxRatioEnabled,
int maxSeedingTime, int maxSeedingTime,
@@ -745,7 +745,7 @@ namespace Lantean.QBitTorrentClient.Models
public bool MaxInactiveSeedingTimeEnabled { get; } public bool MaxInactiveSeedingTimeEnabled { get; }
[JsonPropertyName("max_ratio")] [JsonPropertyName("max_ratio")]
public int MaxRatio { get; } public float MaxRatio { get; }
[JsonPropertyName("max_ratio_act")] [JsonPropertyName("max_ratio_act")]
public int MaxRatioAct { get; } public int MaxRatioAct { get; }

View File

@@ -323,7 +323,7 @@ namespace Lantean.QBitTorrentClient.Models
public bool? MaxInactiveSeedingTimeEnabled { get; set; } public bool? MaxInactiveSeedingTimeEnabled { get; set; }
[JsonPropertyName("max_ratio")] [JsonPropertyName("max_ratio")]
public int? MaxRatio { get; set; } public float? MaxRatio { get; set; }
[JsonPropertyName("max_ratio_act")] [JsonPropertyName("max_ratio_act")]
public int? MaxRatioAct { get; set; } public int? MaxRatioAct { get; set; }

View File

@@ -68,11 +68,13 @@ cd qbtmud
dotnet restore dotnet restore
``` ```
### 3. Build the Application ### 3. Build and Publish the Application
```sh ```sh
dotnet build --configuration Release dotnet publish --configuration Release
``` ```
This will output the Web UI files to `Lantean.QBTMud\bin\Release\net9.0\publish\wwwroot`.
### 4. Configure qBittorrent to Use qbtmud ### 4. Configure qBittorrent to Use qbtmud
Follow the same steps as in the **Installation** section to set qbtmud as your WebUI. Follow the same steps as in the **Installation** section to set qbtmud as your WebUI.