643 lines
20 KiB
C#
643 lines
20 KiB
C#
namespace VideoBrowser.Core
|
|
{
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.ComponentModel;
|
|
using System.Runtime.CompilerServices;
|
|
using VideoBrowser.Extensions;
|
|
|
|
/// <summary>
|
|
/// Defines the <see cref="Operation" />.
|
|
/// </summary>
|
|
public abstract class Operation : IDisposable, INotifyPropertyChanged
|
|
{
|
|
#region Constants
|
|
|
|
/// <summary>
|
|
/// The amount of time to wait for progress updates in milliseconds.
|
|
/// </summary>
|
|
protected const int ProgressDelay = 500;
|
|
|
|
protected const int ProgressMax = 100;
|
|
|
|
protected const int ProgressMin = 0;
|
|
|
|
#endregion Constants
|
|
|
|
#region Fields
|
|
|
|
private readonly string _progressText = string.Empty;
|
|
|
|
private readonly string _progressTextOverride = string.Empty;
|
|
|
|
/// <summary>
|
|
/// Store running operations that can be stopped automatically when closing application.
|
|
/// </summary>
|
|
public static List<Operation> Running = new List<Operation>();
|
|
|
|
private bool _disposed = false;
|
|
|
|
private long _duration;
|
|
|
|
private string _eta;
|
|
|
|
private long _fileSize;
|
|
|
|
private string _input;
|
|
|
|
private string _link;
|
|
|
|
private string _output;
|
|
|
|
private long _progress;
|
|
|
|
private int _progressPercentage = 0;
|
|
|
|
private bool _reportsProgress = false;
|
|
|
|
private string _speed;
|
|
|
|
private OperationStatus _status = OperationStatus.Queued;
|
|
|
|
private string _thumbnail;
|
|
|
|
private string _title;
|
|
|
|
private BackgroundWorker _worker;
|
|
|
|
#endregion Fields
|
|
|
|
#region Constructors
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Operation"/> class.
|
|
/// </summary>
|
|
protected Operation()
|
|
{
|
|
_worker = new BackgroundWorker()
|
|
{
|
|
WorkerReportsProgress = true,
|
|
WorkerSupportsCancellation = true
|
|
};
|
|
_worker.DoWork += Worker_DoWork;
|
|
_worker.ProgressChanged += Worker_ProgressChanged;
|
|
_worker.RunWorkerCompleted += Worker_Completed;
|
|
}
|
|
|
|
#endregion Constructors
|
|
|
|
#region Events
|
|
|
|
/// <summary>
|
|
/// Occurs when the operation is complete.
|
|
/// </summary>
|
|
public event OperationEventHandler Completed;
|
|
|
|
/// <summary>
|
|
/// Defines the ProgressChanged.
|
|
/// </summary>
|
|
public event ProgressChangedEventHandler ProgressChanged;
|
|
|
|
/// <summary>
|
|
/// Defines the PropertyChanged.
|
|
/// </summary>
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
|
|
/// <summary>
|
|
/// Defines the ReportsProgressChanged.
|
|
/// </summary>
|
|
public event EventHandler ReportsProgressChanged;
|
|
|
|
/// <summary>
|
|
/// Defines the Resumed.
|
|
/// </summary>
|
|
public event EventHandler Resumed;
|
|
|
|
/// <summary>
|
|
/// Defines the Started.
|
|
/// </summary>
|
|
public event EventHandler Started;
|
|
|
|
/// <summary>
|
|
/// Defines the StatusChanged.
|
|
/// </summary>
|
|
public event StatusChangedEventHandler StatusChanged;
|
|
|
|
#endregion Events
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether CancellationPending.
|
|
/// </summary>
|
|
public bool CancellationPending => _worker?.CancellationPending == true;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Duration.
|
|
/// </summary>
|
|
public long Duration { get => _duration; set => this.Set(this.PropertyChanged, ref _duration, value); }
|
|
|
|
/// <summary>
|
|
/// Gets the Errors
|
|
/// Gets a human readable list of errors caused by the operation..
|
|
/// </summary>
|
|
public ReadOnlyCollection<string> Errors => new ReadOnlyCollection<string>(ErrorsInternal);
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Errors1.
|
|
/// </summary>
|
|
public List<string> Errors1 { get => ErrorsInternal; set => ErrorsInternal = value; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the ETA.
|
|
/// </summary>
|
|
public string ETA { get => _eta; set => this.Set(this.PropertyChanged, ref _eta, value); }
|
|
|
|
/// <summary>
|
|
/// Gets the Exception.
|
|
/// </summary>
|
|
public Exception Exception { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the FileSize.
|
|
/// </summary>
|
|
public long FileSize
|
|
{
|
|
get => _fileSize;
|
|
set
|
|
{
|
|
_fileSize = value;
|
|
this.OnPropertyChanged();
|
|
this.OnPropertyChangedExplicit(nameof(ProgressText));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether HasStarted.
|
|
/// </summary>
|
|
public bool HasStarted { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Input
|
|
/// Gets the input file or download url..
|
|
/// </summary>
|
|
public string Input { get => _input; set => this.Set(this.PropertyChanged, ref _input, value); }
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether IsBusy.
|
|
/// </summary>
|
|
public bool IsBusy => _worker?.IsBusy == true;
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether IsCanceled.
|
|
/// </summary>
|
|
public bool IsCanceled => this.Status == OperationStatus.Canceled;
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether IsDone
|
|
/// Returns True if Operation is done, regardless of result..
|
|
/// </summary>
|
|
public bool IsDone => this.Status == OperationStatus.Canceled
|
|
|| this.Status == OperationStatus.Failed
|
|
|| this.Status == OperationStatus.Success;
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether IsPaused.
|
|
/// </summary>
|
|
public bool IsPaused => this.Status == OperationStatus.Paused;
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether IsQueued.
|
|
/// </summary>
|
|
public bool IsQueued => this.Status == OperationStatus.Queued;
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether IsSuccessful.
|
|
/// </summary>
|
|
public bool IsSuccessful => this.Status == OperationStatus.Success;
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether IsWorking.
|
|
/// </summary>
|
|
public bool IsWorking => this.Status == OperationStatus.Working;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Link.
|
|
/// </summary>
|
|
public string Link { get => _link; set => this.Set(this.PropertyChanged, ref _link, value); }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Output
|
|
/// Gets the output file..
|
|
/// </summary>
|
|
public string Output { get => _output; set => this.Set(this.PropertyChanged, ref _output, value); }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Progress.
|
|
/// </summary>
|
|
public long Progress
|
|
{
|
|
get => _progress;
|
|
set
|
|
{
|
|
_progress = value;
|
|
this.OnPropertyChanged();
|
|
this.OnPropertyChangedExplicit(nameof(ProgressText));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the ProgressPercentage
|
|
/// Gets the operation progress, as a double between 0-100..
|
|
/// </summary>
|
|
public int ProgressPercentage
|
|
{
|
|
get => _progressPercentage;
|
|
set
|
|
{
|
|
_progressPercentage = value;
|
|
this.OnPropertyChanged();
|
|
this.OnPropertyChangedExplicit(nameof(ProgressText));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the ProgressText.
|
|
/// </summary>
|
|
public string ProgressText { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether ReportsProgress.
|
|
/// </summary>
|
|
public bool ReportsProgress
|
|
{
|
|
get => _reportsProgress;
|
|
set
|
|
{
|
|
_reportsProgress = value;
|
|
this.OnReportsProgressChanged(EventArgs.Empty);
|
|
this.OnPropertyChanged();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Speed.
|
|
/// </summary>
|
|
public string Speed
|
|
{
|
|
get => _speed;
|
|
set
|
|
{
|
|
_speed = value;
|
|
this.OnPropertyChanged();
|
|
this.OnPropertyChangedExplicit(nameof(ProgressText));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Status
|
|
/// Gets the operation status..
|
|
/// </summary>
|
|
public OperationStatus Status
|
|
{
|
|
get => _status;
|
|
set
|
|
{
|
|
var oldStatus = _status;
|
|
|
|
_status = value;
|
|
|
|
this.OnStatusChanged(new StatusChangedEventArgs(this, value, oldStatus));
|
|
this.OnPropertyChanged();
|
|
|
|
// Send Changed notification to following properties
|
|
foreach (string property in new string[] {
|
|
nameof(IsCanceled),
|
|
nameof(IsDone),
|
|
nameof(IsPaused),
|
|
nameof(IsSuccessful),
|
|
nameof(IsWorking) })
|
|
{
|
|
this.OnPropertyChangedExplicit(property);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Thumbnail.
|
|
/// </summary>
|
|
public string Thumbnail { get => _thumbnail; set => this.Set(this.PropertyChanged, ref _thumbnail, value); }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Title.
|
|
/// </summary>
|
|
public string Title { get => _title; set => this.Set(this.PropertyChanged, ref _title, value); }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Operation's arguments..
|
|
/// </summary>
|
|
protected Dictionary<string, object> Arguments { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the ErrorsInternal
|
|
/// Gets or sets a editable list of errors..
|
|
/// </summary>
|
|
protected List<string> ErrorsInternal { get; set; } = new List<string>();
|
|
|
|
#endregion Properties
|
|
|
|
#region Methods
|
|
|
|
/// <summary>
|
|
/// Returns whether 'Open' method is supported and available at the moment.
|
|
/// </summary>
|
|
/// <returns>The <see cref="bool"/>.</returns>
|
|
public virtual bool CanOpen() => false;
|
|
|
|
/// <summary>
|
|
/// Returns whether 'Pause' method is supported and available at the moment.
|
|
/// </summary>
|
|
/// <returns>The <see cref="bool"/>.</returns>
|
|
public virtual bool CanPause() => false;
|
|
|
|
/// <summary>
|
|
/// Returns whether 'Resume' method is supported and available at the moment.
|
|
/// </summary>
|
|
/// <returns>The <see cref="bool"/>.</returns>
|
|
public virtual bool CanResume() => false;
|
|
|
|
/// <summary>
|
|
/// Returns whether 'Stop' method is supported and available at the moment.
|
|
/// </summary>
|
|
/// <returns>The <see cref="bool"/>.</returns>
|
|
public virtual bool CanStop() => false;
|
|
|
|
/// <summary>
|
|
/// The Dispose.
|
|
/// </summary>
|
|
public virtual void Dispose()
|
|
{
|
|
this.Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The OnPropertyChanged.
|
|
/// </summary>
|
|
/// <param name="propertyName">The propertyName<see cref="string"/>.</param>
|
|
public void OnPropertyChanged([CallerMemberName] string propertyName = "")
|
|
{
|
|
this.OnPropertyChangedExplicit(propertyName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The OnPropertyChangedExplicit.
|
|
/// </summary>
|
|
/// <param name="propertyName">The propertyName<see cref="string"/>.</param>
|
|
public void OnPropertyChangedExplicit(string propertyName)
|
|
{
|
|
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens the output file.
|
|
/// </summary>
|
|
/// <returns>.</returns>
|
|
public virtual bool Open()
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens the containing folder of the output file(s).
|
|
/// </summary>
|
|
/// <returns>The <see cref="bool"/>.</returns>
|
|
public virtual bool OpenContainingFolder()
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pauses the operation if supported & available.
|
|
/// </summary>
|
|
public virtual void Pause()
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Queues the operation. Used to pause and put the operation back to being queued.
|
|
/// </summary>
|
|
public virtual void Queue()
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resumes the operation if supported & available.
|
|
/// </summary>
|
|
public void Resume()
|
|
{
|
|
if (!this.HasStarted)
|
|
{
|
|
this.Start();
|
|
}
|
|
else
|
|
{
|
|
this.ResumeInternal();
|
|
}
|
|
|
|
this.Resumed?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resumes the operation if supported & available, but does not fire the Resumed event.
|
|
/// </summary>
|
|
public void ResumeQuiet()
|
|
{
|
|
this.ResumeInternal();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts the operation.
|
|
/// </summary>
|
|
public void Start()
|
|
{
|
|
_worker.RunWorkerAsync(this.Arguments);
|
|
|
|
Operation.Running.Add(this);
|
|
|
|
this.Status = OperationStatus.Working;
|
|
this.OnStarted(EventArgs.Empty);
|
|
|
|
this.HasStarted = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops the operation if supported & available.
|
|
/// </summary>
|
|
/// <returns>The <see cref="bool"/>.</returns>
|
|
public virtual bool Stop()
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// The CancelAsync.
|
|
/// </summary>
|
|
protected void CancelAsync() => _worker?.CancelAsync();
|
|
|
|
/// <summary>
|
|
/// The Complete.
|
|
/// </summary>
|
|
protected void Complete()
|
|
{
|
|
this.ReportsProgress = true;
|
|
|
|
if (this.Status == OperationStatus.Success)
|
|
{
|
|
this.ProgressPercentage = ProgressMax;
|
|
}
|
|
|
|
this.OnPropertyChangedExplicit(nameof(ProgressText));
|
|
OnCompleted(new OperationEventArgs(null, this.Status));
|
|
}
|
|
|
|
/// <summary>
|
|
/// The OnCompleted.
|
|
/// </summary>
|
|
/// <param name="e">The e<see cref="OperationEventArgs"/>.</param>
|
|
protected virtual void OnCompleted(OperationEventArgs e) => this.Completed?.Invoke(this, e);
|
|
|
|
/// <summary>
|
|
/// The OnProgressChanged.
|
|
/// </summary>
|
|
/// <param name="e">The e<see cref="ProgressChangedEventArgs"/>.</param>
|
|
protected virtual void OnProgressChanged(ProgressChangedEventArgs e) => this.ProgressChanged?.Invoke(this, e);
|
|
|
|
/// <summary>
|
|
/// The OnReportsProgressChanged.
|
|
/// </summary>
|
|
/// <param name="e">The e<see cref="EventArgs"/>.</param>
|
|
protected virtual void OnReportsProgressChanged(EventArgs e) => this.ReportsProgressChanged?.Invoke(this, e);
|
|
|
|
/// <summary>
|
|
/// The OnStarted.
|
|
/// </summary>
|
|
/// <param name="e">The e<see cref="EventArgs"/>.</param>
|
|
protected virtual void OnStarted(EventArgs e) => this.Started?.Invoke(this, e);
|
|
|
|
/// <summary>
|
|
/// The OnStatusChanged.
|
|
/// </summary>
|
|
/// <param name="e">The e<see cref="StatusChangedEventArgs"/>.</param>
|
|
protected virtual void OnStatusChanged(StatusChangedEventArgs e) => this.StatusChanged?.Invoke(this, e);
|
|
|
|
/// <summary>
|
|
/// The ReportProgress.
|
|
/// </summary>
|
|
/// <param name="percentProgress">The percentProgress<see cref="int"/>.</param>
|
|
/// <param name="userState">The userState<see cref="object"/>.</param>
|
|
protected void ReportProgress(int percentProgress, object userState) => _worker?.ReportProgress(percentProgress, userState);
|
|
|
|
/// <summary>
|
|
/// Resumes the operation if supported & available.
|
|
/// </summary>
|
|
protected virtual void ResumeInternal() => throw new NotSupportedException();
|
|
|
|
/// <summary>
|
|
/// The WorkerCompleted.
|
|
/// </summary>
|
|
/// <param name="e">The e<see cref="RunWorkerCompletedEventArgs"/>.</param>
|
|
protected abstract void WorkerCompleted(RunWorkerCompletedEventArgs e);
|
|
|
|
/// <summary>
|
|
/// The WorkerDoWork.
|
|
/// </summary>
|
|
/// <param name="e">The e<see cref="DoWorkEventArgs"/>.</param>
|
|
protected abstract void WorkerDoWork(DoWorkEventArgs e);
|
|
|
|
/// <summary>
|
|
/// The WorkerProgressChanged.
|
|
/// </summary>
|
|
/// <param name="e">The e<see cref="ProgressChangedEventArgs"/>.</param>
|
|
protected abstract void WorkerProgressChanged(ProgressChangedEventArgs e);
|
|
|
|
/// <summary>
|
|
/// The Dispose.
|
|
/// </summary>
|
|
/// <param name="disposing">The disposing<see cref="bool"/>.</param>
|
|
private void Dispose(bool disposing)
|
|
{
|
|
if (_disposed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (disposing)
|
|
{
|
|
_worker.DoWork -= Worker_DoWork;
|
|
_worker.ProgressChanged -= Worker_ProgressChanged;
|
|
_worker.RunWorkerCompleted -= Worker_Completed;
|
|
this.Completed = null;
|
|
|
|
if (_worker != null)
|
|
{
|
|
_worker.Dispose();
|
|
_worker = null;
|
|
}
|
|
}
|
|
|
|
_disposed = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Worker_Completed.
|
|
/// </summary>
|
|
/// <param name="sender">The sender<see cref="object"/>.</param>
|
|
/// <param name="e">The e<see cref="RunWorkerCompletedEventArgs"/>.</param>
|
|
private void Worker_Completed(object sender, RunWorkerCompletedEventArgs e)
|
|
{
|
|
Operation.Running.Remove(this);
|
|
|
|
if (e.Error != null)
|
|
{
|
|
this.Exception = e.Error;
|
|
this.Status = OperationStatus.Failed;
|
|
}
|
|
else
|
|
{
|
|
this.Status = (OperationStatus)e.Result;
|
|
}
|
|
|
|
this.Complete();
|
|
this.WorkerCompleted(e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Worker_DoWork.
|
|
/// </summary>
|
|
/// <param name="sender">The sender<see cref="object"/>.</param>
|
|
/// <param name="e">The e<see cref="DoWorkEventArgs"/>.</param>
|
|
private void Worker_DoWork(object sender, DoWorkEventArgs e) => WorkerDoWork(e);
|
|
|
|
/// <summary>
|
|
/// The Worker_ProgressChanged.
|
|
/// </summary>
|
|
/// <param name="sender">The sender<see cref="object"/>.</param>
|
|
/// <param name="e">The e<see cref="ProgressChangedEventArgs"/>.</param>
|
|
private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
|
|
{
|
|
if (e.ProgressPercentage >= 0 && e.ProgressPercentage <= 100)
|
|
{
|
|
this.ProgressPercentage = e.ProgressPercentage;
|
|
}
|
|
|
|
this.WorkerProgressChanged(e);
|
|
this.OnProgressChanged(e);
|
|
}
|
|
|
|
#endregion Methods
|
|
}
|
|
} |