9bca44c5f4
Added "Download" option to video cards context menu Updated changelog Related Work Items: #192
292 lines
11 KiB
C#
292 lines
11 KiB
C#
using System;
|
|
using System.IO;
|
|
using Windows.UI.Xaml;
|
|
using Windows.UI.Xaml.Controls;
|
|
using Windows.UI.Xaml.Media.Imaging;
|
|
using Windows.System;
|
|
using YoutubeExplode.Models.MediaStreams;
|
|
using YoutubeExplode;
|
|
using Windows.Storage;
|
|
using Google.Apis.YouTube.v3.Data;
|
|
using System.Threading;
|
|
using System.Xml;
|
|
using Windows.UI.Popups;
|
|
using Windows.UI.Notifications;
|
|
using Microsoft.Toolkit.Uwp.Notifications;
|
|
using System.Threading.Tasks;
|
|
using Windows.ApplicationModel.Resources;
|
|
using Microsoft.AppCenter.Analytics;
|
|
using System.Collections.Generic;
|
|
using Newtonsoft.Json;
|
|
|
|
namespace FoxTube.Controls
|
|
{
|
|
public class DownloadItemContainer
|
|
{
|
|
public string Title { get; set; }
|
|
public string Channel { get; set; }
|
|
public string Id { get; set; }
|
|
public string Name { get; set; }
|
|
public string Extension { get; set; }
|
|
public Uri Thumbnail { get; set; }
|
|
public string Quality { get; set; }
|
|
public TimeSpan Duration { get; set; }
|
|
public bool IsDownloaded { get; set; }
|
|
}
|
|
|
|
public sealed partial class DownloadItem : UserControl
|
|
{
|
|
ResourceLoader resources = ResourceLoader.GetForCurrentView("Downloads");
|
|
|
|
public DownloadItemContainer Container { get; private set; }
|
|
public StorageFile File { get; private set; }
|
|
|
|
CancellationTokenSource cts;
|
|
Progress<double> progress;
|
|
double percentage;
|
|
DispatcherTimer timer;
|
|
NotificationData data;
|
|
|
|
public DownloadItem(MediaStreamInfo info, Video meta, string q)
|
|
{
|
|
InitializeComponent();
|
|
|
|
Container = new DownloadItemContainer()
|
|
{
|
|
Channel = meta.Snippet.ChannelTitle,
|
|
Duration = Methods.GetDuration(meta.ContentDetails.Duration),
|
|
Extension = info.Container.GetFileExtension(),
|
|
Id = meta.Id,
|
|
IsDownloaded = false,
|
|
Quality = q,
|
|
Thumbnail = meta.Snippet.Thumbnails.Medium.Url.ToUri(),
|
|
Title = meta.Snippet.Title
|
|
};
|
|
|
|
Download(info);
|
|
}
|
|
|
|
public DownloadItem(DownloadItemContainer container)
|
|
{
|
|
InitializeComponent();
|
|
|
|
Container = container;
|
|
Load();
|
|
}
|
|
|
|
async void Load()
|
|
{
|
|
File = await DownloadAgent.Downloads.TryGetItemAsync(Container.Name) as StorageFile;
|
|
if (File == null)
|
|
DownloadAgent.Remove(this);
|
|
|
|
donePanel.Visibility = Visibility.Visible;
|
|
progressPanel.Visibility = Visibility.Collapsed;
|
|
|
|
SetMeta();
|
|
}
|
|
|
|
async void Download(MediaStreamInfo info)
|
|
{
|
|
File = await DownloadAgent.Downloads.CreateFileAsync($"{Container.Title.ReplaceInvalidChars('_')}.{Container.Extension}", CreationCollisionOption.GenerateUniqueName);
|
|
Container.Name = File.Name;
|
|
|
|
donePanel.Visibility = Visibility.Collapsed;
|
|
progressPanel.Visibility = Visibility.Visible;
|
|
SetMeta();
|
|
|
|
cts = new CancellationTokenSource();
|
|
progress = new Progress<double>();
|
|
progress.ProgressChanged += (s, e) => percentage = e;
|
|
|
|
timer = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) };
|
|
timer.Tick += (s, e) => UpdateInfo();
|
|
|
|
#region Polling notification
|
|
ToastContent toastContent = new ToastContent()
|
|
{
|
|
Visual = new ToastVisual()
|
|
{
|
|
BindingGeneric = new ToastBindingGeneric()
|
|
{
|
|
Children =
|
|
{
|
|
new AdaptiveText() { Text = resources.GetString("/Downloads/toastStartHeader") },
|
|
new AdaptiveProgressBar()
|
|
{
|
|
Title = Container.Title,
|
|
Status = resources.GetString("/Downloads/downloading/Text"),
|
|
Value = new BindableProgressBarValue("value")
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
Actions = new ToastActionsCustom()
|
|
{
|
|
Buttons =
|
|
{
|
|
new ToastButton(resources.GetString("/Downloads/cancel/Content"), $"dcancel|{Container.Id}")
|
|
}
|
|
},
|
|
Launch = "download",
|
|
ActivationType = ToastActivationType.Foreground
|
|
};
|
|
|
|
data = new NotificationData();
|
|
data.Values["value"] = "0";
|
|
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(toastContent.GetXml()) { Tag = $"download|{Container.Id}", Data = data });
|
|
#endregion
|
|
|
|
timer.Start();
|
|
|
|
try
|
|
{
|
|
await new YoutubeClient().DownloadMediaStreamAsync(info, await File.OpenStreamForWriteAsync(), progress, cts.Token);
|
|
|
|
Container.IsDownloaded = true;
|
|
timer.Stop();
|
|
|
|
if (cts.IsCancellationRequested)
|
|
throw new TaskCanceledException();
|
|
else
|
|
DownloadCompleted();
|
|
}
|
|
catch (TaskCanceledException)
|
|
{
|
|
Windows.Data.Xml.Dom.XmlDocument template = new Windows.Data.Xml.Dom.XmlDocument();
|
|
template.LoadXml($@"<toast>
|
|
<visual>
|
|
<binding template='ToastGeneric'>
|
|
<text>{resources.GetString("/Downloads/toastCanceledHeader")}</text>
|
|
<text>{Container.Title}</text>
|
|
</binding>
|
|
</visual>
|
|
</toast>");
|
|
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(template) { Tag = $"download|{Container.Id}" });
|
|
|
|
try { await File.DeleteAsync(StorageDeleteOption.PermanentDelete); }
|
|
catch { }
|
|
|
|
DownloadAgent.Remove(this);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Windows.Data.Xml.Dom.XmlDocument template = new Windows.Data.Xml.Dom.XmlDocument();
|
|
template.LoadXml($@"<toast>
|
|
<visual>
|
|
<binding template='ToastGeneric'>
|
|
<text>{resources.GetString("/Downloads/failedHead")}</text>
|
|
<text>{resources.GetString("/Downloads/failedBody")}</text>
|
|
</binding>
|
|
</visual>
|
|
</toast>");
|
|
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(template) { Tag = $"download|{Container.Id}" });
|
|
|
|
Analytics.TrackEvent("Failed to download video", new Dictionary<string, string>()
|
|
{
|
|
{ "Exception", e.GetType().ToString() },
|
|
{ "Message", e.Message },
|
|
{ "Container", JsonConvert.SerializeObject(Container) }
|
|
});
|
|
|
|
try { await File.DeleteAsync(StorageDeleteOption.PermanentDelete); }
|
|
catch { }
|
|
|
|
DownloadAgent.Remove(this);
|
|
}
|
|
}
|
|
|
|
private void UpdateInfo()
|
|
{
|
|
progressBar.Value = percentage;
|
|
progressText.Text = Math.Round(percentage * 100, 1) + "%";
|
|
|
|
data.Values["value"] = percentage.ToString();
|
|
ToastNotificationManager.CreateToastNotifier().Update(data, $"download|{Container.Id}");
|
|
}
|
|
|
|
private void DownloadCompleted()
|
|
{
|
|
Windows.Data.Xml.Dom.XmlDocument template = new Windows.Data.Xml.Dom.XmlDocument();
|
|
template.LoadXml($@"<toast activationType='foreground' launch='download'>
|
|
<visual>
|
|
<binding template='ToastGeneric'>
|
|
<text>{resources.GetString("/Downloads/toastCompleteHeader")}</text>
|
|
<text>{Container.Title}</text>
|
|
</binding>
|
|
</visual>
|
|
|
|
<actions>
|
|
<action content='{resources.GetString("/Downloads/gotoOrign/Text")}'
|
|
activationType='foreground'
|
|
arguments='video|{Container.Id}'/>
|
|
</actions>
|
|
</toast>");
|
|
ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(template) { Tag = $"download|{Container.Id}" });
|
|
|
|
donePanel.Visibility = Visibility.Visible;
|
|
progressPanel.Visibility = Visibility.Collapsed;
|
|
}
|
|
|
|
private async void open_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
await Launcher.LaunchFileAsync(File);
|
|
}
|
|
|
|
private void gotoOriginal_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
Methods.MainPage.GoToVideo(Container.Id);
|
|
}
|
|
|
|
public async void Cancel(bool prompt = true)
|
|
{
|
|
cancel.IsEnabled = false;
|
|
if(prompt)
|
|
{
|
|
MessageDialog dialog = new MessageDialog(resources.GetString("/Downloads/prompt"), resources.GetString("/Downloads/cancellingHeader"));
|
|
|
|
dialog.Commands.Add(new UICommand(resources.GetString("/Downloads/yes"), null, false));
|
|
dialog.Commands.Add(new UICommand(resources.GetString("/Downloads/no"), null, true));
|
|
|
|
dialog.DefaultCommandIndex = 1;
|
|
if ((bool)(await dialog.ShowAsync()).Id)
|
|
{
|
|
cancel.IsEnabled = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
cts.Cancel();
|
|
DownloadAgent.Remove(this);
|
|
status.Text = resources.GetString("/Downloads/cancelling");
|
|
progressBar.IsIndeterminate = true;
|
|
}
|
|
|
|
void SetMeta()
|
|
{
|
|
try
|
|
{
|
|
thumbnail.Source = new BitmapImage(Container.Thumbnail) { DecodePixelHeight = (int)thumbnail.ActualHeight, DecodePixelWidth = (int)thumbnail.ActualWidth };
|
|
|
|
title.Text = Container.Title;
|
|
path.Text = File.Path;
|
|
|
|
meta.Text = $@"{resources.GetString("/Downloads/ext")}: {Container.Extension}
|
|
{resources.GetString("/Downloads/quality")}: {Container.Quality}
|
|
{resources.GetString("/Downloads/duration")}: {Container.Duration}
|
|
{resources.GetString("/Downloads/author")}: {Container.Channel}";
|
|
}
|
|
catch
|
|
{
|
|
DownloadAgent.Remove(this);
|
|
}
|
|
}
|
|
|
|
private void Cancel_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
Cancel();
|
|
}
|
|
}
|
|
}
|