/* 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); } } }