Playlist downloading

This commit is contained in:
Assistant 2020-05-18 09:05:42 -06:00
parent 07c2d90b09
commit e7dbe58025
5 changed files with 179 additions and 26 deletions

View file

@ -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<BeatSaverMap> 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<BeatSaverMap> 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<BeatSaverApiResponse> GetResponse(string url)
private static async Task<BeatSaverMap> 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<BeatSaverApiResponse>(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<BeatSaverApiResponse> 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<BeatSaverApiResponseMap>(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<string> 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; }

View file

@ -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<Playlist>(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
{

View file

@ -104,6 +104,9 @@
<sys:String x:Key="Options:GameType:Oculus">Options:GameType:Oculus</sys:String>
<sys:String x:Key="Options:Tools">Options:Tools</sys:String>
<sys:String x:Key="Options:InstallPlaylist">Options:InstallPlaylist</sys:String>
<sys:String x:Key="Options:InstallingPlaylist">{0} Options:InstallingPlaylist</sys:String>
<sys:String x:Key="Options:FailedPlaylistSong">{0} Options:FailedPlaylistSong</sys:String>
<sys:String x:Key="Options:FinishedPlaylist">{0} {1} Options:FinishedPlaylist</sys:String>
<sys:String x:Key="Options:Diagnostics">Options:Diagnostics</sys:String>
<sys:String x:Key="Options:OpenLogsButton">Options:OpenLogsButton</sys:String>
<sys:String x:Key="Options:OpenAppDataButton">Options:OpenAppDataButton</sys:String>

View file

@ -144,6 +144,9 @@
<sys:String x:Key="Options:GameType:Oculus">Oculus</sys:String>
<sys:String x:Key="Options:Tools">Tools</sys:String>
<sys:String x:Key="Options:InstallPlaylist">Install Playlist</sys:String>
<sys:String x:Key="Options:InstallingPlaylist">Installing Playlist: {0}</sys:String>
<sys:String x:Key="Options:FailedPlaylistSong">Failed song: {0}</sys:String>
<sys:String x:Key="Options:FinishedPlaylist">[{0} fails] Finished Installing Playlist: {1}</sys:String>
<sys:String x:Key="Options:Diagnostics">Diagnostics</sys:String>
<sys:String x:Key="Options:OpenLogsButton">Open Logs</sys:String>
<sys:String x:Key="Options:OpenAppDataButton">Open AppData</sys:String>

View file

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