Archived
1
0

Core update. Base core features are done (main app doesn't compile)

Related Work Items: #416, #422, #423, #424
This commit is contained in:
Michael Gordeev
2020-06-11 21:17:18 +03:00
parent b3212738e8
commit c58d846057
18 changed files with 386 additions and 281 deletions
@@ -9,23 +9,25 @@ using YoutubeExplode.Videos;
using YoutubeExplode.Videos.Streams;
using FoxTube.Utils;
using FoxTube.Models;
using Windows.Storage.Pickers;
namespace FoxTube.Services
{
public static class DownloadsCenter
public static class DownloadsService
{
public static List<SavedVideo> History { get; private set; }
public static List<SavedVideo> History { get; } = new List<SavedVideo>();
public static List<DownloadItem> Queue { get; } = new List<DownloadItem>();
static DownloadsCenter() =>
static DownloadsService() =>
Initialize();
private static async void Initialize()
{
StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync("DownloadHistory.json", CreationCollisionOption.OpenIfExists);
StorageFile file = await Storage.Folder.CreateFileAsync("DownloadHistory.json", CreationCollisionOption.OpenIfExists);
try
{
History = JsonConvert.DeserializeObject<List<SavedVideo>>(File.ReadAllText(file.Path) ?? "") ?? new List<SavedVideo>();
List<SavedVideo> savedVideos = JsonConvert.DeserializeObject<List<SavedVideo>>(File.ReadAllText(file.Path) ?? "") ?? new List<SavedVideo>();
History.AddRange(savedVideos);
foreach (SavedVideo i in History)
try { i.IsPathValid = await StorageApplicationPermissions.MostRecentlyUsedList.GetFileAsync(i.AccessToken) != null; }
@@ -33,7 +35,6 @@ namespace FoxTube.Services
}
catch (Exception e)
{
History = new List<SavedVideo>();
await file.DeleteAsync(StorageDeleteOption.PermanentDelete);
StorageApplicationPermissions.MostRecentlyUsedList.Clear();
Metrics.SendReport(new Exception("Failed to load downloads history", e));
@@ -64,12 +65,12 @@ namespace FoxTube.Services
{
await item.CommenceDownload(streamInfo, destination);
SavedVideo savedItem = item as SavedVideo;
SavedVideo savedItem = item;
savedItem.AccessToken = StorageApplicationPermissions.MostRecentlyUsedList.Add(destination);
History.Add(savedItem);
StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync("DownloadHistory.json", CreationCollisionOption.OpenIfExists);
StorageFile file = await Storage.Folder.CreateFileAsync("DownloadHistory.json", CreationCollisionOption.OpenIfExists);
File.WriteAllText(file.Path, JsonConvert.SerializeObject(History));
}
catch (OperationCanceledException) { }
@@ -111,10 +112,11 @@ namespace FoxTube.Services
public static async Task<StorageFolder> GetDefaultDownloadsFolder()
{
if (string.IsNullOrWhiteSpace(Settings.DefaultDownloadsFolder))
return await KnownFolders.VideosLibrary.CreateFolderAsync("FoxTube", CreationCollisionOption.OpenIfExists);
if (Storage.GetValue<string>(Storage.Settings.DefaultDownloadsFolder) is string token && !string.IsNullOrWhiteSpace(token))
return await StorageApplicationPermissions.FutureAccessList.GetFolderAsync(token) ??
await KnownFolders.VideosLibrary.CreateFolderAsync("FoxTube", CreationCollisionOption.OpenIfExists);
else
return await StorageApplicationPermissions.FutureAccessList.GetFolderAsync(Settings.DefaultDownloadsFolder);
return await KnownFolders.VideosLibrary.CreateFolderAsync("FoxTube", CreationCollisionOption.OpenIfExists);
}
public static async Task CancelAll()
@@ -123,5 +125,28 @@ namespace FoxTube.Services
while (Queue.Count > 0)
await Task.Delay(500);
}
public static async Task<StorageFolder> ChangeDefaultFolder()
{
FolderPicker picker = new FolderPicker
{
SuggestedStartLocation = PickerLocationId.Downloads
};
StorageFolder folder = await picker.PickSingleFolderAsync();
if (folder != null)
{
if (Storage.GetValue<string>(Storage.Settings.DefaultDownloadsFolder) is string token && !string.IsNullOrWhiteSpace(token))
StorageApplicationPermissions.FutureAccessList.AddOrReplace(token, folder);
else
{
token = StorageApplicationPermissions.FutureAccessList.Add(folder);
Storage.SetValue(Storage.Settings.DefaultDownloadsFolder, token);
}
}
return folder;
}
}
}
@@ -1,27 +1,36 @@
using FoxTube.Models;
using FoxTube.Models.Collections;
using FoxTube.Utils;
using Newtonsoft.Json;
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.UI.Notifications;
namespace FoxTube.Services
{
public static class Inbox
public static class InboxService
{
private static readonly HttpClient client = new HttpClient();
private static readonly ApplicationDataContainer storage = ApplicationData.Current.RoamingSettings;
public const string lastChangelogVersionKey = "Inbox.lastChangelogVersion";
public const string lastCheckKey = "Inbox.lastChangelogVersion";
public static async void PushNew()
private static readonly HttpClient client = new HttpClient();
public static InboxCollection GetInboxCollection() =>
new InboxCollection();
public static async Task PushNew()
{
try
{
// TODO: Add backend
HttpResponseMessage response = await client.GetAsync($"https://xfox111.net/FoxTube/Inbox?toast=true&publishedAfter={storage.Values["Inbox.lastCheck"]}&lang={Settings.Language}&appVersion={Metrics.CurrentVersion}");
storage.Values["Inbox.lastCheck"] = DateTime.UtcNow.Ticks;
HttpResponseMessage response = await client.GetAsync($"https://xfox111.net/FoxTube/Inbox?" +
$"toast=true&" +
$"publishedAfter={Storage.Registry.Values[lastCheckKey]}&" +
$"lang={Storage.GetValue<string>(Storage.Settings.UILanguage)}&" +
$"appVersion={Metrics.CurrentVersion}");
Storage.Registry.Values[lastCheckKey] = DateTime.UtcNow.Ticks;
if (response.StatusCode == HttpStatusCode.NoContent)
return;
@@ -37,26 +46,6 @@ namespace FoxTube.Services
}
}
public static async Task<InboxItem[]> GetMessages()
{
try
{
// TODO: Add backend
HttpResponseMessage response = await client.GetAsync($"https://xfox111.net/API/FoxTube/Inbox?lang={Settings.Language}&currentVersion={Metrics.CurrentVersion}");
if (response.StatusCode == HttpStatusCode.NoContent)
return new InboxItem[0];
return JsonConvert.DeserializeObject<InboxItem[]>(await response.Content.ReadAsStringAsync());
}
catch (Exception e)
{
Metrics.SendReport(new Exception("Unable to retrieve inbox messages", e));
return new InboxItem[0];
}
}
/// <summary>
/// Fires toast notification with the last changelog content
/// </summary>
@@ -65,9 +54,14 @@ namespace FoxTube.Services
try
{
// TODO: Add backend
Settings.LastReviewedVersion = Metrics.CurrentVersion;
if ((string)Storage.Registry.Values[lastChangelogVersionKey] == Metrics.CurrentVersion)
return;
HttpResponseMessage response = await client.GetAsync($"https://xfox111.net/API/FoxTube/Changelog?lang={Settings.Language}&version={Metrics.CurrentVersion}");
Storage.Registry.Values[lastChangelogVersionKey] = Metrics.CurrentVersion;
HttpResponseMessage response = await client.GetAsync($"https://xfox111.net/API/FoxTube/Changelog?" +
$"lang={Storage.GetValue<string>(Storage.Settings.UILanguage)}&" +
$"version={Metrics.CurrentVersion}");
if (response.StatusCode == HttpStatusCode.NoContent)
return;
+1 -1
View File
@@ -16,7 +16,7 @@ namespace FoxTube.Services
try
{
using HttpClient client = new HttpClient();
string results = await client.GetStringAsync($"http://suggestqueries.google.com/complete/search?ds=yt&client=toolbar&q={term}&hl={Settings.RelevanceLanguage}");
string results = await client.GetStringAsync($"http://suggestqueries.google.com/complete/search?ds=yt&client=toolbar&q={term}&hl={Storage.GetValue<string>(Storage.Settings.RelevanceLanguage)}");
XmlDocument doc = new XmlDocument();
doc.LoadXml(results);
+101
View File
@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.UI.Xaml;
namespace FoxTube.Services
{
public static class Storage
{
public static event EventHandler<Settings> SettingsChanged;
public static StorageFolder Folder => ApplicationData.Current.RoamingFolder;
public static ApplicationDataContainer Registry { get; } = ApplicationData.Current.RoamingSettings;
private static readonly Dictionary<Settings, object> _defaultSettings = new Dictionary<Settings, object>
{
{ Settings.Theme, ElementTheme.Default },
{ Settings.UILanguage, GetDefaultLanguage() },
{ Settings.RelevanceLanguage, GetDefaultLanguage() },
{ Settings.PromptFeedback, true },
{ Settings.PromptReview, true },
{ Settings.AllowAnalytics, true }
};
public enum Settings
{
/// <summary>
/// ElementTheme
/// </summary>
Theme,
/// <summary>
/// string
/// </summary>
UILanguage,
/// <summary>
/// string
/// </summary>
RelevanceLanguage,
/// <summary>
/// string
/// </summary>
DefaultDownloadsFolder,
/// <summary>
/// bool
/// </summary>
PromptFeedback,
/// <summary>
/// bool
/// </summary>
PromptReview,
/// <summary>
/// bool
/// </summary>
AllowAnalytics,
/// <summary>
/// string
/// </summary>
Region
}
public enum Metrics
{
/// <summary>
/// TimeSpan
/// </summary>
Uptime
}
public static void SetValue(Settings key, object value)
{
Registry.Values[$"{key.GetType().Name}.{key}"] = value;
SettingsChanged?.Invoke(value, key);
}
public static void SetValue(Metrics key, object value) =>
Registry.Values[$"{key.GetType().Name}.{key}"] = value;
public static T GetValue<T>(Settings key) =>
(T)(Registry.Values[$"{key.GetType().Name}.{key}"] ?? (_defaultSettings.ContainsKey(key) ? _defaultSettings[key] : null));
public static T GetValue<T>(Metrics key) =>
(T)Registry.Values[$"{key.GetType().Name}.{key}"];
private static string GetDefaultLanguage()
{
if (CultureInfo.InstalledUICulture.TwoLetterISOLanguageName.Belongs("ua", "ru", "by", "kz", "kg", "md", "lv", "ee")) //Languages for Russian-speaking countries
return "ru-RU";
else
return "en-US";
}
public static async Task ResetStorage()
{
Registry.Values.Clear();
foreach (IStorageItem i in await Folder.GetItemsAsync())
await i.DeleteAsync();
}
}
}
+232
View File
@@ -0,0 +1,232 @@
using Google.Apis.Auth.OAuth2;
using Google.Apis.Oauth2.v2;
using System;
using System.Threading.Tasks;
using Windows.Security.Authentication.Web;
using YouTube.Authorization;
using Windows.Security.Credentials;
using FoxTube.Models;
using YouTube;
using System.Threading;
using Google.Apis.YouTube.v3;
using Windows.UI.Xaml.Controls;
using FoxTube.Utils;
using YoutubeExplode;
using System.Linq;
using Newtonsoft.Json;
using Google.Apis.Oauth2.v2.Data;
namespace FoxTube.Services
{
public static class UserService
{
public const string UsersStorageKey = "UserService.Users";
public const string LastUserInfoKey = "UserService.LastUser";
public const int MaxUsersCount = 1;
#region Private members
private static readonly ExtendedYouTubeService _defaultService = new ExtendedYouTubeService(new Google.Apis.Services.BaseClientService.Initializer
{
ApplicationName = "FoxTube",
ApiKey = SecretConstants.YoutubeApiKey, // TODO: Replace with an actual API key
});
private static readonly YoutubeClient _defaultYtClient = new YoutubeClient();
private static string[] Scopes { get; } = new string[]
{
Oauth2Service.Scope.UserinfoProfile,
Oauth2Service.Scope.UserinfoEmail,
YouTubeService.Scope.YoutubeForceSsl
};
private static ClientSecrets[] ClientSecrets { get; } = SecretConstants.ClientSecrets;
#endregion
public static Userinfoplus[] Users { get; private set; } = new Userinfoplus[MaxUsersCount];
public static bool IncognitoMode { get; set; } = false;
public static bool CanAddAccounts => Users.Any(i => i == null);
public static User CurrentUser { get; set; }
public static bool Authorized => CurrentUser != null;
public static ExtendedYouTubeService Service
{
get
{
if (IncognitoMode || CurrentUser == null)
return _defaultService;
else
return CurrentUser.Service;
}
}
public static YoutubeClient YouTubeClient
{
get
{
if (IncognitoMode || CurrentUser == null)
return _defaultYtClient;
else
return CurrentUser.Client;
}
}
public static event EventHandler<bool> UserStateUpdated;
public static event EventHandler<Subscription> SubscriptionsChanged;
static UserService() =>
Initialize();
public static async Task<bool> AddUser()
{
int queueIndex = Users.ToList().FindIndex(i => i == null);
if (queueIndex < 0)
throw new StackOverflowException("The maximum accounts limit is reached");
ClientSecrets secrets = ClientSecrets[queueIndex];
Uri requestString = AuthorizationHelpers.FormQueryString(secrets, Scopes);
WebAuthenticationResult result = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.UseTitle, requestString, AuthorizationHelpers.Endpoint);
switch (result.ResponseStatus)
{
case WebAuthenticationStatus.Success:
string successCode = AuthorizationHelpers.ParseSuccessCode(result.ResponseData);
YouTube.Authorization.UserCredential credential = await AuthorizationHelpers.ExchangeToken(secrets, successCode);
await LoadUser(credential, queueIndex);
return true;
case WebAuthenticationStatus.UserCancel:
break;
case WebAuthenticationStatus.ErrorHttp:
await new ContentDialog
{
Title = "Something went wrong...",
Content = "It may be a bug or temporary server issues. Please, try again later"
}.ShowAsync();
Metrics.SendReport(new Exception("Authorization failed (HTTP Error)"), null,
("Response status", result.ResponseStatus.ToString()),
("Response data", result.ResponseData),
("Error details", result.ResponseErrorDetail.ToString()));
break;
}
return false;
}
public static async void Initialize()
{
Users = JsonConvert.DeserializeObject<Userinfoplus[]>(Storage.Registry.Values[UsersStorageKey] as string ?? "") ?? new Userinfoplus[MaxUsersCount];
int? lastUserIndex = Storage.Registry.Values[LastUserInfoKey] as int?;
if (lastUserIndex.HasValue && Users[lastUserIndex.Value] != null ||
(lastUserIndex = Users.ToList().FindIndex(i => i != null)) > -1)
await SwitchUser(lastUserIndex.Value);
}
public static async Task<bool> Logout()
{
if (CurrentUser?.UserInfo?.Id == null)
return false;
try
{
string userId = CurrentUser.UserInfo.Id;
PasswordVault passwordVault = new PasswordVault();
try
{
PasswordCredential credential = passwordVault.Retrieve("foxtube", userId);
passwordVault.Remove(credential);
}
catch { }
await CurrentUser.Credential.RevokeTokenAsync(CancellationToken.None);
Storage.Registry.Values.Remove($"Subscriptions.{CurrentUser.UserInfo.Id}");
CurrentUser = null;
Users[Users.ToList().FindIndex(i => i.Id == userId)] = null;
Storage.Registry.Values[UsersStorageKey] = JsonConvert.SerializeObject(Users);
Storage.Registry.Values[LastUserInfoKey] = null;
if (Users.Any(i => i != null))
await SwitchUser(Users.ToList().FindIndex(i => i != null));
else
UserStateUpdated?.Invoke(Users, false);
return true;
}
catch (Exception e)
{
Metrics.SendReport(new Exception("Failed to logout", e));
await new ContentDialog
{
Title = "Something went wrong...",
Content = "It may be a bug or temporary server issues. Please, try again later"
}.ShowAsync();
return false;
}
}
public static async Task<bool> SwitchUser(int userIndex)
{
Userinfoplus userInfo = Users[userIndex];
PasswordVault valut = new PasswordVault();
try
{
PasswordCredential vaultCredential = valut.Retrieve("foxtube", userInfo.Id);
vaultCredential.RetrievePassword();
string token = vaultCredential.Password;
YouTube.Authorization.UserCredential credential = await AuthorizationHelpers.RestoreUser(ClientSecrets[userIndex], token);
await LoadUser(credential, userIndex);
return true;
}
catch
{
return false;
}
}
private static async Task LoadUser(YouTube.Authorization.UserCredential credential, int userIndex)
{
CurrentUser = await User.GetUser(credential);
Users[userIndex] = CurrentUser.UserInfo;
Storage.Registry.Values[UsersStorageKey] = JsonConvert.SerializeObject(Users);
Storage.Registry.Values[LastUserInfoKey] = userIndex;
credential.RefreshTokenUpdated += (s, e) => UpdateToken(CurrentUser.UserInfo.Id, credential.Token.RefreshToken);
UpdateToken(CurrentUser.UserInfo.Id, credential.Token.RefreshToken);
UserStateUpdated?.Invoke(Users, true);
}
private static void UpdateToken(string id, string refreshToken)
{
PasswordVault passwordVault = new PasswordVault();
try
{
PasswordCredential vaultCredential = passwordVault.Retrieve("foxtube", id);
vaultCredential.Password = refreshToken;
}
catch
{
PasswordCredential vaultCredential = new PasswordCredential("foxtube", CurrentUser.UserInfo.Id, refreshToken);
passwordVault.Add(vaultCredential);
}
}
internal static void SubscriptionsChangedInvoker(User sender, Subscription subscription) =>
SubscriptionsChanged?.Invoke(sender, subscription);
}
}