From e7dbe58025bbbde620bc1f6d0197e5deba6c92ee Mon Sep 17 00:00:00 2001 From: Assistant Date: Mon, 18 May 2020 09:05:42 -0600 Subject: [PATCH] Playlist downloading --- .../Classes/External Interfaces/BeatSaver.cs | 149 ++++++++++++++++-- .../Classes/External Interfaces/Playlists.cs | 43 ++++- ModAssistant/Localisation/en-DEBUG.xaml | 3 + ModAssistant/Localisation/en.xaml | 3 + ModAssistant/Pages/Options.xaml.cs | 7 +- 5 files changed, 179 insertions(+), 26 deletions(-) diff --git a/ModAssistant/Classes/External Interfaces/BeatSaver.cs b/ModAssistant/Classes/External Interfaces/BeatSaver.cs index ed67cf7..d501ceb 100644 --- a/ModAssistant/Classes/External Interfaces/BeatSaver.cs +++ b/ModAssistant/Classes/External Interfaces/BeatSaver.cs @@ -1,6 +1,8 @@ using System; using System.IO; using System.IO.Compression; +using System.Net; +using System.Net.Http.Headers; using System.Threading.Tasks; using System.Windows; using static ModAssistant.Http; @@ -13,35 +15,108 @@ namespace ModAssistant.API private static readonly string CustomSongsFolder = Path.Combine("Beat Saber_Data", "CustomLevels"); private const bool BypassDownloadCounter = false; - public static async Task GetFromKey(string Key, bool showNotification = true) + public static async Task GetFromKey(string Key, bool showNotification = true) { - BeatSaverApiResponse Map = await GetResponse(BeatSaverURLPrefix + "/api/maps/detail/" + Key); - await InstallMap(Map, showNotification); + return await GetMap(Key, "key", showNotification); } - public static async Task GetFromHash(string Hash, bool showNotification = true) + public static async Task GetFromHash(string Hash, bool showNotification = true) { - BeatSaverApiResponse Map = await GetResponse(BeatSaverURLPrefix + "/api/maps/by-hash/" + Hash); - await InstallMap(Map, showNotification); + return await GetMap(Hash, "hash", showNotification); } - private static async Task GetResponse(string url) + private static async Task GetMap(string id, string type, bool showNotification) { + string urlSegment; + switch (type) + { + case "hash": + urlSegment = "/api/maps/by-hash/"; + break; + case "key": + urlSegment = "/api/maps/detail/"; + break; + default: + return null; + } + + BeatSaverMap map = new BeatSaverMap(); + map.Success = false; try { - var resp = await HttpClient.GetAsync(url); - var body = await resp.Content.ReadAsStringAsync(); - - return JsonSerializer.Deserialize(body); + BeatSaverApiResponse beatsaver = await GetResponse(BeatSaverURLPrefix + urlSegment + id); + map.Name = await InstallMap(beatsaver.map, showNotification); + map.Success = true; } catch (Exception e) { - MessageBox.Show($"{Application.Current.FindResource("OneClick:MapDownloadFailed")}\n\n" + e); + ModAssistant.Utils.Log($"Failed downloading BeatSaver map: {id} | Error: {e}", "ERROR"); + } + return map; + } + + private static async Task GetResponse(string url, bool showNotification = true) + { + BeatSaverApiResponse response = new BeatSaverApiResponse(); + try + { + var resp = await HttpClient.GetAsync(url); + response.statusCode = resp.StatusCode; + response.ratelimit = GetRatelimit(resp.Headers); + if (response.statusCode == HttpStatusCode.OK) + { + if (response.ratelimit.IsSafe) + { + string body = await resp.Content.ReadAsStringAsync(); + response.map = JsonSerializer.Deserialize(body); + return response; + } + else + { + return response; + } + } + else + { + return response; + } + } + catch (Exception e) + { + if (showNotification) + { + MessageBox.Show($"{Application.Current.FindResource("OneClick:MapDownloadFailed")}\n\n" + e); + } return null; } } - public static async Task InstallMap(BeatSaverApiResponse Map, bool showNotification = true) + private static BeatSaverRatelimit GetRatelimit(HttpResponseHeaders headers) + { + BeatSaverRatelimit ratelimit = new BeatSaverRatelimit(); + + var _rateLimitRemaining = headers.GetValues("Rate-Limit-Remaining").GetEnumerator(); + var _rateLimitReset = headers.GetValues("Rate-Limit-Reset").GetEnumerator(); + var _rateLimitTotal = headers.GetValues("Rate-Limit-Total").GetEnumerator(); + _rateLimitRemaining.MoveNext(); + _rateLimitReset.MoveNext(); + _rateLimitTotal.MoveNext(); + ratelimit.Remaining = Int32.Parse(_rateLimitRemaining.Current); + ratelimit.Reset = Int32.Parse(_rateLimitReset.Current); + ratelimit.Total = Int32.Parse(_rateLimitTotal.Current); + ratelimit.ResetTime = UnixTimestampToDateTime((long)ratelimit.Reset); + + return ratelimit; + } + + public static DateTime UnixTimestampToDateTime(double unixTime) + { + DateTime unixStart = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc); + long unixTimeStampInTicks = (long)(unixTime * TimeSpan.TicksPerSecond); + return new DateTime(unixStart.Ticks + unixTimeStampInTicks, System.DateTimeKind.Utc); + } + + public static async Task InstallMap(BeatSaverApiResponseMap Map, bool showNotification = true) { string zip = Path.Combine(Utils.BeatSaberPath, CustomSongsFolder, Map.hash) + ".zip"; string mapName = string.Concat(($"{Map.key} ({Map.metadata.songName} - {Map.metadata.levelAuthorName})") @@ -83,14 +158,54 @@ namespace ModAssistant.API } else { - string line1 = (string)Application.Current.FindResource("OneClick:SongDownload:Failed"); - string line2 = (string)Application.Current.FindResource("OneClick:SongDownload:NetworkIssues"); - string title = (string)Application.Current.FindResource("OneClick:SongDownload:FailedTitle"); - MessageBox.Show($"{line1}\n{line2}", title); + if (showNotification) + { + string line1 = (string)Application.Current.FindResource("OneClick:SongDownload:Failed"); + string line2 = (string)Application.Current.FindResource("OneClick:SongDownload:NetworkIssues"); + string title = (string)Application.Current.FindResource("OneClick:SongDownload:FailedTitle"); + MessageBox.Show($"{line1}\n{line2}", title); + } + return null; } + return mapName; + } + + public class BeatSaverMap + { + public BeatSaverApiResponse response { get; set; } + public bool Success { get; set; } + public string Name { get; set; } } public class BeatSaverApiResponse + { + public HttpStatusCode statusCode { get; set; } + public BeatSaverRatelimit ratelimit { get; set;} + public BeatSaverApiResponseMap map { get; set; } + } + + public class BeatSaverRatelimit + { + public int Remaining { get; set; } + public int Total { get; set; } + public int Reset { get; set; } + public DateTime ResetTime { get; set; } + public bool IsSafe + { + get + { + if (Remaining > 3) return true; + else return false; + } + } + + public async Task Wait() + { + await Task.Delay(new TimeSpan(ResetTime.Ticks - DateTime.Now.Ticks)); + } + } + + public class BeatSaverApiResponseMap { public Metadata metadata { get; set; } public Stats stats { get; set; } diff --git a/ModAssistant/Classes/External Interfaces/Playlists.cs b/ModAssistant/Classes/External Interfaces/Playlists.cs index 2df7106..714d304 100644 --- a/ModAssistant/Classes/External Interfaces/Playlists.cs +++ b/ModAssistant/Classes/External Interfaces/Playlists.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; -using System.Net; using System.Threading.Tasks; using static ModAssistant.Http; using System.Windows; @@ -44,8 +41,11 @@ namespace ModAssistant.API } } - public static async Task DownloadFrom(string file, System.Windows.Controls.ProgressBar progress = null) + public static async Task DownloadFrom(string file, bool gui = false, System.Windows.Controls.ProgressBar progress = null) { + int Errors = 0; + int Minimum = 0; + int Value = 0; if (progress != null) { progress.Minimum = 0; @@ -53,22 +53,53 @@ namespace ModAssistant.API progress.Value = 0; } Playlist playlist = JsonSerializer.Deserialize(File.ReadAllText(file)); + int Maximum = playlist.songs.Length; if (progress != null) progress.Maximum = playlist.songs.Length; foreach (Playlist.Song song in playlist.songs) { + API.BeatSaver.BeatSaverMap response = new BeatSaver.BeatSaverMap(); if (!string.IsNullOrEmpty(song.hash)) { - await BeatSaver.GetFromHash(song.hash, false); + response = await BeatSaver.GetFromHash(song.hash, false); } else if (!string.IsNullOrEmpty(song.key)) { - await BeatSaver.GetFromKey(song.key, false); + response = await BeatSaver.GetFromKey(song.key, false); } + Value++; if (progress != null) progress.Value++; + + if (gui) + { + if (response.Success) + { + MainWindow.Instance.MainText = $"{string.Format((string)Application.Current.FindResource("Options:InstallingPlaylist"), TextProgress(Minimum, Maximum, Value))}"; + } + else + { + MainWindow.Instance.MainText = $"{string.Format((string)Application.Current.FindResource("Options:FailedPlaylistSong"), song.songName)}"; + ModAssistant.Utils.Log($"Failed installing BeatSaver map: {song.songName} | {song.key} | {song.hash}"); + await Task.Delay(3 * 1000); + Errors++; + } + } + } + if (gui) + { + MainWindow.Instance.MainText = $"{string.Format((string)Application.Current.FindResource("Options:FinishedPlaylist"), Errors, playlist.playlistTitle)}"; } } + private static string TextProgress(int min, int max, int value) + { + if (max == value) + { + return $" {string.Concat(Enumerable.Repeat("▒", 10))} [{value}/{max}]"; + } + int interval = (int)Math.Floor((double)value / ( ((double)max - (double)min ) / (double)10)); + return $" {string.Concat(Enumerable.Repeat("▒", interval))}{string.Concat(Enumerable.Repeat("░", 10 - interval))} [{value}/{max}]"; + } class Playlist { diff --git a/ModAssistant/Localisation/en-DEBUG.xaml b/ModAssistant/Localisation/en-DEBUG.xaml index 42885b1..5d123f8 100644 --- a/ModAssistant/Localisation/en-DEBUG.xaml +++ b/ModAssistant/Localisation/en-DEBUG.xaml @@ -104,6 +104,9 @@ Options:GameType:Oculus Options:Tools Options:InstallPlaylist + {0} Options:InstallingPlaylist + {0} Options:FailedPlaylistSong + {0} {1} Options:FinishedPlaylist Options:Diagnostics Options:OpenLogsButton Options:OpenAppDataButton diff --git a/ModAssistant/Localisation/en.xaml b/ModAssistant/Localisation/en.xaml index 8e7c2c7..3c2876a 100644 --- a/ModAssistant/Localisation/en.xaml +++ b/ModAssistant/Localisation/en.xaml @@ -144,6 +144,9 @@ Oculus Tools Install Playlist + Installing Playlist: {0} + Failed song: {0} + [{0} fails] Finished Installing Playlist: {1} Diagnostics Open Logs Open AppData diff --git a/ModAssistant/Pages/Options.xaml.cs b/ModAssistant/Pages/Options.xaml.cs index 43d09d1..809dba4 100644 --- a/ModAssistant/Pages/Options.xaml.cs +++ b/ModAssistant/Pages/Options.xaml.cs @@ -312,9 +312,10 @@ namespace ModAssistant.Pages private void InstallPlaylistButton_Click(object sender, RoutedEventArgs e) { string playlistFile = Utils.GetManualFile(); - Utils.Log(playlistFile, "DEBUG"); - var result = Task.Run(async () => { API.Playlists.DownloadFrom(playlistFile).Wait(); }); - + if (File.Exists(playlistFile)) + { + Task.Run(() => { API.Playlists.DownloadFrom(playlistFile, true).Wait(); }); + } } } }