using Lantean.QBitTorrentClient; using Lantean.QBitTorrentClient.Models; using Lantean.QBTMud.Components.UI; using Lantean.QBTMud.Helpers; using Lantean.QBTMud.Interop; using Lantean.QBTMud.Models; using Lantean.QBTMud.Services; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; using MudBlazor; using System.Net; namespace Lantean.QBTMud.Components { public partial class TrackersTab : IAsyncDisposable { private readonly CancellationTokenSource _timerCancellationToken = new(); private bool _disposedValue; private string? _sortColumn; private SortDirection _sortDirection; private const string _toolbar = nameof(_toolbar); private const string _context = nameof(_context); [Parameter, EditorRequired] public string? Hash { get; set; } [Parameter] public bool Active { get; set; } [CascadingParameter(Name = "RefreshInterval")] public int RefreshInterval { get; set; } [Inject] protected IDialogService DialogService { get; set; } = default!; [Inject] protected IJSRuntime JSRuntime { get; set; } = default!; [Inject] protected IApiClient ApiClient { get; set; } = default!; [Inject] protected IDataManager DataManager { get; set; } = default!; protected IReadOnlyList? TrackerList { get; set; } protected IEnumerable? Trackers => GetTrackers(); protected TorrentTracker? ContextMenuItem { get; set; } protected TorrentTracker? SelectedItem { get; set; } protected MudMenu? ContextMenu { get; set; } protected DynamicTable? Table { get; set; } protected override async Task OnParametersSetAsync() { if (Hash is null) { return; } if (!Active) { return; } TrackerList = await ApiClient.GetTorrentTrackers(Hash); await InvokeAsync(StateHasChanged); } protected IEnumerable? GetTrackers() { if (TrackerList is null) { return null; } var trackers = TrackerList.Where(t => !IsRealTracker(t)).ToList(); trackers.AddRange(TrackerList.Where(IsRealTracker).OrderByDirection(_sortDirection, GetSortSelector())); return trackers.AsReadOnly(); } private static bool IsRealTracker(TorrentTracker torrentTracker) { return !torrentTracker.Url.StartsWith("**"); } private Func GetSortSelector() { var sortSelector = ColumnsDefinitions.Find(c => c.Id == _sortColumn)?.SortSelector; return sortSelector ?? (i => i.Url); } protected void SortDirectionChanged(SortDirection sortDirection) { _sortDirection = sortDirection; StateHasChanged(); } protected void SortColumnChanged(string column) { _sortColumn = column; StateHasChanged(); } protected async Task ColumnOptions() { if (Table is null) { return; } await Table.ShowColumnOptionsDialog(); } protected Task TableDataContextMenu(TableDataContextMenuEventArgs eventArgs) { return ShowContextMenu(eventArgs.Item, eventArgs.MouseEventArgs); } protected Task TableDataLongPress(TableDataLongPressEventArgs eventArgs) { return ShowContextMenu(eventArgs.Item, eventArgs.LongPressEventArgs); } protected async Task ShowContextMenu(TorrentTracker? tracker, EventArgs eventArgs) { if (tracker is not null && IsRealTracker(tracker)) { ContextMenuItem = tracker; } else { ContextMenuItem = null; } if (ContextMenu is null) { return; } var normalizedEventArgs = eventArgs.NormalizeForContextMenu(); await ContextMenu.OpenMenuAsync(normalizedEventArgs); } protected void SelectedItemChanged(TorrentTracker torrentTracker) { SelectedItem = torrentTracker; } protected async Task AddTracker() { if (Hash is null) { return; } var trackers = await DialogService.ShowAddTrackersDialog(); if (trackers is null || trackers.Count == 0) { return; } await ApiClient.AddTrackersToTorrent(Hash, trackers); } protected Task EditTrackerToolbar() { return EditTracker(SelectedItem); } protected Task EditTrackerContextMenu() { return EditTracker(ContextMenuItem); } protected async Task EditTracker(TorrentTracker? tracker) { if (Hash is null || tracker is null) { return; } await DialogService.InvokeStringFieldDialog("Edit Tracker", "Tracker URL", tracker.Url, async (value) => await ApiClient.EditTracker(Hash, tracker.Url, value)); } protected Task RemoveTrackerToolbar() { return RemoveTracker(SelectedItem); } protected Task RemoveTrackerContextMenu() { return RemoveTracker(ContextMenuItem); } protected async Task RemoveTracker(TorrentTracker? tracker) { if (Hash is null || tracker is null) { return; } await ApiClient.RemoveTrackers(Hash, [tracker.Url]); } protected Task CopyTrackerUrlToolbar() { return CopyTrackerUrl(SelectedItem); } protected Task CopyTrackerUrlContextMenu() { return CopyTrackerUrl(ContextMenuItem); } protected async Task CopyTrackerUrl(TorrentTracker? tracker) { if (Hash is null) { return; } if (tracker is null) { return; } await JSRuntime.WriteToClipboard(tracker.Url); } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { using (var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(RefreshInterval))) { while (!_timerCancellationToken.IsCancellationRequested && await timer.WaitForNextTickAsync()) { if (Active && Hash is not null) { try { TrackerList = await ApiClient.GetTorrentTrackers(Hash); } catch (HttpRequestException exception) when (exception.StatusCode == HttpStatusCode.Forbidden || exception.StatusCode == HttpStatusCode.NotFound) { _timerCancellationToken.CancelIfNotDisposed(); return; } } await InvokeAsync(StateHasChanged); } } } } protected IEnumerable> Columns => ColumnsDefinitions; public static List> ColumnsDefinitions { get; } = [ new ColumnDefinition("Tier", w => w.Tier, w => w.Tier > 0 ? w.Tier.ToString() : ""), new ColumnDefinition("URL", w => w.Url), new ColumnDefinition("Status", w => w.Status), new ColumnDefinition("Peers", w => w.Peers), new ColumnDefinition("Seeds", w => w.Seeds), new ColumnDefinition("Leeches", w => w.Leeches), new ColumnDefinition("Times Downloaded", w => w.Downloads), new ColumnDefinition("Message", w => w.Message), ]; protected virtual async Task DisposeAsync(bool disposing) { if (!_disposedValue) { if (disposing) { _timerCancellationToken.Cancel(); _timerCancellationToken.Dispose(); await Task.CompletedTask; } _disposedValue = true; } } public async ValueTask DisposeAsync() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method await DisposeAsync(disposing: true); GC.SuppressFinalize(this); } } }