Skip to content

Commit

Permalink
Merge pull request #1397 from Nexus-Mods/feat/888-completed-downloads
Browse files Browse the repository at this point in the history
Completed downloads section on Downloads page
  • Loading branch information
Al12rs authored May 22, 2024
2 parents c2a7369 + 8b5f27d commit 3742d53
Show file tree
Hide file tree
Showing 21 changed files with 1,484 additions and 513 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using NexusMods.MnemonicDB.Abstractions.Attributes;
using NexusMods.MnemonicDB.Abstractions.ElementComparers;

namespace NexusMods.Abstractions.MnemonicDB.Attributes;

/// <summary>
/// A MneumonicDB attribute for a DateTime value
/// </summary>
/// <remarks>
/// Depending on use case, consider using transaction timestamps instead of a dedicated DateTimeAttribute.
/// </remarks>
public class DateTimeAttribute(string ns, string name) : ScalarAttribute<DateTime, long>(ValueTags.Int64, ns, name)
{
/// <inheritdoc />
protected override long ToLowLevel(DateTime value) => value.ToBinary();

/// <inheritdoc />
protected override DateTime FromLowLevel(long value, ValueTags tags) => DateTime.FromBinary(value);
}
11 changes: 11 additions & 0 deletions src/Networking/NexusMods.Networking.Downloaders/DownloadService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,17 @@ public Optional<Percent> GetTotalProgress()
return Optional<Percent>.Create(Percent.CreateClamped(total / tasks.Length));
}

/// <inheritdoc />
public async Task SetIsHidden(bool isHidden, IDownloadTask[] targets)
{
using var tx = _conn.BeginTransaction();
foreach (var downloadTask in targets)
{
downloadTask.SetIsHidden(isHidden, tx);
}
await tx.Commit();
}

public async ValueTask DisposeAsync()
{
if (_isDisposed)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System.Collections.ObjectModel;
using DynamicData;
using DynamicData.Kernel;
using NexusMods.Abstractions.Activities;
using NexusMods.Abstractions.NexusWebApi.Types;
using NexusMods.Networking.Downloaders.Tasks.State;
using NexusMods.Paths;

namespace NexusMods.Networking.Downloaders.Interfaces;
Expand Down Expand Up @@ -41,4 +41,9 @@ public interface IDownloadService
/// Gets the total progress of all downloads.
/// </summary>
Optional<Percent> GetTotalProgress();

/// <summary>
/// Sets the <see cref="CompletedDownloadState.Hidden"/> on the tasks if they are completed.
/// </summary>
Task SetIsHidden(bool isHidden, IDownloadTask[] targets);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.ComponentModel;
using NexusMods.Abstractions.Activities;
using NexusMods.MnemonicDB.Abstractions;
using NexusMods.Networking.Downloaders.Tasks.State;
Expand Down Expand Up @@ -57,6 +56,15 @@ public interface IDownloadTask
/// </summary>
Task Resume();

/// <summary>
/// Sets the <see cref="CompletedDownloadState.Hidden"/> on the task if it is completed.
/// </summary>
/// <param name="isHidden"> Value to set</param>
/// <param name="tx">Transaction to use, if none is passed a new transaction is created and committed.</param>
/// <remarks>If a transaction is passed, it is not committed, as it is assumed the caller will</remarks>
/// <returns></returns>
void SetIsHidden(bool isHidden, ITransaction tx);

/// <summary>
/// Reset (reload) the persistent state of the task from the database.
/// </summary>
Expand Down
3 changes: 2 additions & 1 deletion src/Networking/NexusMods.Networking.Downloaders/Services.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public static IServiceCollection AddDownloaders(this IServiceCollection services
.AddTransient<HttpDownloadTask>()
.AddAttributeCollection(typeof(DownloaderState))
.AddAttributeCollection(typeof(HttpDownloadState))
.AddAttributeCollection(typeof(NxmDownloadState));
.AddAttributeCollection(typeof(NxmDownloadState))
.AddAttributeCollection(typeof(CompletedDownloadState));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,15 @@ protected async Task SetStatus(DownloadTaskStatus status)
PersistentState = result.Remap(PersistentState);
}

protected async Task MarkComplete()
{
using var tx = Connection.BeginTransaction();
tx.Add(PersistentState.Id, DownloaderState.Status, (byte)DownloadTaskStatus.Completed);
tx.Add(PersistentState.Id, CompletedDownloadState.CompletedDateTime, DateTime.Now);
var result = await tx.Commit();
PersistentState = result.Remap(PersistentState);
}

[Reactive]
public DownloaderState.Model PersistentState { get; set; } = null!;

Expand Down Expand Up @@ -188,7 +197,7 @@ public async Task Resume()
await SetStatus(DownloadTaskStatus.Analyzing);
Logger.LogInformation("Finished download of {Name} starting analysis", PersistentState.FriendlyName);
await AnalyzeFile();
await SetStatus(DownloadTaskStatus.Completed);
await MarkComplete();
}

private async Task AnalyzeFile()
Expand Down Expand Up @@ -236,6 +245,13 @@ private void UpdateActivity()
Logger.LogError(ex, "Failed to update activity status");
}
}

/// <inheritdoc />
public void SetIsHidden(bool isHidden, ITransaction tx)
{
if (PersistentState.Status != DownloadTaskStatus.Completed) return;
tx.Add(PersistentState.Id, CompletedDownloadState.Hidden, isHidden);
}

/// <inheritdoc />
public void ResetState(IDb db)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using NexusMods.Abstractions.MnemonicDB.Attributes;
using NexusMods.MnemonicDB.Abstractions;

namespace NexusMods.Networking.Downloaders.Tasks.State;

/// <summary>
/// Additional state for a <see cref="DownloaderState"/> that is completed
/// </summary>
public static class CompletedDownloadState
{
private const string Namespace = "NexusMods.Networking.Downloaders.Tasks.DownloaderState";

/// <summary>
/// The timestamp the download was completed at
/// </summary>
public static readonly DateTimeAttribute CompletedDateTime= new(Namespace, nameof(CompletedDateTime));

/// <summary>
/// Whether the download is hidden (clear action) in the UI
/// </summary>
public static readonly BooleanAttribute Hidden = new(Namespace, nameof(Hidden));

/// <summary>
/// Model for reading and writing CompletedDownloadStates
/// </summary>
/// <param name="tx"></param>
public class Model(ITransaction tx) : DownloaderState.Model(tx)
{

/// <summary>
/// The timestamp the download was completed at
/// </summary>
public DateTime CompletedAt
{
get => CompletedDateTime.Get(this, default(DateTime));
set => CompletedDateTime.Add(this, value);
}

/// <summary>
/// Whether the download is hidden (clear action) in the UI
/// </summary>
public bool IsHidden
{
get => Hidden.Get(this, false);
set => Hidden.Add(this, value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public static string FormatStatus(DownloadTaskStatus state, long totalBytes, lon
DownloadTaskStatus.Installing => Language.DownloadStatusDesignViewModel_FormatStatus_Installing,
DownloadTaskStatus.Completed => Language.DownloadStatusDesignViewModel_FormatStatus_Complete,
DownloadTaskStatus.Analyzing => Language.DownloadStatusDesignViewModel_FormatStatus_Analyzing,
DownloadTaskStatus.Cancelled => Language.DownloadStatusDesignViewModel_FormatStatus_Cancelled,
_ => throw new ArgumentOutOfRangeException(nameof(state), state, null)
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
using System.Collections.ObjectModel;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using DynamicData;
using DynamicData.Kernel;
using Microsoft.Extensions.DependencyInjection;
using NexusMods.Abstractions.Diagnostics;
using NexusMods.Abstractions.Loadouts;
using NexusMods.App.UI.Controls.Navigation;
using NexusMods.App.UI.LeftMenu.Items;
using NexusMods.App.UI.Pages.Diagnostics;
using NexusMods.App.UI.Pages.LoadoutGrid;
using NexusMods.App.UI.Pages.ModLibrary;
using NexusMods.App.UI.Pages.MyGames;
using NexusMods.App.UI.Resources;
using NexusMods.App.UI.WorkspaceSystem;
using NexusMods.Icons;
Expand Down
51 changes: 22 additions & 29 deletions src/NexusMods.App.UI/Pages/Downloads/IInProgressViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
using System.Collections.ObjectModel;
using System.Windows.Input;
using System.Reactive;
using DynamicData;
using LiveChartsCore;
using LiveChartsCore.SkiaSharpView;
using NexusMods.App.UI.Controls.DataGrid;
using NexusMods.App.UI.Controls.DownloadGrid;
using NexusMods.App.UI.Pages.Downloads.ViewModels;
using NexusMods.App.UI.WorkspaceSystem;
using ReactiveUI;

namespace NexusMods.App.UI.Pages.Downloads;

public interface IInProgressViewModel : IPageViewModelInterface
{
/// <summary>
/// These tasks contain only current in-progress tasks; completed tasks are removed from this list.
/// Collection of in progress download tasks (downloading, paused, etc.)
/// </summary>
ReadOnlyObservableCollection<IDownloadTaskViewModel> Tasks { get; }
ReadOnlyObservableCollection<IDownloadTaskViewModel> InProgressTasks { get; }

/// <summary>
/// Collection of completed download tasks
/// </summary>
ReadOnlyObservableCollection<IDownloadTaskViewModel> CompletedTasks { get; }

ReadOnlyObservableCollection<IDataGridColumnFactory<DownloadColumn>> Columns { get; }

Expand Down Expand Up @@ -49,51 +55,38 @@ public interface IInProgressViewModel : IPageViewModelInterface
/// <summary>
/// The currently selected task.
/// </summary>
SourceList<IDownloadTaskViewModel> SelectedTasks { get; set;}
SourceList<IDownloadTaskViewModel> SelectedInProgressTasks { get; }


SourceList<IDownloadTaskViewModel> SelectedCompletedTasks { get; }

/// <summary>
/// Shows the cancel 'dialog' to the user.
/// </summary>
ICommand ShowCancelDialogCommand { get; set; }
ReactiveCommand<Unit,Unit> ShowCancelDialogCommand { get; }

/// <summary>
/// Suspends the current task.
/// </summary>
ICommand SuspendSelectedTasksCommand { get; }
ReactiveCommand<Unit,Unit> SuspendSelectedTasksCommand { get; }

/// <summary>
/// Resumes the current task.
/// </summary>
ICommand ResumeSelectedTasksCommand { get; }
ReactiveCommand<Unit,Unit> ResumeSelectedTasksCommand { get; }

/// <summary>
/// Suspends all the tasks.
/// </summary>
ICommand SuspendAllTasksCommand { get; }
ReactiveCommand<Unit,Unit> SuspendAllTasksCommand { get; }

/// <summary>
/// Resumes all the tasks.
/// </summary>
ICommand ResumeAllTasksCommand { get; }

/// <summary>
/// Shows the additional settings for the current task (there is nothing for now).
/// </summary>
ICommand ShowSettings { get; }

/// <summary>
/// Cancels all the passed tasks, without asking for confirmation.
/// </summary>
void CancelTasks(IEnumerable<IDownloadTaskViewModel> tasks);

/// <summary>
/// Suspends all the "Downloading" passed tasks.
/// </summary>
void SuspendTasks(IEnumerable<IDownloadTaskViewModel> tasks);

/// <summary>
/// Resumes all the "Paused" passed tasks.
/// </summary>
void ResumeTasks(IEnumerable<IDownloadTaskViewModel> tasks);
ReactiveCommand<Unit,Unit> ResumeAllTasksCommand { get; }

ReactiveCommand<Unit, Unit> HideSelectedCommand { get; }

ReactiveCommand<Unit, Unit> HideAllCommand { get; }

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class InProgressDesignViewModel : InProgressViewModel
{
public InProgressDesignViewModel()
{
// Note (Al12rs): We can't simply assign a new collection to the Tasks property,
// Note (Al12rs): We can't simply assign a new collection to the InProgressTasks property,
// because all the bindings are already subscribed to the old collection.
// It would be possible to unsubscribe from the old collection and subscribe to the new one,
// but that would make all the bindings code much more messy, with nested subscriptions.
Expand Down Expand Up @@ -58,6 +58,28 @@ public InProgressDesignViewModel()
DownloadedBytes = 0,
TaskId = EntityId.From(1027),
});

DesignTimeDownloadTasks.AddOrUpdate(new DownloadTaskDesignViewModel()
{
Name = "Fast Travel From Anywhere",
Game = "Chrono Fiddler",
Version = "13.3.7",
DownloadedBytes = 1000_000000,
SizeBytes = 1000_000000,
Status = DownloadTaskStatus.Completed,
TaskId = EntityId.From(1028),
});

DesignTimeDownloadTasks.AddOrUpdate(new DownloadTaskDesignViewModel()
{
Name = "Better Farmhouse Textures",
Game = "Valdur's Fate",
Version = "13.3.10",
DownloadedBytes = 300,
SizeBytes = 300,
Status = DownloadTaskStatus.Completed,
TaskId = EntityId.From(1029),
});
}

internal void AddDownload(DownloadTaskDesignViewModel vm) => DesignTimeDownloadTasks.AddOrUpdate(vm);
Expand Down
Loading

0 comments on commit 3742d53

Please sign in to comment.