namespace VideoBrowser.Models { using System; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using VideoBrowser.Common; using VideoBrowser.Controls.CefSharpBrowser.Models; using VideoBrowser.Core; using VideoBrowser.Extensions; using VideoBrowser.Helpers; /// /// Defines the /// The Download Queue Items. /// public class OperationModel : DownloadItemModel, IDisposable { #region Fields private string _duration; #endregion Fields #region Constructors /// /// Initializes a new instance of the class. /// /// The operation. public OperationModel(Operation operation) { this.Operation = operation; this.Title = this.Operation.Title; this.Url = this.Operation.Link; this.Duration = FormatString.FormatVideoLength(this.Operation.Duration); this.FileSize = FormatString.FormatFileSize(this.Operation.FileSize); this.OutputPath = this.Operation.Output; this.Thumbnail = this.Operation.Thumbnail; this.CancelDownloadCommand = new RelayCommand((o) => this.CancelDownloadAction?.Invoke(this), nameof(this.CancelDownloadCommand), (o) => this.Operation.CanStop()); this.PauseDownloadCommand = new RelayCommand((o) => this.PauseDownloadAction?.Invoke(this), nameof(this.PauseDownloadCommand), (o) => this.Operation.CanPause() || this.Operation.CanResume()); this.Operation.Completed += OnOperation_Completed; this.Operation.ProgressChanged += OnOperation_ProgressChanged; this.Operation.PropertyChanged += OnOperation_PropertyChanged; this.Operation.ReportsProgressChanged += OnOperation_ReportsProgressChanged; this.Operation.Started += OnOperation_Started; this.Operation.StatusChanged += OnOperation_StatusChanged; // Set Status text, so it's not empty until a StatusChanged event is fired this.OnOperation_StatusChanged(this, EventArgs.Empty); } #endregion Constructors #region Events /// /// Defines the OperationComplete. /// public event OperationEventHandler OperationComplete; #endregion Events #region Properties /// /// Gets the Duration. /// public string Duration { get => this._duration; private set => this.Set(this.PropertyChangedHandler, ref this._duration, value); } /// /// Gets or sets the Operation /// Gets or sets a value indicating whether IsQueuedControlsVisible. /// /// /// Gets the Operation... /// public Operation Operation { get; protected set; } /// /// Gets the ProgressText. /// public string ProgressText { get { string status = string.Empty; switch (this.Operation.Status) { case OperationStatus.Working: status = $"{this.Operation.ProgressPercentage}%"; if (!string.IsNullOrEmpty(this.Status)) status += $" ({this.Status})"; break; default: status = this.Status; break; } return status; } } /// /// Gets the Stopwatch. /// public Stopwatch Stopwatch { get; private set; } /// /// Gets or sets the CancelDownloadAction. /// internal Action CancelDownloadAction { get; set; } /// /// Gets or sets the PauseDownloadAction. /// internal Action PauseDownloadAction { get; set; } /// /// Gets the ProgressMaximum. /// private static int ProgressMaximum { get; } = 100; /// /// Gets the ProgressMinimum. /// private static int ProgressMinimum { get; } = 0; #endregion Properties #region Methods /// /// The Dispose. /// /// The disposing. protected override void Dispose(bool disposing) { base.Dispose(disposing); if (this.Operation.CanStop()) { this.Operation.Stop(); } this.Operation.Dispose(); this.CancelDownloadAction = null; this.PauseDownloadAction = null; } /// /// The OnOperation_Completed. /// /// The sender. /// The e. private void OnOperation_Completed(object sender, OperationEventArgs e) { this.Stopwatch?.Stop(); this.Stopwatch = null; if (File.Exists(this.OutputPath)) { this.FileSize = string.Format(new ByteFormatProvider(), "{0:fs}", this.OutputPath); } else if (Directory.Exists(this.OutputPath)) { /* Get total file size of all affected files * * Directory can contain unrelated files, so make use of List properties * from Operation that contains the affected files only. */ string[] fileList = null; ////if (this.Operation is BatchOperation) //// fileList = (this.Operation as BatchOperation).DownloadedFiles.ToArray(); ////else if (this.Operation is ConvertOperation) //// fileList = (this.Operation as ConvertOperation).ProcessedFiles.ToArray(); ////else if (this.Operation is PlaylistOperation) //// fileList = (this.Operation as PlaylistOperation).DownloadedFiles.ToArray(); ////else //// throw new Exception("Couldn't get affected file list from operation " + this.Operation.GetType().Name); long fileSize = fileList.Sum(f => FileHelper.GetFileSize(f)); this.FileSize = FormatString.FormatFileSize(fileSize); } this.Progress = ProgressMaximum; this.OperationComplete?.Invoke(this, e); } /// /// The OnOperation_ProgressChanged. /// /// The sender. /// The e. private void OnOperation_ProgressChanged(object sender, ProgressChangedEventArgs e) { this.Progress = Math.Min(ProgressMaximum, Math.Max(ProgressMinimum, e.ProgressPercentage)); if (!string.IsNullOrEmpty(this.Operation.ProgressText)) { this.Status = this.Operation.ProgressText; } else { if (this.Wait()) { return; } this.Stopwatch?.Restart(); this.Status = this.Operation.Speed + this.Operation.ETA; } } /// /// The OnOperation_PropertyChanged. /// /// The sender. /// The e. private void OnOperation_PropertyChanged(object sender, PropertyChangedEventArgs e) { switch (e.PropertyName) { case nameof(Operation.Duration): this.Duration = FormatString.FormatVideoLength(this.Operation.Duration); break; case nameof(Operation.FileSize): this.FileSize = FormatString.FormatFileSize(this.Operation.FileSize); break; case nameof(Operation.Input): this.Url = this.Operation.Input; break; case nameof(Operation.Title): this.Title = this.Operation.Title; break; case nameof(Operation.Thumbnail): this.Thumbnail = this.Operation.Thumbnail; break; } } /// /// The OnOperation_ReportsProgressChanged. /// /// The sender. /// The e. private void OnOperation_ReportsProgressChanged(object sender, EventArgs e) { } /// /// The OnOperation_Started. /// /// The sender. /// The e. private void OnOperation_Started(object sender, EventArgs e) { this.Stopwatch = new Stopwatch(); this.Stopwatch.Start(); } /// /// The OnOperation_StatusChanged. /// /// The sender. /// The e. private void OnOperation_StatusChanged(object sender, EventArgs e) { switch (this.Operation.Status) { case OperationStatus.Success: this.Status = "Completed"; this.IsQueuedControlsVisible = false; break; case OperationStatus.Canceled: case OperationStatus.Failed: case OperationStatus.Paused: case OperationStatus.Queued: this.Status = this.Operation.Status.ToString(); break; case OperationStatus.Working: if (!string.IsNullOrEmpty(this.Operation.ProgressText)) { this.Status = this.Operation.ProgressText; } break; } } /// /// The Wait. /// /// The . private bool Wait() { // Limit the progress update to avoid flickering. if (this.Stopwatch == null || !this.Stopwatch.IsRunning) { return false; } return this.Stopwatch.ElapsedMilliseconds < AppEnvironment.ProgressUpdateDelay; } #endregion Methods } }