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
}
}