Update project name and namespaces

This commit is contained in:
ahjephson
2024-10-22 09:57:50 +01:00
parent e83488326b
commit 170b2ca601
226 changed files with 2776 additions and 848 deletions

View File

@@ -0,0 +1,42 @@
using Lantean.QBTMud.Models;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace Lantean.QBTMud.Helpers
{
public static class ColumnDefinitionHelper
{
public static ColumnDefinition<T> CreateColumnDefinition<T>(string name, Func<T, object?> selector, RenderFragment<RowContext<T>> rowTemplate, bool iconOnly = false, int? width = null, string? tdClass = null, Func<T, string?>? classFunc = null, bool enabled = true, SortDirection initialDirection = SortDirection.None)
{
var cd = new ColumnDefinition<T>(name, selector, rowTemplate);
cd.Class = "no-wrap";
if (tdClass is not null)
{
cd.Class += " " + tdClass;
}
cd.ClassFunc = classFunc;
cd.Width = width;
cd.Enabled = enabled;
cd.InitialDirection = initialDirection;
cd.IconOnly = iconOnly;
return cd;
}
public static ColumnDefinition<T> CreateColumnDefinition<T>(string name, Func<T, object?> selector, Func<T, string>? formatter = null, bool iconOnly = false, int? width = null, string? tdClass = null, Func<T, string?>? classFunc = null, bool enabled = true, SortDirection initialDirection = SortDirection.None)
{
var cd = new ColumnDefinition<T>(name, selector, formatter);
cd.Class = "no-wrap";
if (tdClass is not null)
{
cd.Class += " " + tdClass;
}
cd.ClassFunc = classFunc;
cd.Width = width;
cd.Enabled = enabled;
cd.InitialDirection = initialDirection;
cd.IconOnly = iconOnly;
return cd;
}
}
}

View File

@@ -0,0 +1,426 @@
using Lantean.QBitTorrentClient;
using Lantean.QBTMud.Components.Dialogs;
using Lantean.QBTMud.Filter;
using Lantean.QBTMud.Models;
using MudBlazor;
namespace Lantean.QBTMud.Helpers
{
public static class DialogHelper
{
public const long _maxFileSize = 4194304;
public static readonly DialogOptions ConfirmDialogOptions = new() { BackgroundClass = "background-blur", MaxWidth = MaxWidth.Small, FullWidth = true };
public static readonly DialogOptions FormDialogOptions = new() { CloseButton = true, MaxWidth = MaxWidth.Medium, BackgroundClass = "background-blur", FullWidth = true };
public static readonly DialogOptions FullScreenDialogOptions = new() { CloseButton = true, MaxWidth = MaxWidth.ExtraExtraLarge, BackgroundClass = "background-blur", FullWidth = true };
public static readonly DialogOptions NonBlurConfirmDialogOptions = new() { MaxWidth = MaxWidth.Small, FullWidth = true };
public static readonly DialogOptions NonBlurFormDialogOptions = new() { CloseButton = true, MaxWidth = MaxWidth.Medium, FullWidth = true };
public static async Task<string?> InvokeAddCategoryDialog(this IDialogService dialogService, IApiClient apiClient)
{
var reference = await dialogService.ShowAsync<CategoryPropertiesDialog>("Add Category", NonBlurFormDialogOptions);
var dialogResult = await reference.Result;
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return null;
}
var category = (Category)dialogResult.Data;
await apiClient.AddCategory(category.Name, category.SavePath);
return category.Name;
}
public static async Task InvokeAddTorrentFileDialog(this IDialogService dialogService, IApiClient apiClient)
{
var result = await dialogService.ShowAsync<AddTorrentFileDialog>("Upload local torrent", FormDialogOptions);
var dialogResult = await result.Result;
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return;
}
var options = (AddTorrentFileOptions)dialogResult.Data;
var streams = new List<Stream>();
var files = new Dictionary<string, Stream>();
foreach (var file in options.Files)
{
var stream = file.OpenReadStream(_maxFileSize);
streams.Add(stream);
files.Add(file.Name, stream);
}
await apiClient.AddTorrent(
urls: null,
files,
options.SavePath,
options.Cookie,
options.Category,
tags: null,
options.SkipHashCheck,
!options.StartTorrent,
options.ContentLayout,
options.RenameTorrent,
options.UploadLimit,
options.DownloadLimit,
ratioLimit: null,
seedingTimeLimit: null,
options.TorrentManagementMode,
options.DownloadInSequentialOrder,
options.DownloadFirstAndLastPiecesFirst);
foreach (var stream in streams)
{
await stream.DisposeAsync();
}
}
public static async Task InvokeAddTorrentLinkDialog(this IDialogService dialogService, IApiClient apiClient, string? url = null)
{
var parameters = new DialogParameters
{
{ nameof(AddTorrentLinkDialog.Url), url }
};
var result = await dialogService.ShowAsync<AddTorrentLinkDialog>("Download from URLs", parameters, FormDialogOptions);
var dialogResult = await result.Result;
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return;
}
var options = (AddTorrentLinkOptions)dialogResult.Data;
await apiClient.AddTorrent(
urls: options.Urls,
torrents: null,
options.SavePath,
options.Cookie,
options.Category,
tags: null,
options.SkipHashCheck,
!options.StartTorrent,
options.ContentLayout,
options.RenameTorrent,
options.UploadLimit,
options.DownloadLimit,
ratioLimit: null,
seedingTimeLimit: null,
options.TorrentManagementMode,
options.DownloadInSequentialOrder,
options.DownloadFirstAndLastPiecesFirst);
}
public static async Task<bool> InvokeDeleteTorrentDialog(this IDialogService dialogService, IApiClient apiClient, params string[] hashes)
{
if (hashes.Length == 0)
{
return false;
}
var parameters = new DialogParameters
{
{ nameof(DeleteDialog.Count), hashes.Length }
};
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)
{
return false;
}
await apiClient.DeleteTorrents(hashes, (bool)dialogResult.Data);
return true;
}
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();
Func<string, long> valueGetFunc = v => v == "∞" ? Limits.NoLimit : long.Parse(v);
var parameters = new DialogParameters
{
{ nameof(SliderFieldDialog<long>.Min), -1L },
{ nameof(SliderFieldDialog<long>.Max), 1000L },
{ nameof(SliderFieldDialog<long>.Value), rate / 1024 },
{ nameof(SliderFieldDialog<long>.ValueDisplayFunc), valueDisplayFunc },
{ nameof(SliderFieldDialog<long>.ValueGetFunc), valueGetFunc },
{ nameof(SliderFieldDialog<long>.Label), "Download rate limit" },
{ nameof(SliderFieldDialog<long>.Adornment), Adornment.End },
{ nameof(SliderFieldDialog<long>.AdornmentText), "KiB/s" },
};
var result = await dialogService.ShowAsync<SliderFieldDialog<long>>("Download Rate", parameters, FormDialogOptions);
var dialogResult = await result.Result;
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return;
}
var kibs = (long)dialogResult.Data;
await apiClient.SetTorrentDownloadLimit(kibs * 1024, null, hashes.ToArray());
}
public static async Task<string?> InvokeEditCategoryDialog(this IDialogService dialogService, IApiClient apiClient, string categoryName)
{
var category = (await apiClient.GetAllCategories()).FirstOrDefault(c => c.Key == categoryName).Value;
var parameters = new DialogParameters
{
{ nameof(CategoryPropertiesDialog.Category), category?.Name },
{ nameof(CategoryPropertiesDialog.SavePath), category?.SavePath },
};
var reference = await dialogService.ShowAsync<CategoryPropertiesDialog>("Edit Category", parameters, NonBlurFormDialogOptions);
var dialogResult = await reference.Result;
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return null;
}
var updatedCategory = (Category)dialogResult.Data;
await apiClient.EditCategory(updatedCategory.Name, updatedCategory.SavePath);
return updatedCategory.Name;
}
public static async Task InvokeRenameFilesDialog(this IDialogService dialogService, string hash)
{
var parameters = new DialogParameters
{
//{ nameof(RenameFilesDialog.Hash), hash }
};
await dialogService.ShowAsync<RenameFilesDialog>("Rename Files", parameters, FullScreenDialogOptions);
}
public static async Task InvokeRssRulesDialog(this IDialogService dialogService)
{
await dialogService.ShowAsync<RssRulesDialog>("Edit Rss Auto Downloading Rules", FullScreenDialogOptions);
}
public static async Task InvokeShareRatioDialog(this IDialogService dialogService, IApiClient apiClient, IEnumerable<Torrent> torrents)
{
var torrentShareRatios = torrents.Select(t => new ShareRatioMax
{
InactiveSeedingTimeLimit = t.InactiveSeedingTimeLimit,
MaxInactiveSeedingTime = t.InactiveSeedingTimeLimit,
MaxRatio = t.MaxRatio,
MaxSeedingTime = t.MaxSeedingTime,
RatioLimit = t.RatioLimit,
SeedingTimeLimit = t.SeedingTimeLimit,
});
var torrentsHaveSameShareRatio = torrentShareRatios.Distinct().Count() == 1;
var parameters = new DialogParameters
{
{ nameof(ShareRatioDialog.Value), torrentsHaveSameShareRatio ? torrentShareRatios.FirstOrDefault() : null },
};
var result = await dialogService.ShowAsync<ShareRatioDialog>("Share ratio", parameters, FormDialogOptions);
var dialogResult = await result.Result;
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return;
}
var shareRatio = (ShareRatio)dialogResult.Data;
await apiClient.SetTorrentShareLimit(shareRatio.RatioLimit, shareRatio.SeedingTimeLimit, shareRatio.InactiveSeedingTimeLimit, null, torrents.Select(t => t.Hash).ToArray());
}
public static async Task InvokeStringFieldDialog(this IDialogService dialogService, string title, string label, string? value, Func<string, Task> onSuccess)
{
var result = await dialogService.ShowStringFieldDialog(title, label, value);
if (result is not null)
{
await onSuccess(result);
}
}
public static async Task InvokeUploadRateDialog(this IDialogService dialogService, IApiClient apiClient, long rate, IEnumerable<string> hashes)
{
Func<long, string> valueDisplayFunc = v => v == Limits.NoLimit ? "∞" : v.ToString();
Func<string, long> valueGetFunc = v => v == "∞" ? Limits.NoLimit : long.Parse(v);
var parameters = new DialogParameters
{
{ nameof(SliderFieldDialog<long>.Min), -1L },
{ nameof(SliderFieldDialog<long>.Max), 1000L },
{ nameof(SliderFieldDialog<long>.Value), rate / 1024 },
{ nameof(SliderFieldDialog<long>.ValueDisplayFunc), valueDisplayFunc },
{ nameof(SliderFieldDialog<long>.ValueGetFunc), valueGetFunc },
{ nameof(SliderFieldDialog<long>.Label), "Upload rate limit" },
{ nameof(SliderFieldDialog<long>.Adornment), Adornment.End },
{ nameof(SliderFieldDialog<long>.AdornmentText), "KiB/s" },
};
var result = await dialogService.ShowAsync<SliderFieldDialog<long>>("Upload Rate", parameters, FormDialogOptions);
var dialogResult = await result.Result;
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return;
}
var kibs = (long)dialogResult.Data;
await apiClient.SetTorrentUploadLimit(kibs * 1024, null, hashes.ToArray());
}
public static async Task<HashSet<QBitTorrentClient.Models.PeerId>?> ShowAddPeersDialog(this IDialogService dialogService)
{
var reference = await dialogService.ShowAsync<AddPeerDialog>("Add Peer", NonBlurFormDialogOptions);
var dialogResult = await reference.Result;
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return null;
}
var peers = (HashSet<QBitTorrentClient.Models.PeerId>)dialogResult.Data;
return peers;
}
public static async Task<HashSet<string>?> ShowAddTagsDialog(this IDialogService dialogService)
{
var reference = await dialogService.ShowAsync<AddTagDialog>("Add Tags", NonBlurFormDialogOptions);
var dialogResult = await reference.Result;
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return null;
}
var tags = (HashSet<string>)dialogResult.Data;
return tags;
}
public static async Task<HashSet<string>?> ShowAddTrackersDialog(this IDialogService dialogService)
{
var reference = await dialogService.ShowAsync<AddTrackerDialog>("Add Tracker", NonBlurFormDialogOptions);
var dialogResult = await reference.Result;
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return null;
}
var tags = (HashSet<string>)dialogResult.Data;
return tags;
}
public static async Task<(HashSet<string> SelectedColumns, Dictionary<string, int?> ColumnWidths)> ShowColumnsOptionsDialog<T>(this IDialogService dialogService, List<ColumnDefinition<T>> columnDefinitions, HashSet<string> selectedColumns, Dictionary<string, int?> widths)
{
var parameters = new DialogParameters
{
{ nameof(ColumnOptionsDialog<T>.Columns), columnDefinitions },
{ nameof(ColumnOptionsDialog<T>.SelectedColumns), selectedColumns },
{ nameof(ColumnOptionsDialog<T>.Widths), widths },
};
var reference = await dialogService.ShowAsync<ColumnOptionsDialog<T>>("Column Options", parameters, FormDialogOptions);
var dialogResult = await reference.Result;
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return default;
}
return ((HashSet<string>, Dictionary<string, int?>))dialogResult.Data;
}
public static async Task<bool> ShowConfirmDialog(this IDialogService dialogService, string title, string content)
{
var parameters = new DialogParameters
{
{ nameof(ConfirmDialog.Content), content }
};
var result = await dialogService.ShowAsync<ConfirmDialog>(title, parameters, ConfirmDialogOptions);
var dialogResult = await result.Result;
return dialogResult is not null && !dialogResult.Canceled;
}
public static async Task ShowConfirmDialog(this IDialogService dialogService, string title, string content, Func<Task> onSuccess)
{
var parameters = new DialogParameters
{
{ nameof(ConfirmDialog.Content), content }
};
var result = await dialogService.ShowAsync<ConfirmDialog>(title, parameters, ConfirmDialogOptions);
var dialogResult = await result.Result;
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return;
}
await onSuccess();
}
public static async Task ShowConfirmDialog(this IDialogService dialogService, string title, string content, Action onSuccess)
{
await dialogService.ShowConfirmDialog(title, content, () =>
{
onSuccess();
return Task.CompletedTask;
});
}
public static async Task<List<PropertyFilterDefinition<T>>?> ShowFilterOptionsDialog<T>(this IDialogService dialogService, List<PropertyFilterDefinition<T>>? propertyFilterDefinitions)
{
var parameters = new DialogParameters
{
{ nameof(FilterOptionsDialog<T>.FilterDefinitions), propertyFilterDefinitions },
};
var result = await dialogService.ShowAsync<FilterOptionsDialog<T>>("Filters", parameters, FormDialogOptions);
var dialogResult = await result.Result;
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return null;
}
return (List<PropertyFilterDefinition<T>>?)dialogResult.Data;
}
public static async Task<string?> ShowStringFieldDialog(this IDialogService dialogService, string title, string label, string? value)
{
var parameters = new DialogParameters
{
{ nameof(StringFieldDialog.Label), label },
{ nameof(StringFieldDialog.Value), value }
};
var result = await dialogService.ShowAsync<StringFieldDialog>(title, parameters, FormDialogOptions);
var dialogResult = await result.Result;
if (dialogResult is null || dialogResult.Canceled || dialogResult.Data is null)
{
return null;
}
return (string)dialogResult.Data;
}
public static async Task ShowSubMenu(this IDialogService dialogService, IEnumerable<string> hashes, UIAction parent, Dictionary<string, Torrent> torrents, QBitTorrentClient.Models.Preferences? preferences)
{
var parameters = new DialogParameters
{
{ nameof(SubMenuDialog.ParentAction), parent },
{ nameof(SubMenuDialog.Hashes), hashes },
{ nameof(SubMenuDialog.Torrents), torrents },
{ nameof(SubMenuDialog.Preferences), preferences },
};
await dialogService.ShowAsync<SubMenuDialog>(parent.Text, parameters, FormDialogOptions);
}
}
}

View File

@@ -0,0 +1,421 @@
using ByteSizeLib;
using Lantean.QBTMud.Models;
using MudBlazor;
using System.Diagnostics.CodeAnalysis;
using System.Text;
namespace Lantean.QBTMud.Helpers
{
public static class DisplayHelpers
{
/// <summary>
/// Formats a time period in seconds into an appropriate unit based on the size.
/// </summary>
/// <param name="seconds"></param>
/// <param name="prefix"></param>
/// <param name="suffix"></param>
/// <returns></returns>
public static string Duration(long? seconds, string? prefix = null, string? suffix = null)
{
if (seconds is null)
{
return "";
}
if (seconds == 8640000)
{
return "∞";
}
if (seconds < 60)
{
return "< 1m";
}
TimeSpan time;
try
{
time = TimeSpan.FromSeconds(seconds.Value);
}
catch (OverflowException)
{
return "∞";
}
var sb = new StringBuilder();
if (prefix is not null)
{
sb.Append(prefix);
}
if (time.Days > 0)
{
sb.Append(time.Days);
sb.Append('d');
if (time.Hours != 0)
{
sb.Append(' ');
sb.Append(time.Hours);
sb.Append('h');
}
}
else if (time.Hours > 0)
{
sb.Append(time.Hours);
sb.Append('h');
if (time.Minutes != 0)
{
sb.Append(' ');
sb.Append(time.Minutes);
sb.Append('m');
}
}
else
{
sb.Append(time.Minutes);
sb.Append('m');
}
if (suffix is not null)
{
sb.Append(' ');
sb.Append(suffix);
}
return sb.ToString();
}
/// <summary>
/// Formats a file size in bytes into an appropriate unit based on the size.
/// </summary>
/// <param name="size"></param>
/// <param name="prefix"></param>
/// <param name="suffix"></param>
/// <returns></returns>
public static string Size(long? size, string? prefix = null, string? suffix = null)
{
if (size is null)
{
return "";
}
if (size < 0)
{
size = 0;
}
var stringBuilder = new StringBuilder();
if (prefix is not null)
{
stringBuilder.Append(prefix);
}
stringBuilder.Append(ByteSize.FromBytes(size.Value).ToString("#.##"));
if (suffix is not null)
{
stringBuilder.Append(suffix);
}
return stringBuilder.ToString();
}
/// <summary>
/// Formats a file size in bytes into an appropriate unit based on the size.
/// </summary>
/// <param name="size"></param>
/// <param name="prefix"></param>
/// <param name="suffix"></param>
/// <returns></returns>
public static string Size(object? sizeValue, string? prefix = null, string? suffix = null)
{
if (sizeValue is not long size)
{
return "";
}
return Size(size);
}
/// <summary>
/// Formats a transfer speed in bytes/s into an appropriate unit based on the size.
/// </summary>
/// <param name="size"></param>
/// <param name="prefix"></param>
/// <param name="suffix"></param>
/// <returns></returns>
public static string Speed(long? size, string? prefix = null, string? suffix = null)
{
if (size is null)
{
return "";
}
if (size == -1)
{
return "∞";
}
var stringBuilder = new StringBuilder();
if (prefix is not null)
{
stringBuilder.Append(prefix);
}
stringBuilder.Append(ByteSize.FromBytes(size.Value).ToString("#.##"));
stringBuilder.Append("/s");
if (suffix is not null)
{
stringBuilder.Append(suffix);
}
return stringBuilder.ToString();
}
/// <summary>
/// Formats a value into an empty string if null, otherwise the value.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="prefix"></param>
/// <param name="suffix"></param>
/// <returns></returns>
public static string EmptyIfNull<T>(T? value, string? prefix = null, string? suffix = null, [StringSyntax("NumericFormat")] string? format = null) where T : struct, IConvertible
{
if (value is null)
{
return "";
}
var stringBuilder = new StringBuilder();
if (prefix is not null)
{
stringBuilder.Append(prefix);
}
if (format is not null)
{
if (value is long longValue)
{
stringBuilder.Append(longValue.ToString(format));
}
else if (value is int intValue)
{
stringBuilder.Append(intValue.ToString(format));
}
else if (value is float floatValue)
{
stringBuilder.Append(floatValue.ToString(format));
}
else if (value is double doubleValue)
{
stringBuilder.Append(doubleValue.ToString(format));
}
else if (value is decimal decimalValue)
{
stringBuilder.Append(decimalValue.ToString(format));
}
else if (value is short shortValue)
{
stringBuilder.Append(shortValue.ToString(format));
}
else
{
stringBuilder.Append(value.Value);
}
}
else
{
stringBuilder.Append(value.Value);
}
if (suffix is not null)
{
stringBuilder.Append(suffix);
}
return stringBuilder.ToString();
}
/// <summary>
/// Formats a value into an empty string if null, otherwise the value.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="prefix"></param>
/// <param name="suffix"></param>
/// <returns></returns>
public static string EmptyIfNull(string? value, string? prefix = null, string? suffix = null)
{
if (value is null)
{
return "";
}
var stringBuilder = new StringBuilder();
if (prefix is not null)
{
stringBuilder.Append(prefix);
}
stringBuilder.Append(value);
if (suffix is not null)
{
stringBuilder.Append(suffix);
}
return stringBuilder.ToString();
}
/// <summary>
/// Formats a unix time (in seconds) into a local date time.
/// </summary>
/// <param name="value"></param>
/// <param name="negativeDescription"></param>
/// <returns></returns>
public static string DateTime(long? value, string negativeDescription = "")
{
if (value is null)
{
return "";
}
if (value.Value == -1)
{
return negativeDescription;
}
var dateTimeOffset = DateTimeOffset.FromUnixTimeSeconds(value.Value);
return dateTimeOffset.ToLocalTime().DateTime.ToString();
}
/// <summary>
/// Formats a value into a percentage or empty string if null.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static string Percentage(float? value)
{
if (value is null)
{
return "";
}
if (value < 0)
{
value = 0;
}
if (value == 0)
{
return "0%";
}
return value.Value.ToString("0.#%");
}
public static string State(string? state)
{
var status = state switch
{
"downloading" => "Downloading",
"stalledDL" => "Stalled",
"metaDL" => "Downloading metadata",
"forcedMetaDL" => "[F] Downloading metadata",
"forcedDL" => "[F] Downloading",
"uploading" or "stalledUP" => "Seeding",
"forcedUP" => "[F] Seeding",
"queuedDL" or "queuedUP" => "Queued",
"checkingDL" or "checkingUP" => "Checking",
"queuedForChecking" => "Queued for checking",
"checkingResumeData" => "Checking resume data",
"pausedDL" => "Paused",
"pausedUP" => "Completed",
"stoppedDL" => "Stopped",
"stoppedUP" => "Completed",
"moving" => "Moving",
"missingFiles" => "Missing Files",
"error" => "Errored",
_ => "Unknown",
};
return status;
}
public static (string, Color) GetStateIcon(string? state)
{
switch (state)
{
case "forcedDL":
case "metaDL":
case "forcedMetaDL":
case "downloading":
return (Icons.Material.Filled.Downloading, Color.Success);
case "forcedUP":
case "uploading":
return (Icons.Material.Filled.Upload, Color.Info);
case "stalledUP":
return (Icons.Material.Filled.KeyboardDoubleArrowUp, Color.Info);
case "stalledDL":
return (Icons.Material.Filled.KeyboardDoubleArrowDown, Color.Success);
case "pausedDL":
return (Icons.Material.Filled.Pause, Color.Success);
case "pausedUP":
return (Icons.Material.Filled.Pause, Color.Info);
case "stoppedDL":
return (Icons.Material.Filled.Stop, Color.Success);
case "stoppedUP":
return (Icons.Material.Filled.Stop, Color.Info);
case "queuedDL":
case "queuedUP":
return (Icons.Material.Filled.Queue, Color.Default);
case "checkingDL":
case "checkingUP":
return (Icons.Material.Filled.Loop, Color.Info);
case "queuedForChecking":
case "checkingResumeData":
return (Icons.Material.Filled.Loop, Color.Warning);
case "moving":
return (Icons.Material.Filled.Moving, Color.Info);
case "error":
case "unknown":
case "missingFiles":
return (Icons.Material.Filled.Error, Color.Error);
default:
return (Icons.Material.Filled.QuestionMark, Color.Warning);
}
}
public static (string, Color) GetStatusIcon(string statusValue)
{
var status = Enum.Parse<Status>(statusValue);
return GetStatusIcon(status);
}
private static (string, Color) GetStatusIcon(Status status)
{
return status switch
{
Status.All => (Icons.Material.Filled.AllOut, Color.Warning),
Status.Downloading => (Icons.Material.Filled.Downloading, Color.Success),
Status.Seeding => (Icons.Material.Filled.Upload, Color.Info),
Status.Completed => (Icons.Material.Filled.Check, Color.Default),
Status.Resumed => (Icons.Material.Filled.PlayArrow, Color.Success),
Status.Paused => (Icons.Material.Filled.Pause, Color.Default),
Status.Stopped => (Icons.Material.Filled.Stop, Color.Default),
Status.Active => (Icons.Material.Filled.Sort, Color.Success),
Status.Inactive => (Icons.Material.Filled.Sort, Color.Error),
Status.Stalled => (Icons.Material.Filled.Sort, Color.Info),
Status.StalledUploading => (Icons.Material.Filled.KeyboardDoubleArrowUp, Color.Info),
Status.StalledDownloading => (Icons.Material.Filled.KeyboardDoubleArrowDown, Color.Success),
Status.Checking => (Icons.Material.Filled.Loop, Color.Info),
Status.Errored => (Icons.Material.Filled.Error, Color.Error),
_ => (Icons.Material.Filled.QuestionMark, Color.Inherit),
};
}
}
}

View File

@@ -0,0 +1,287 @@
using Lantean.QBTMud.Models;
namespace Lantean.QBTMud.Helpers
{
public static class FilterHelper
{
public const string TAG_ALL = "All";
public const string TAG_UNTAGGED = "Untagged";
public const string CATEGORY_ALL = "All";
public const string CATEGORY_UNCATEGORIZED = "Uncategorized";
public const string TRACKER_ALL = "All";
public const string TRACKER_TRACKERLESS = "Trackerless";
public static IEnumerable<Torrent> Filter(this IEnumerable<Torrent> torrents, FilterState filterState)
{
return torrents.Where(t => FilterStatus(t, filterState.Status))
.Where(t => FilterTag(t, filterState.Tag))
.Where(t => FilterCategory(t, filterState.Category, filterState.UseSubcategories))
.Where(t => FilterTracker(t, filterState.Tracker))
.Where(t => FilterTerms(t.Name, filterState.Terms));
}
public static HashSet<string> ToHashesHashSet(this IEnumerable<Torrent> torrents)
{
return torrents.Select(t => t.Hash).ToHashSet();
}
public static bool AddIfTrue(this HashSet<string> hashSet, string value, bool condition)
{
if (condition)
{
return hashSet.Add(value);
}
return false;
}
public static bool RemoveIfTrue(this HashSet<string> hashSet, string value, bool condition)
{
if (condition)
{
return hashSet.Remove(value);
}
return false;
}
public static bool AddIfTrueOrRemove(this HashSet<string> hashSet, string value, bool condition)
{
if (condition)
{
return hashSet.Add(value);
}
else
{
return hashSet.Remove(value);
}
}
public static bool ContainsAllTerms(string text, IEnumerable<string> terms)
{
return terms.Any(t =>
{
var term = t;
var isTermRequired = term[0] == '+';
var isTermExcluded = term[0] == '-';
if (isTermRequired || isTermExcluded)
{
if (term.Length == 1)
{
return true;
}
term = term[1..];
}
var textContainsTerm = text.Contains(term, StringComparison.OrdinalIgnoreCase);
return isTermExcluded ? !textContainsTerm : textContainsTerm;
});
}
public static bool FilterTerms(string field, string? terms)
{
if (terms is null || terms == "")
{
return true;
}
return ContainsAllTerms(field, terms.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
}
public static bool FilterTerms(Torrent torrent, string? terms)
{
if (terms is null || terms == "")
{
return true;
}
return ContainsAllTerms(torrent.Name, terms.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
}
public static bool FilterTracker(Torrent torrent, string tracker)
{
if (tracker == TRACKER_ALL)
{
return true;
}
if (tracker == TRACKER_TRACKERLESS)
{
return torrent.Tracker == "";
}
return torrent.Tracker.Contains(tracker);
}
public static bool FilterCategory(Torrent torrent, string category, bool useSubcategories)
{
switch (category)
{
case CATEGORY_ALL:
break;
case CATEGORY_UNCATEGORIZED:
if (!string.IsNullOrEmpty(torrent.Category))
{
return false;
}
break;
default:
if (!useSubcategories)
{
if (torrent.Category != category)
{
return false;
}
else
{
if (!torrent.Category.StartsWith(category))
{
return false;
}
}
}
break;
}
return true;
}
public static bool FilterTag(Torrent torrent, string tag)
{
if (tag == TAG_ALL)
{
return true;
}
if (tag == TAG_UNTAGGED)
{
return torrent.Tags.Count == 0;
}
return torrent.Tags.Contains(tag);
}
public static bool FilterStatus(Torrent torrent, Status status)
{
return FilterStatus(torrent.State, torrent.UploadSpeed, status);
}
public static bool FilterStatus(string state, long uploadSpeed, Status status)
{
bool inactive = false;
switch (status)
{
case Status.All:
return true;
case Status.Downloading:
if (state != "downloading" && !state.Contains("DL"))
{
return false;
}
break;
case Status.Seeding:
if (state != "uploading" && state != "forcedUP" && state != "stalledUP" && state != "queuedUP" && state != "checkingUP")
{
return false;
}
break;
case Status.Completed:
if (state != "uploading" && !state.Contains("UP"))
{
return false;
}
break;
case Status.Resumed:
if (!state.Contains("resumed"))
{
return false;
}
break;
case Status.Paused:
if (!state.Contains("paused") || !state.Contains("stopped"))
{
return false;
}
break;
case Status.Inactive:
case Status.Active:
if (status == Status.Inactive)
{
inactive = true;
}
bool check;
if (state == "stalledDL")
{
check = uploadSpeed > 0;
}
else
{
check = state == "metaDL" || state == "forcedMetaDL" || state == "downloading" || state == "forcedDL" || state == "uploading" || state == "forcedUP";
}
if (check == inactive)
{
return false;
}
break;
case Status.Stalled:
if (state != "stalledUP" && state != "stalledDL")
{
return false;
}
break;
case Status.StalledUploading:
if (state != "stalledUP")
{
return false;
}
break;
case Status.StalledDownloading:
if (state != "stalledDL")
{
return false;
}
break;
case Status.Checking:
if (state != "checkingUP" && state != "checkingDL" && state != "checkingResumeData")
{
return false;
}
break;
case Status.Errored:
if (state != "error" && state != "unknown" && state != "missingFiles")
{
return false;
}
break;
}
return true;
}
public static string GetStatusName(this string status)
{
return status switch
{
nameof(Status.StalledUploading) => "Stalled Uploading",
nameof(Status.StalledDownloading) => "Stalled Downloading",
_ => status,
};
}
}
}