-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from dragonfruitnetwork/net8-mvvm
Upgrade to .NET 8 (and re-write some MVMM components)
- Loading branch information
Showing
16 changed files
with
387 additions
and
276 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
// Kaplan Copyright (c) DragonFruit Network <[email protected]> | ||
// Licensed under Apache-2. Refer to the LICENSE file for more info | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Windows.ApplicationModel; | ||
using Windows.Management.Deployment; | ||
using DragonFruit.Kaplan.ViewModels.Enums; | ||
|
||
namespace DragonFruit.Kaplan | ||
{ | ||
public class PackageRemover | ||
{ | ||
private readonly RemovalOptions _mode; | ||
private readonly PackageManager _manager; | ||
private readonly IReadOnlyList<Package> _packages; | ||
|
||
private Package _currentPackage; | ||
private DeploymentProgress _currentPackageRemovalProgress; | ||
|
||
private OperationState _state; | ||
private Task<int> _currentRemovalTask; | ||
|
||
public PackageRemover(PackageInstallationMode mode, PackageManager manager, IReadOnlyList<Package> packages) | ||
{ | ||
_manager = manager; | ||
_packages = packages; | ||
|
||
_mode = mode switch | ||
{ | ||
PackageInstallationMode.Machine => RemovalOptions.RemoveForAllUsers, | ||
PackageInstallationMode.User => RemovalOptions.None, | ||
|
||
_ => throw new ArgumentOutOfRangeException() | ||
}; | ||
} | ||
|
||
/// <summary> | ||
/// The total number of packages to be removed | ||
/// </summary> | ||
public int TotalPackages => _packages.Count; | ||
|
||
/// <summary> | ||
/// The index of the package currently being removed | ||
/// </summary> | ||
public int CurrentIndex { get; private set; } | ||
|
||
public OperationState State | ||
{ | ||
get => _state; | ||
private set | ||
{ | ||
if (_state == value) return; | ||
|
||
_state = value; | ||
StateChanged?.Invoke(this, value); | ||
} | ||
} | ||
|
||
public Package CurrentPackage | ||
{ | ||
get => _currentPackage; | ||
private set | ||
{ | ||
_currentPackage = value; | ||
CurrentPackageChanged?.Invoke(this, value); | ||
} | ||
} | ||
|
||
public DeploymentProgress CurrentPackageRemovalProgress | ||
{ | ||
get => _currentPackageRemovalProgress; | ||
private set | ||
{ | ||
_currentPackageRemovalProgress = value; | ||
CurrentPackageRemovalProgressChanged?.Invoke(this, value); | ||
} | ||
} | ||
|
||
public event EventHandler<OperationState> StateChanged; | ||
public event EventHandler<Package> CurrentPackageChanged; | ||
public event EventHandler<DeploymentProgress> CurrentPackageRemovalProgressChanged; | ||
|
||
/// <summary> | ||
/// Iterates through the provided packages, removing them from the system. | ||
/// If previously cancelled, will continue from the last package. | ||
/// </summary> | ||
public Task<int> RemovePackagesAsync(CancellationToken cancellation = default) | ||
{ | ||
// prevent multiple removal tasks from running at once | ||
if (_currentRemovalTask?.Status is TaskStatus.WaitingForActivation or TaskStatus.WaitingToRun or TaskStatus.Running or TaskStatus.Created) | ||
{ | ||
return _currentRemovalTask; | ||
} | ||
|
||
return _currentRemovalTask = RemovePackagesAsyncImpl(cancellation); | ||
} | ||
|
||
private async Task<int> RemovePackagesAsyncImpl(CancellationToken cancellation) | ||
{ | ||
var removed = 0; | ||
State = OperationState.Pending; | ||
|
||
for (int i = CurrentIndex; i < _packages.Count; i++) | ||
{ | ||
if (cancellation.IsCancellationRequested) | ||
{ | ||
State = OperationState.Canceled; | ||
break; | ||
} | ||
|
||
CurrentIndex = i; | ||
CurrentPackage = _packages[i]; | ||
CurrentPackageRemovalProgress = default; | ||
|
||
try | ||
{ | ||
State = OperationState.Running; | ||
#if !DRY_RUN | ||
var progress = new Progress<DeploymentProgress>(p => CurrentPackageRemovalProgress = p); | ||
await _manager.RemovePackageAsync(_packages[i].Id.FullName, _mode).AsTask(cancellation, progress).ConfigureAwait(false); | ||
#else | ||
// dummy removal progress | ||
for (uint j = 0; j < 50; j++) | ||
{ | ||
await Task.Delay(50, cancellation); | ||
CurrentPackageRemovalProgress = new DeploymentProgress(DeploymentProgressState.Processing, j * 2); | ||
} | ||
#endif | ||
removed++; | ||
} | ||
catch (OperationCanceledException) | ||
{ | ||
State = OperationState.Canceled; | ||
return removed; | ||
} | ||
catch | ||
{ | ||
State = OperationState.Errored; | ||
return removed; | ||
} | ||
} | ||
|
||
State = OperationState.Completed; | ||
return removed; | ||
} | ||
|
||
public enum OperationState | ||
{ | ||
Pending, | ||
Running, | ||
Errored, | ||
Completed, | ||
Canceled | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
// Kaplan Copyright (c) DragonFruit Network <[email protected]> | ||
// Licensed under Apache-2. Refer to the LICENSE file for more info | ||
|
||
using Avalonia; | ||
using Avalonia.Controls; | ||
using Avalonia.ReactiveUI; | ||
using FluentAvalonia.UI.Windowing; | ||
using ReactiveUI; | ||
|
||
#nullable enable | ||
|
||
namespace DragonFruit.Kaplan | ||
{ | ||
/// <summary> | ||
/// A ReactiveUI <see cref="Window"/> that implements the <see cref="IViewFor"/> interface and will | ||
/// activate your ViewModel automatically if the view model implements <see cref="IActivatableViewModel"/>. When | ||
/// the DataContext property changes, this class will update the ViewModel property with the new DataContext value, | ||
/// and vice versa. | ||
/// </summary> | ||
/// <typeparam name="TViewModel">ViewModel type.</typeparam> | ||
/// <remarks> | ||
/// This is a version of the ReactiveUI <see cref="ReactiveWindow{TViewModel}"/> class modified to support <see cref="AppWindow"/>. | ||
/// See https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.ReactiveUI/ReactiveWindow.cs for the original implementation. | ||
/// </remarks> | ||
public class ReactiveAppWindow<TViewModel> : AppWindow, IViewFor<TViewModel> where TViewModel : class | ||
{ | ||
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1002", Justification = "Generic avalonia property is expected here.")] | ||
public static readonly StyledProperty<TViewModel?> ViewModelProperty = AvaloniaProperty.Register<ReactiveWindow<TViewModel>, TViewModel?>(nameof(ViewModel)); | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="ReactiveWindow{TViewModel}"/> class. | ||
/// </summary> | ||
public ReactiveAppWindow() | ||
{ | ||
// This WhenActivated block calls ViewModel's WhenActivated | ||
// block if the ViewModel implements IActivatableViewModel. | ||
this.WhenActivated(disposables => { }); | ||
} | ||
|
||
/// <summary> | ||
/// The ViewModel. | ||
/// </summary> | ||
public TViewModel? ViewModel | ||
{ | ||
get => GetValue(ViewModelProperty); | ||
set => SetValue(ViewModelProperty, value); | ||
} | ||
|
||
object? IViewFor.ViewModel | ||
{ | ||
get => ViewModel; | ||
set => ViewModel = (TViewModel?)value; | ||
} | ||
|
||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) | ||
{ | ||
base.OnPropertyChanged(change); | ||
|
||
if (change.Property == DataContextProperty) | ||
{ | ||
if (ReferenceEquals(change.OldValue, ViewModel) | ||
&& change.NewValue is null or TViewModel) | ||
{ | ||
SetCurrentValue(ViewModelProperty, change.NewValue); | ||
} | ||
} | ||
else if (change.Property == ViewModelProperty) | ||
{ | ||
if (ReferenceEquals(change.OldValue, DataContext)) | ||
{ | ||
SetCurrentValue(DataContextProperty, change.NewValue); | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.