Skip to content

Commit 3742d53

Browse files
authored
Merge pull request #1397 from Nexus-Mods/feat/888-completed-downloads
Completed downloads section on Downloads page
2 parents c2a7369 + 8b5f27d commit 3742d53

File tree

21 files changed

+1484
-513
lines changed

21 files changed

+1484
-513
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using NexusMods.MnemonicDB.Abstractions.Attributes;
2+
using NexusMods.MnemonicDB.Abstractions.ElementComparers;
3+
4+
namespace NexusMods.Abstractions.MnemonicDB.Attributes;
5+
6+
/// <summary>
7+
/// A MneumonicDB attribute for a DateTime value
8+
/// </summary>
9+
/// <remarks>
10+
/// Depending on use case, consider using transaction timestamps instead of a dedicated DateTimeAttribute.
11+
/// </remarks>
12+
public class DateTimeAttribute(string ns, string name) : ScalarAttribute<DateTime, long>(ValueTags.Int64, ns, name)
13+
{
14+
/// <inheritdoc />
15+
protected override long ToLowLevel(DateTime value) => value.ToBinary();
16+
17+
/// <inheritdoc />
18+
protected override DateTime FromLowLevel(long value, ValueTags tags) => DateTime.FromBinary(value);
19+
}

src/Networking/NexusMods.Networking.Downloaders/DownloadService.cs

+11
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,17 @@ public Optional<Percent> GetTotalProgress()
159159
return Optional<Percent>.Create(Percent.CreateClamped(total / tasks.Length));
160160
}
161161

162+
/// <inheritdoc />
163+
public async Task SetIsHidden(bool isHidden, IDownloadTask[] targets)
164+
{
165+
using var tx = _conn.BeginTransaction();
166+
foreach (var downloadTask in targets)
167+
{
168+
downloadTask.SetIsHidden(isHidden, tx);
169+
}
170+
await tx.Commit();
171+
}
172+
162173
public async ValueTask DisposeAsync()
163174
{
164175
if (_isDisposed)

src/Networking/NexusMods.Networking.Downloaders/Interfaces/IDownloadService.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
using System.Collections.ObjectModel;
2-
using DynamicData;
32
using DynamicData.Kernel;
43
using NexusMods.Abstractions.Activities;
54
using NexusMods.Abstractions.NexusWebApi.Types;
5+
using NexusMods.Networking.Downloaders.Tasks.State;
66
using NexusMods.Paths;
77

88
namespace NexusMods.Networking.Downloaders.Interfaces;
@@ -41,4 +41,9 @@ public interface IDownloadService
4141
/// Gets the total progress of all downloads.
4242
/// </summary>
4343
Optional<Percent> GetTotalProgress();
44+
45+
/// <summary>
46+
/// Sets the <see cref="CompletedDownloadState.Hidden"/> on the tasks if they are completed.
47+
/// </summary>
48+
Task SetIsHidden(bool isHidden, IDownloadTask[] targets);
4449
}

src/Networking/NexusMods.Networking.Downloaders/Interfaces/IDownloadTask.cs

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.ComponentModel;
21
using NexusMods.Abstractions.Activities;
32
using NexusMods.MnemonicDB.Abstractions;
43
using NexusMods.Networking.Downloaders.Tasks.State;
@@ -57,6 +56,15 @@ public interface IDownloadTask
5756
/// </summary>
5857
Task Resume();
5958

59+
/// <summary>
60+
/// Sets the <see cref="CompletedDownloadState.Hidden"/> on the task if it is completed.
61+
/// </summary>
62+
/// <param name="isHidden"> Value to set</param>
63+
/// <param name="tx">Transaction to use, if none is passed a new transaction is created and committed.</param>
64+
/// <remarks>If a transaction is passed, it is not committed, as it is assumed the caller will</remarks>
65+
/// <returns></returns>
66+
void SetIsHidden(bool isHidden, ITransaction tx);
67+
6068
/// <summary>
6169
/// Reset (reload) the persistent state of the task from the database.
6270
/// </summary>

src/Networking/NexusMods.Networking.Downloaders/Services.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public static IServiceCollection AddDownloaders(this IServiceCollection services
2323
.AddTransient<HttpDownloadTask>()
2424
.AddAttributeCollection(typeof(DownloaderState))
2525
.AddAttributeCollection(typeof(HttpDownloadState))
26-
.AddAttributeCollection(typeof(NxmDownloadState));
26+
.AddAttributeCollection(typeof(NxmDownloadState))
27+
.AddAttributeCollection(typeof(CompletedDownloadState));
2728
}
2829
}

src/Networking/NexusMods.Networking.Downloaders/Tasks/ADownloadTask.cs

+17-1
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,15 @@ protected async Task SetStatus(DownloadTaskStatus status)
133133
PersistentState = result.Remap(PersistentState);
134134
}
135135

136+
protected async Task MarkComplete()
137+
{
138+
using var tx = Connection.BeginTransaction();
139+
tx.Add(PersistentState.Id, DownloaderState.Status, (byte)DownloadTaskStatus.Completed);
140+
tx.Add(PersistentState.Id, CompletedDownloadState.CompletedDateTime, DateTime.Now);
141+
var result = await tx.Commit();
142+
PersistentState = result.Remap(PersistentState);
143+
}
144+
136145
[Reactive]
137146
public DownloaderState.Model PersistentState { get; set; } = null!;
138147

@@ -188,7 +197,7 @@ public async Task Resume()
188197
await SetStatus(DownloadTaskStatus.Analyzing);
189198
Logger.LogInformation("Finished download of {Name} starting analysis", PersistentState.FriendlyName);
190199
await AnalyzeFile();
191-
await SetStatus(DownloadTaskStatus.Completed);
200+
await MarkComplete();
192201
}
193202

194203
private async Task AnalyzeFile()
@@ -236,6 +245,13 @@ private void UpdateActivity()
236245
Logger.LogError(ex, "Failed to update activity status");
237246
}
238247
}
248+
249+
/// <inheritdoc />
250+
public void SetIsHidden(bool isHidden, ITransaction tx)
251+
{
252+
if (PersistentState.Status != DownloadTaskStatus.Completed) return;
253+
tx.Add(PersistentState.Id, CompletedDownloadState.Hidden, isHidden);
254+
}
239255

240256
/// <inheritdoc />
241257
public void ResetState(IDb db)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using NexusMods.Abstractions.MnemonicDB.Attributes;
2+
using NexusMods.MnemonicDB.Abstractions;
3+
4+
namespace NexusMods.Networking.Downloaders.Tasks.State;
5+
6+
/// <summary>
7+
/// Additional state for a <see cref="DownloaderState"/> that is completed
8+
/// </summary>
9+
public static class CompletedDownloadState
10+
{
11+
private const string Namespace = "NexusMods.Networking.Downloaders.Tasks.DownloaderState";
12+
13+
/// <summary>
14+
/// The timestamp the download was completed at
15+
/// </summary>
16+
public static readonly DateTimeAttribute CompletedDateTime= new(Namespace, nameof(CompletedDateTime));
17+
18+
/// <summary>
19+
/// Whether the download is hidden (clear action) in the UI
20+
/// </summary>
21+
public static readonly BooleanAttribute Hidden = new(Namespace, nameof(Hidden));
22+
23+
/// <summary>
24+
/// Model for reading and writing CompletedDownloadStates
25+
/// </summary>
26+
/// <param name="tx"></param>
27+
public class Model(ITransaction tx) : DownloaderState.Model(tx)
28+
{
29+
30+
/// <summary>
31+
/// The timestamp the download was completed at
32+
/// </summary>
33+
public DateTime CompletedAt
34+
{
35+
get => CompletedDateTime.Get(this, default(DateTime));
36+
set => CompletedDateTime.Add(this, value);
37+
}
38+
39+
/// <summary>
40+
/// Whether the download is hidden (clear action) in the UI
41+
/// </summary>
42+
public bool IsHidden
43+
{
44+
get => Hidden.Get(this, false);
45+
set => Hidden.Add(this, value);
46+
}
47+
}
48+
}

src/NexusMods.App.UI/Controls/DownloadGrid/Columns/DownloadStatus/DownloadStatusDesignViewModel.cs

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public static string FormatStatus(DownloadTaskStatus state, long totalBytes, lon
7474
DownloadTaskStatus.Installing => Language.DownloadStatusDesignViewModel_FormatStatus_Installing,
7575
DownloadTaskStatus.Completed => Language.DownloadStatusDesignViewModel_FormatStatus_Complete,
7676
DownloadTaskStatus.Analyzing => Language.DownloadStatusDesignViewModel_FormatStatus_Analyzing,
77+
DownloadTaskStatus.Cancelled => Language.DownloadStatusDesignViewModel_FormatStatus_Cancelled,
7778
_ => throw new ArgumentOutOfRangeException(nameof(state), state, null)
7879
};
7980

src/NexusMods.App.UI/LeftMenu/Loadout/LoadoutLeftMenuViewModel.cs

-4
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
using System.Collections.ObjectModel;
22
using System.Reactive.Disposables;
3-
using System.Reactive.Linq;
4-
using DynamicData;
53
using DynamicData.Kernel;
64
using Microsoft.Extensions.DependencyInjection;
75
using NexusMods.Abstractions.Diagnostics;
8-
using NexusMods.Abstractions.Loadouts;
96
using NexusMods.App.UI.Controls.Navigation;
107
using NexusMods.App.UI.LeftMenu.Items;
118
using NexusMods.App.UI.Pages.Diagnostics;
129
using NexusMods.App.UI.Pages.LoadoutGrid;
1310
using NexusMods.App.UI.Pages.ModLibrary;
14-
using NexusMods.App.UI.Pages.MyGames;
1511
using NexusMods.App.UI.Resources;
1612
using NexusMods.App.UI.WorkspaceSystem;
1713
using NexusMods.Icons;
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
11
using System.Collections.ObjectModel;
2-
using System.Windows.Input;
2+
using System.Reactive;
33
using DynamicData;
44
using LiveChartsCore;
55
using LiveChartsCore.SkiaSharpView;
66
using NexusMods.App.UI.Controls.DataGrid;
77
using NexusMods.App.UI.Controls.DownloadGrid;
88
using NexusMods.App.UI.Pages.Downloads.ViewModels;
99
using NexusMods.App.UI.WorkspaceSystem;
10+
using ReactiveUI;
1011

1112
namespace NexusMods.App.UI.Pages.Downloads;
1213

1314
public interface IInProgressViewModel : IPageViewModelInterface
1415
{
1516
/// <summary>
16-
/// These tasks contain only current in-progress tasks; completed tasks are removed from this list.
17+
/// Collection of in progress download tasks (downloading, paused, etc.)
1718
/// </summary>
18-
ReadOnlyObservableCollection<IDownloadTaskViewModel> Tasks { get; }
19+
ReadOnlyObservableCollection<IDownloadTaskViewModel> InProgressTasks { get; }
20+
21+
/// <summary>
22+
/// Collection of completed download tasks
23+
/// </summary>
24+
ReadOnlyObservableCollection<IDownloadTaskViewModel> CompletedTasks { get; }
1925

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

@@ -49,51 +55,38 @@ public interface IInProgressViewModel : IPageViewModelInterface
4955
/// <summary>
5056
/// The currently selected task.
5157
/// </summary>
52-
SourceList<IDownloadTaskViewModel> SelectedTasks { get; set;}
58+
SourceList<IDownloadTaskViewModel> SelectedInProgressTasks { get; }
59+
60+
61+
SourceList<IDownloadTaskViewModel> SelectedCompletedTasks { get; }
5362

5463
/// <summary>
5564
/// Shows the cancel 'dialog' to the user.
5665
/// </summary>
57-
ICommand ShowCancelDialogCommand { get; set; }
66+
ReactiveCommand<Unit,Unit> ShowCancelDialogCommand { get; }
5867

5968
/// <summary>
6069
/// Suspends the current task.
6170
/// </summary>
62-
ICommand SuspendSelectedTasksCommand { get; }
71+
ReactiveCommand<Unit,Unit> SuspendSelectedTasksCommand { get; }
6372

6473
/// <summary>
6574
/// Resumes the current task.
6675
/// </summary>
67-
ICommand ResumeSelectedTasksCommand { get; }
76+
ReactiveCommand<Unit,Unit> ResumeSelectedTasksCommand { get; }
6877

6978
/// <summary>
7079
/// Suspends all the tasks.
7180
/// </summary>
72-
ICommand SuspendAllTasksCommand { get; }
81+
ReactiveCommand<Unit,Unit> SuspendAllTasksCommand { get; }
7382

7483
/// <summary>
7584
/// Resumes all the tasks.
7685
/// </summary>
77-
ICommand ResumeAllTasksCommand { get; }
78-
79-
/// <summary>
80-
/// Shows the additional settings for the current task (there is nothing for now).
81-
/// </summary>
82-
ICommand ShowSettings { get; }
83-
84-
/// <summary>
85-
/// Cancels all the passed tasks, without asking for confirmation.
86-
/// </summary>
87-
void CancelTasks(IEnumerable<IDownloadTaskViewModel> tasks);
88-
89-
/// <summary>
90-
/// Suspends all the "Downloading" passed tasks.
91-
/// </summary>
92-
void SuspendTasks(IEnumerable<IDownloadTaskViewModel> tasks);
93-
94-
/// <summary>
95-
/// Resumes all the "Paused" passed tasks.
96-
/// </summary>
97-
void ResumeTasks(IEnumerable<IDownloadTaskViewModel> tasks);
86+
ReactiveCommand<Unit,Unit> ResumeAllTasksCommand { get; }
87+
88+
ReactiveCommand<Unit, Unit> HideSelectedCommand { get; }
89+
90+
ReactiveCommand<Unit, Unit> HideAllCommand { get; }
9891

9992
}

src/NexusMods.App.UI/Pages/Downloads/InProgressDesignViewModel.cs

+23-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public class InProgressDesignViewModel : InProgressViewModel
99
{
1010
public InProgressDesignViewModel()
1111
{
12-
// Note (Al12rs): We can't simply assign a new collection to the Tasks property,
12+
// Note (Al12rs): We can't simply assign a new collection to the InProgressTasks property,
1313
// because all the bindings are already subscribed to the old collection.
1414
// It would be possible to unsubscribe from the old collection and subscribe to the new one,
1515
// but that would make all the bindings code much more messy, with nested subscriptions.
@@ -58,6 +58,28 @@ public InProgressDesignViewModel()
5858
DownloadedBytes = 0,
5959
TaskId = EntityId.From(1027),
6060
});
61+
62+
DesignTimeDownloadTasks.AddOrUpdate(new DownloadTaskDesignViewModel()
63+
{
64+
Name = "Fast Travel From Anywhere",
65+
Game = "Chrono Fiddler",
66+
Version = "13.3.7",
67+
DownloadedBytes = 1000_000000,
68+
SizeBytes = 1000_000000,
69+
Status = DownloadTaskStatus.Completed,
70+
TaskId = EntityId.From(1028),
71+
});
72+
73+
DesignTimeDownloadTasks.AddOrUpdate(new DownloadTaskDesignViewModel()
74+
{
75+
Name = "Better Farmhouse Textures",
76+
Game = "Valdur's Fate",
77+
Version = "13.3.10",
78+
DownloadedBytes = 300,
79+
SizeBytes = 300,
80+
Status = DownloadTaskStatus.Completed,
81+
TaskId = EntityId.From(1029),
82+
});
6183
}
6284

6385
internal void AddDownload(DownloadTaskDesignViewModel vm) => DesignTimeDownloadTasks.AddOrUpdate(vm);

0 commit comments

Comments
 (0)