namespace VideoBrowser.Core { using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Text.RegularExpressions; using System.Threading; using VideoBrowser.Common; using VideoBrowser.Helpers; /// /// Defines the /// public class DownloadOperation : Operation { #region Constants private const string RegexAddToFilename = @"^(\w:.*\\.*)(\..*)$"; #endregion Constants #region Fields private readonly bool _combine; private bool _downloadSuccessful; private bool _processing; private FileDownloader downloader; #endregion Fields #region Constructors /// /// Initializes a new instance of the class. /// /// The format /// The output public DownloadOperation(VideoFormat format, string output) : this(format) { this.Input = format.DownloadUrl; this.Output = output; this.Title = Path.GetFileName(this.Output); downloader.Files.Add(new FileDownload(this.Output, this.Input)); } /// /// Initializes a new instance of the class. /// /// The video /// The audio /// The output public DownloadOperation(VideoFormat video, VideoFormat audio, string output) : this(video) { _combine = true; this.Input = $"{audio.DownloadUrl}|{video.DownloadUrl}"; this.Output = output; this.FileSize += audio.FileSize; this.Title = Path.GetFileName(this.Output); this.Formats.Add(audio); var regex = new Regex(RegexAddToFilename); downloader.Files.Add(new FileDownload(regex.Replace(this.Output, "$1_audio$2"), audio.DownloadUrl, true)); downloader.Files.Add(new FileDownload(regex.Replace(this.Output, "$1_video$2"), video.DownloadUrl, true)); // Delete _audio and _video files in case they exists from a previous attempt FileHelper.DeleteFiles(downloader.Files[0].Path, downloader.Files[1].Path); } /// /// Prevents a default instance of the class from being created. /// private DownloadOperation() { downloader = new FileDownloader(); // Attach events downloader.Canceled += downloader_Canceled; downloader.Completed += downloader_Completed; downloader.FileDownloadFailed += downloader_FileDownloadFailed; downloader.CalculatedTotalFileSize += downloader_CalculatedTotalFileSize; downloader.ProgressChanged += downloader_ProgressChanged; } /// /// Prevents a default instance of the class from being created. /// /// The format private DownloadOperation(VideoFormat format) : this() { this.ReportsProgress = true; this.Duration = format.VideoInfo.Duration; this.FileSize = format.FileSize; this.Link = format.VideoInfo.Url; this.Thumbnail = format.VideoInfo.ThumbnailUrl; this.Formats.Add(format); this.Video = format.VideoInfo; } #endregion Constructors #region Properties /// /// Gets the Formats /// public List Formats { get; } = new List(); /// /// Gets the Video /// public VideoInfo Video { get; private set; } #endregion Properties #region Methods /// /// The CanOpen /// /// The public override bool CanOpen() => this.IsSuccessful; /// /// The CanPause /// /// The public override bool CanPause() { // Only downloader can pause. return downloader?.CanPause == true && this.IsWorking; } /// /// The CanResume /// /// The public override bool CanResume() { // Only downloader can resume. return downloader?.CanResume == true && (this.IsPaused || this.IsQueued); } /// /// The CanStop /// /// The public override bool CanStop() => this.IsPaused || this.IsWorking || this.IsQueued; /// /// The Dispose /// public override void Dispose() { base.Dispose(); downloader?.Dispose(); downloader = null; } /// /// The Open /// /// The public override bool Open() { try { Controls.CefSharpBrowser.Helpers.ProcessHelper.Start(this.Output); } catch { return false; } return true; } /// /// The OpenContainingFolder /// /// The public override bool OpenContainingFolder() { try { Controls.CefSharpBrowser.Helpers.ProcessHelper.OpenContainedFolder(this.Output); } catch { return false; } return true; } /// /// The Pause /// public override void Pause() { downloader.Pause(); this.Status = OperationStatus.Paused; } /// /// The Queue /// public override void Queue() { downloader.Pause(); this.Status = OperationStatus.Queued; } /// /// The Stop /// /// The public override bool Stop() { // Stop downloader if still running. if (downloader?.CanStop == true) { downloader.Stop(); } // Don't set status to canceled if already successful. if (!this.IsSuccessful) { this.Status = OperationStatus.Canceled; } return true; } /// /// The ResumeInternal /// protected override void ResumeInternal() { downloader.Resume(); this.Status = OperationStatus.Working; } /// /// The WorkerCompleted /// /// The e protected override void WorkerCompleted(RunWorkerCompletedEventArgs e) { } /// /// The WorkerDoWork /// /// The e protected override void WorkerDoWork(DoWorkEventArgs e) { downloader.Start(); while (downloader?.IsBusy == true) { Thread.Sleep(200); } if (_combine && _downloadSuccessful) { var audio = downloader.Files[0].Path; var video = downloader.Files[1].Path; this.ReportProgress(-1, new Dictionary() { { nameof(Progress), 0 } }); this.ReportProgress(ProgressMax, null); try { FFMpegResult result; this.ReportProgress(-1, new Dictionary() { { nameof(ProgressText), "Combining..." } }); result = FFMpeg.Combine(video, audio, this.Output, delegate (int percentage) { // Combine progress this.ReportProgress(percentage, null); }); if (result.Value) { e.Result = OperationStatus.Success; } else { e.Result = OperationStatus.Failed; this.ErrorsInternal.AddRange(result.Errors); } // Cleanup the separate audio and video files FileHelper.DeleteFiles(audio, video); } catch (Exception ex) { Logger.WriteException(ex); e.Result = OperationStatus.Failed; } } else { e.Result = this.Status; } } /// /// The WorkerProgressChanged /// /// The e protected override void WorkerProgressChanged(ProgressChangedEventArgs e) { if (e.UserState == null) { return; } // Used to set multiple properties if (e.UserState is Dictionary) { foreach (var pair in (e.UserState as Dictionary)) { this.GetType().GetProperty(pair.Key).SetValue(this, pair.Value); } } } /// /// The downloader_CalculatedTotalFileSize /// /// The sender /// The e private void downloader_CalculatedTotalFileSize(object sender, EventArgs e) { this.FileSize = downloader.TotalSize; } /// /// The downloader_Canceled /// /// The sender /// The e private void downloader_Canceled(object sender, EventArgs e) { if (this.Status == OperationStatus.Failed) { this.Status = OperationStatus.Canceled; } } /// /// The downloader_Completed /// /// The sender /// The e private void downloader_Completed(object sender, EventArgs e) { // Set status to successful if no download(s) failed. if (this.Status != OperationStatus.Failed) { if (_combine) { _downloadSuccessful = true; } else { this.Status = OperationStatus.Success; } } } /// /// The downloader_FileDownloadFailed /// /// The sender /// The e private void downloader_FileDownloadFailed(object sender, FileDownloadFailedEventArgs e) { // If one or more files fail, whole operation failed. Might handle it more // elegantly in the future. this.Status = OperationStatus.Failed; downloader.Stop(); } /// /// The downloader_ProgressChanged /// /// The sender /// The e private void downloader_ProgressChanged(object sender, EventArgs e) { if (_processing) { return; } try { _processing = true; var speed = string.Format(new ByteFormatProvider(), "{0:s}", downloader.Speed); var longETA = FileHelper.GetETA(downloader.Speed, downloader.TotalSize, downloader.TotalProgress); var ETA = longETA == 0 ? "" : " " + TimeSpan.FromMilliseconds(longETA * 1000).ToString(@"hh\:mm\:ss"); this.ETA = ETA; this.Speed = speed; this.Progress = downloader.TotalProgress; this.ReportProgress((int)downloader.TotalPercentage(), null); } catch { } finally { _processing = false; } } #endregion Methods } }