/*
Copyright (C) 2014-2019 de4dot@gmail.com
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see .
*/
using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Input;
using System.Windows.Threading;
namespace dnSpy.Contracts.MVVM.Dialogs {
///
/// Progress
///
interface IProgress {
///
/// Sets total progress
///
/// Total progress
void SetTotalProgress(double progress);
///
/// Sets the description
///
/// Description
void SetDescription(string description);
///
/// Throws if it should be cancelled
///
void ThrowIfCancellationRequested();
///
/// Cancellation token
///
CancellationToken Token { get; }
}
///
/// Progress task
///
interface IProgressTask {
///
/// true if an indeterminate progress bar should be used
///
bool IsIndeterminate { get; }
///
/// Max progress
///
double ProgressMaximum { get; }
///
/// Minimum progress
///
double ProgressMinimum { get; }
///
/// Executes the code
///
/// Progress
void Execute(IProgress progress);
}
///
/// Progress VM
///
sealed class ProgressVM : ViewModelBase, IProgress {
///
/// Cancel command
///
public ICommand CancelCommand => new RelayCommand(a => Cancel(), a => CanCancel);
readonly Dispatcher dispatcher;
readonly IProgressTask task;
CancellationTokenSource? cancellationTokenSource;
///
/// Constructor
///
/// Dispatcher to use
/// Task
public ProgressVM(Dispatcher dispatcher, IProgressTask task) {
this.dispatcher = dispatcher;
this.task = task;
ProgressMinimum = task.ProgressMinimum;
ProgressMaximum = task.ProgressMaximum;
IsIndeterminate = task.IsIndeterminate;
cancellationTokenSource = new CancellationTokenSource();
Token = cancellationTokenSource.Token;
Start();
}
///
/// Raised when it has completed
///
public event EventHandler? OnCompleted;
///
/// true if it can be called
///
public bool CanCancel => !cancelling;
bool cancelling;
///
/// Cancels the task
///
public void Cancel() {
if (cancelling)
return;
cancelling = true;
cancellationTokenSource?.Cancel();
}
///
/// true if an indeterminate progress bar should be used
///
public bool IsIndeterminate { get; }
///
/// Minimum progress
///
public double ProgressMinimum { get; }
///
/// Max progress
///
public double ProgressMaximum { get; }
///
/// Gets/sets the total progress
///
public double TotalProgress {
get => totalProgress;
set {
if (totalProgress != value) {
totalProgress = value;
OnPropertyChanged(nameof(TotalProgress));
}
}
}
double totalProgress;
///
/// Gets/sets whether it was cancelled
///
public bool WasCanceled {
get => wasCanceled;
set {
if (wasCanceled != value) {
wasCanceled = value;
OnPropertyChanged(nameof(WasCanceled));
}
}
}
bool wasCanceled;
///
/// Gets/sets has-completed
///
public bool HasCompleted {
get => hasCompleted;
set {
if (hasCompleted != value) {
hasCompleted = value;
OnPropertyChanged(nameof(HasCompleted));
}
}
}
bool hasCompleted;
///
/// Gets/sets current description
///
public string? CurrentItemDescription {
get => currentItemDescription;
set {
if (currentItemDescription != value) {
currentItemDescription = value;
OnPropertyChanged(nameof(CurrentItemDescription));
}
}
}
string? currentItemDescription;
///
/// true if there was an error
///
public bool WasError => ErrorMessage is not null;
///
/// Gets the error message or null if no error
///
public string? ErrorMessage { get; private set; }
class MyAction {
public readonly Action Action;
public MyAction(Action action) => Action = action;
}
sealed class UpdateProgressAction : MyAction {
public UpdateProgressAction(Action action)
: base(action) {
}
}
sealed class SetDescriptionAction : MyAction {
public SetDescriptionAction(Action action)
: base(action) {
}
}
void QueueAction(MyAction action, bool unique) {
bool start;
lock (lockObj) {
if (unique) {
for (int i = 0; i < actions.Count; i++) {
if (actions[i].GetType() == action.GetType()) {
actions.RemoveAt(i);
break;
}
}
}
actions.Add(action);
start = actions.Count == 1;
}
if (start)
dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(EmptyQueue));
}
object lockObj = new object();
List actions = new List();
void EmptyQueue() {
MyAction[] ary;
lock (lockObj) {
ary = actions.ToArray();
actions.Clear();
}
foreach (var a in ary)
a.Action();
}
void IProgress.SetTotalProgress(double progress) =>
QueueAction(new UpdateProgressAction(() => TotalProgress = progress), true);
void IProgress.SetDescription(string desc) =>
QueueAction(new SetDescriptionAction(() => CurrentItemDescription = desc), true);
///
/// Gets the cancellation token
///
public CancellationToken Token { get; }
void IProgress.ThrowIfCancellationRequested() => Token.ThrowIfCancellationRequested();
void OnTaskCompleted() {
cancellationTokenSource?.Dispose();
cancellationTokenSource = null;
HasCompleted = true;
OnCompleted?.Invoke(this, EventArgs.Empty);
}
void Start() => new Thread(ThreadProc).Start();
void ThreadProc() {
try {
task.Execute(this);
}
catch (OperationCanceledException) {
QueueAction(new MyAction(() => WasCanceled = true), false);
}
catch (Exception ex) {
ErrorMessage = ex.Message;
}
QueueAction(new MyAction(OnTaskCompleted), false);
}
}
}