Archived
1
0
This repository has been archived on 2026-04-22. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
FoxTube/FoxTube/Controls/DownloadItem.xaml.cs
T
Michael Gordeev 9bca44c5f4 Added "Add to" option to video cards context menu
Added "Download" option to video cards context menu
Updated changelog

Related Work Items: #192
2019-04-06 18:31:11 +03:00

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();
}
}
}