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