mirror of
https://github.com/knah/VRCMelonAssistant.git
synced 2024-05-18 03:12:18 +12:00
commit
16dd4ba72f
|
@ -23,6 +23,7 @@ namespace ModAssistant
|
|||
public static bool ReinstallInstalledMods;
|
||||
public static string Version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
public static List<string> SavedMods = ModAssistant.Properties.Settings.Default.SavedMods.Split(',').ToList();
|
||||
public static MainWindow window;
|
||||
public static bool Update = true;
|
||||
public static bool GUI = true;
|
||||
|
||||
|
@ -82,12 +83,12 @@ namespace ModAssistant
|
|||
|
||||
if (GUI)
|
||||
{
|
||||
MainWindow window = new MainWindow();
|
||||
window = new MainWindow();
|
||||
window.Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
Application.Current.Shutdown();
|
||||
//Application.Current.Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,8 +108,6 @@ namespace ModAssistant
|
|||
await OneClickInstaller.InstallAsset(args[1]);
|
||||
}
|
||||
|
||||
Current.Shutdown();
|
||||
|
||||
Update = false;
|
||||
GUI = false;
|
||||
args = Shift(args, 2);
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.IO;
|
|||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using static ModAssistant.Http;
|
||||
|
@ -18,11 +19,13 @@ namespace ModAssistant.API
|
|||
|
||||
public static async Task<BeatSaverMap> GetFromKey(string Key, bool showNotification = true)
|
||||
{
|
||||
if (showNotification) OneClickInstaller.Status.Show();
|
||||
return await GetMap(Key, "key", showNotification);
|
||||
}
|
||||
|
||||
public static async Task<BeatSaverMap> GetFromHash(string Hash, bool showNotification = true)
|
||||
{
|
||||
if (showNotification) OneClickInstaller.Status.Show();
|
||||
return await GetMap(Hash, "hash", showNotification);
|
||||
}
|
||||
|
||||
|
@ -43,24 +46,34 @@ namespace ModAssistant.API
|
|||
|
||||
BeatSaverMap map = new BeatSaverMap();
|
||||
map.Success = false;
|
||||
if (showNotification) Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:Installing"), id)}");
|
||||
try
|
||||
{
|
||||
BeatSaverApiResponse beatsaver = await GetResponse(BeatSaverURLPrefix + urlSegment + id);
|
||||
if (beatsaver != null && beatsaver.map != null)
|
||||
{
|
||||
map.response = beatsaver;
|
||||
map.Name = await InstallMap(beatsaver.map, showNotification);
|
||||
map.Success = true;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ModAssistant.Utils.Log($"Failed downloading BeatSaver map: {id} | Error: {e}", "ERROR");
|
||||
ModAssistant.Utils.Log($"Failed downloading BeatSaver map: {id} | Error: {e.Message}", "ERROR");
|
||||
Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:Failed"), (map.Name ?? id))}");
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private static async Task<BeatSaverApiResponse> GetResponse(string url, bool showNotification = true)
|
||||
private static async Task<BeatSaverApiResponse> GetResponse(string url, bool showNotification = true, int retries = 3)
|
||||
{
|
||||
if (retries == 0)
|
||||
{
|
||||
ModAssistant.Utils.Log($"Max tries reached: Skipping {url}", "ERROR");
|
||||
Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:RatelimitSkip"), url)}");
|
||||
throw new Exception("Max retries allowed");
|
||||
}
|
||||
|
||||
BeatSaverApiResponse response = new BeatSaverApiResponse();
|
||||
try
|
||||
{
|
||||
|
@ -69,22 +82,21 @@ namespace ModAssistant.API
|
|||
response.ratelimit = GetRatelimit(resp.Headers);
|
||||
string body = await resp.Content.ReadAsStringAsync();
|
||||
|
||||
if ((int)resp.StatusCode == 429)
|
||||
{
|
||||
Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:RatelimitHit"), response.ratelimit.ResetTime)}");
|
||||
await response.ratelimit.Wait();
|
||||
return await GetResponse(url, showNotification, retries - 1);
|
||||
}
|
||||
|
||||
if (response.statusCode == HttpStatusCode.OK)
|
||||
{
|
||||
if (response.ratelimit.IsSafe)
|
||||
{
|
||||
response.map = JsonSerializer.Deserialize<BeatSaverApiResponseMap>(body);
|
||||
return response;
|
||||
}
|
||||
else
|
||||
{
|
||||
ModAssistant.Utils.Log($"Ratelimit: ({response.ratelimit.Remaining}/{response.ratelimit.Total}) {response.ratelimit.ResetTime}");
|
||||
return response;
|
||||
}
|
||||
response.map = JsonSerializer.Deserialize<BeatSaverApiResponseMap>(body);
|
||||
return response;
|
||||
}
|
||||
else
|
||||
{
|
||||
ModAssistant.Utils.Log($"Ratelimit: [{response.statusCode}]({response.ratelimit.Remaining}/{response.ratelimit.Total}) {response.ratelimit.ResetTime} \n{body}", "ERROR");
|
||||
Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:Failed"), url)}");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
@ -98,9 +110,96 @@ namespace ModAssistant.API
|
|||
}
|
||||
}
|
||||
|
||||
private static BeatSaverRatelimit GetRatelimit(HttpResponseHeaders headers)
|
||||
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
static extern int memcmp(byte[] b1, byte[] b2, long count);
|
||||
|
||||
public static async Task<string> InstallMap(BeatSaverApiResponseMap Map, bool showNotification = true)
|
||||
{
|
||||
BeatSaverRatelimit ratelimit = new BeatSaverRatelimit();
|
||||
string zip = Path.Combine(Utils.BeatSaberPath, CustomSongsFolder, Map.hash) + ".zip";
|
||||
string mapName = string.Concat(($"{Map.key} ({Map.metadata.songName} - {Map.metadata.levelAuthorName})")
|
||||
.Split(ModAssistant.Utils.Constants.IllegalCharacters));
|
||||
string directory = Path.Combine(Utils.BeatSaberPath, CustomSongsFolder, mapName);
|
||||
|
||||
#pragma warning disable CS0162 // Unreachable code detected
|
||||
if (BypassDownloadCounter)
|
||||
{
|
||||
await Utils.DownloadAsset(BeatSaverURLPrefix + Map.directDownload, CustomSongsFolder, Map.hash + ".zip", mapName, showNotification, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Utils.DownloadAsset(BeatSaverURLPrefix + Map.downloadURL, CustomSongsFolder, Map.hash + ".zip", mapName, showNotification, true);
|
||||
}
|
||||
#pragma warning restore CS0162 // Unreachable code detected
|
||||
|
||||
if (File.Exists(zip))
|
||||
{
|
||||
byte[] zipMagicNumber = { 80, 75, 3, 4 };
|
||||
byte[] magicNumber = new byte[4];
|
||||
|
||||
try
|
||||
{
|
||||
using (FileStream fs = new FileStream(zip, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
fs.Read(magicNumber, 0, magicNumber.Length);
|
||||
fs.Close();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(magicNumber.Length == zipMagicNumber.Length && memcmp(magicNumber, zipMagicNumber, magicNumber.Length) == 0))
|
||||
{
|
||||
ModAssistant.Utils.Log($"Failed extracting BeatSaver map: {zip} \n| Content: {string.Join("\n", File.ReadAllLines(zip))}", "ERROR");
|
||||
throw new Exception("File not a zip.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (FileStream stream = new FileStream(zip, FileMode.Open))
|
||||
using (ZipArchive archive = new ZipArchive(stream))
|
||||
{
|
||||
foreach (ZipArchiveEntry file in archive.Entries)
|
||||
{
|
||||
string fileDirectory = Path.GetDirectoryName(Path.Combine(directory, file.FullName));
|
||||
if (!Directory.Exists(fileDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(fileDirectory);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(file.Name))
|
||||
{
|
||||
file.ExtractToFile(Path.Combine(directory, file.FullName), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
File.Delete(zip);
|
||||
ModAssistant.Utils.Log($"Failed extracting BeatSaver map: {zip} | Error: {e} \n| Content: {string.Join("\n", File.ReadAllLines(zip))}", "ERROR");
|
||||
throw new Exception("File extraction failed.");
|
||||
}
|
||||
File.Delete(zip);
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
}
|
||||
throw new Exception("Zip file not found.");
|
||||
}
|
||||
return mapName;
|
||||
}
|
||||
|
||||
public static BeatSaver.BeatSaverRatelimit GetRatelimit(HttpResponseHeaders headers)
|
||||
{
|
||||
BeatSaver.BeatSaverRatelimit ratelimit = new BeatSaver.BeatSaverRatelimit();
|
||||
|
||||
|
||||
if (headers.TryGetValues("Rate-Limit-Remaining", out IEnumerable<string> _remaining))
|
||||
|
@ -138,58 +237,30 @@ namespace ModAssistant.API
|
|||
return new DateTime(unixStart.Ticks + unixTimeStampInTicks, System.DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
public static async Task<string> InstallMap(BeatSaverApiResponseMap Map, bool showNotification = true)
|
||||
public static async Task Download(string url, string output, int retries = 3)
|
||||
{
|
||||
string zip = Path.Combine(Utils.BeatSaberPath, CustomSongsFolder, Map.hash) + ".zip";
|
||||
string mapName = string.Concat(($"{Map.key} ({Map.metadata.songName} - {Map.metadata.levelAuthorName})")
|
||||
.Split(ModAssistant.Utils.Constants.IllegalCharacters));
|
||||
string directory = Path.Combine(Utils.BeatSaberPath, CustomSongsFolder, mapName);
|
||||
|
||||
#pragma warning disable CS0162 // Unreachable code detected
|
||||
if (BypassDownloadCounter)
|
||||
if (retries == 0)
|
||||
{
|
||||
await Utils.DownloadAsset(BeatSaverURLPrefix + Map.directDownload, CustomSongsFolder, Map.hash + ".zip", mapName, showNotification);
|
||||
ModAssistant.Utils.Log($"Max tries reached: Couldn't download {url}", "ERROR");
|
||||
throw new Exception("Max retries allowed");
|
||||
}
|
||||
else
|
||||
{
|
||||
await Utils.DownloadAsset(BeatSaverURLPrefix + Map.downloadURL, CustomSongsFolder, Map.hash + ".zip", mapName, showNotification);
|
||||
}
|
||||
#pragma warning restore CS0162 // Unreachable code detected
|
||||
|
||||
if (File.Exists(zip))
|
||||
{
|
||||
using (FileStream stream = new FileStream(zip, FileMode.Open))
|
||||
using (ZipArchive archive = new ZipArchive(stream))
|
||||
{
|
||||
foreach (ZipArchiveEntry file in archive.Entries)
|
||||
{
|
||||
string fileDirectory = Path.GetDirectoryName(Path.Combine(directory, file.FullName));
|
||||
if (!Directory.Exists(fileDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(fileDirectory);
|
||||
}
|
||||
var resp = await HttpClient.GetAsync(url);
|
||||
|
||||
if (!string.IsNullOrEmpty(file.Name))
|
||||
{
|
||||
file.ExtractToFile(Path.Combine(directory, file.FullName), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File.Delete(zip);
|
||||
}
|
||||
else
|
||||
if ((int)resp.StatusCode == 429)
|
||||
{
|
||||
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;
|
||||
var ratelimit = new BeatSaver.BeatSaverRatelimit();
|
||||
ratelimit = GetRatelimit(resp.Headers);
|
||||
Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:RatelimitHit"), ratelimit.ResetTime)}");
|
||||
await ratelimit.Wait();
|
||||
await Download(url, output, retries - 1);
|
||||
}
|
||||
|
||||
using (var stream = await resp.Content.ReadAsStreamAsync())
|
||||
using (var fs = new FileStream(output, FileMode.OpenOrCreate, FileAccess.Write))
|
||||
{
|
||||
await stream.CopyToAsync(fs);
|
||||
}
|
||||
return mapName;
|
||||
}
|
||||
|
||||
public class BeatSaverMap
|
||||
|
@ -212,15 +283,6 @@ namespace ModAssistant.API
|
|||
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));
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace ModAssistant.API
|
|||
}
|
||||
}
|
||||
|
||||
public static async Task DownloadFrom(string file, bool gui = false, System.Windows.Controls.ProgressBar progress = null)
|
||||
public static async Task DownloadFrom(string file)
|
||||
{
|
||||
if (Path.Combine(BeatSaberPath, PlaylistsFolder) != Path.GetDirectoryName(file))
|
||||
{
|
||||
|
@ -52,15 +52,9 @@ namespace ModAssistant.API
|
|||
int Errors = 0;
|
||||
int Minimum = 0;
|
||||
int Value = 0;
|
||||
if (progress != null)
|
||||
{
|
||||
progress.Minimum = 0;
|
||||
progress.Maximum = 1;
|
||||
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)
|
||||
{
|
||||
|
@ -74,27 +68,20 @@ namespace ModAssistant.API
|
|||
response = await BeatSaver.GetFromKey(song.key, false);
|
||||
}
|
||||
Value++;
|
||||
if (progress != null) progress.Value++;
|
||||
|
||||
if (gui)
|
||||
if (response.Success)
|
||||
{
|
||||
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++;
|
||||
}
|
||||
Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("Options:InstallingPlaylist"), TextProgress(Minimum, Maximum, Value))} {response.Name}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("Options:FailedPlaylistSong"), song.songName)}");
|
||||
ModAssistant.Utils.Log($"Failed installing BeatSaver map: {song.songName} | {song.key} | {song.hash} | ({response?.response?.ratelimit?.Remaining})");
|
||||
await Task.Delay(3 * 1000);
|
||||
Errors++;
|
||||
}
|
||||
}
|
||||
if (gui)
|
||||
{
|
||||
MainWindow.Instance.MainText = $"{string.Format((string)Application.Current.FindResource("Options:FinishedPlaylist"), Errors, playlist.playlistTitle)}";
|
||||
}
|
||||
Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("Options:FinishedPlaylist"), Errors, playlist.playlistTitle)}");
|
||||
}
|
||||
|
||||
private static string TextProgress(int min, int max, int value)
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using static ModAssistant.Http;
|
||||
|
||||
namespace ModAssistant.API
|
||||
{
|
||||
|
@ -11,6 +14,18 @@ namespace ModAssistant.API
|
|||
{
|
||||
public static readonly string BeatSaberPath = App.BeatSaberInstallDirectory;
|
||||
|
||||
public static void SetMessage(string message)
|
||||
{
|
||||
if (App.window == null)
|
||||
{
|
||||
OneClickStatus.Instance.MainText = message;
|
||||
}
|
||||
else
|
||||
{
|
||||
MainWindow.Instance.MainText = message;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task DownloadAsset(string link, string folder, bool showNotifcation, string fileName = null)
|
||||
{
|
||||
await DownloadAsset(link, folder, fileName, null, showNotifcation);
|
||||
|
@ -21,7 +36,7 @@ namespace ModAssistant.API
|
|||
await DownloadAsset(link, folder, fileName, displayName, true);
|
||||
}
|
||||
|
||||
public static async Task DownloadAsset(string link, string folder, string fileName, string displayName, bool showNotification)
|
||||
public static async Task DownloadAsset(string link, string folder, string fileName, string displayName, bool showNotification, bool beatsaver = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(BeatSaberPath))
|
||||
{
|
||||
|
@ -43,17 +58,18 @@ namespace ModAssistant.API
|
|||
displayName = Path.GetFileNameWithoutExtension(fileName);
|
||||
}
|
||||
|
||||
await ModAssistant.Utils.Download(link, fileName);
|
||||
if (beatsaver) await BeatSaver.Download(link, fileName);
|
||||
else await ModAssistant.Utils.Download(link, fileName);
|
||||
|
||||
if (showNotification)
|
||||
{
|
||||
ModAssistant.Utils.SendNotify(string.Format((string)Application.Current.FindResource("OneClick:InstalledAsset"), displayName));
|
||||
SetMessage(string.Format((string)Application.Current.FindResource("OneClick:InstalledAsset"), displayName));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModAssistant.Utils.SendNotify((string)Application.Current.FindResource("OneClick:AssetInstallFailed"));
|
||||
SetMessage((string)Application.Current.FindResource("OneClick:AssetInstallFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ namespace ModAssistant
|
|||
class OneClickInstaller
|
||||
{
|
||||
private static readonly string[] Protocols = new[] { "modelsaber", "beatsaver", "bsplaylist" };
|
||||
public static OneClickStatus Status = new OneClickStatus();
|
||||
|
||||
public static async Task InstallAsset(string link)
|
||||
{
|
||||
|
@ -37,11 +38,14 @@ namespace ModAssistant
|
|||
|
||||
private static async Task ModelSaber(Uri uri)
|
||||
{
|
||||
Status.Show();
|
||||
API.Utils.SetMessage($"{string.Format((string)Application.Current.FindResource("OneClick:Installing"), System.Web.HttpUtility.UrlDecode(uri.Segments.Last()))}");
|
||||
await API.ModelSaber.GetModel(uri);
|
||||
}
|
||||
|
||||
private static async Task Playlist(Uri uri)
|
||||
{
|
||||
Status.Show();
|
||||
await API.Playlists.DownloadAll(uri);
|
||||
}
|
||||
|
||||
|
|
|
@ -158,6 +158,10 @@
|
|||
<sys:String x:Key="OneClick:AssetInstallFailed">OneClick:AssetInstallFailed</sys:String>
|
||||
<sys:String x:Key="OneClick:ProtocolHandler:Registered">{0} OneClick:ProtocolHandler:Registered</sys:String>
|
||||
<sys:String x:Key="OneClick:ProtocolHandler:Unregistered">{0} OneClick:ProtocolHandler:Unregistered</sys:String>
|
||||
<sys:String x:Key="OneClick:Installing">{0} OneClick:Installing</sys:String>
|
||||
<sys:String x:Key="OneClick:RatelimitSkip">{0} OneClick:RatelimitSkip</sys:String>
|
||||
<sys:String x:Key="OneClick:RatelimitHit">{0} OneClick:RatelimitHit</sys:String>
|
||||
<sys:String x:Key="OneClick:Failed">{0} OneClick:Failed</sys:String>
|
||||
|
||||
<!-- Themes Class -->
|
||||
<sys:String x:Key="Themes:ThemeNotFound">Themes:ThemeNotFound</sys:String>
|
||||
|
|
|
@ -217,6 +217,10 @@
|
|||
<sys:String x:Key="OneClick:AssetInstallFailed">Failed to install.</sys:String>
|
||||
<sys:String x:Key="OneClick:ProtocolHandler:Registered">{0} OneClick™ Install handlers registered!</sys:String>
|
||||
<sys:String x:Key="OneClick:ProtocolHandler:Unregistered">{0} OneClick™ Install handlers unregistered!</sys:String>
|
||||
<sys:String x:Key="OneClick:Installing">Installing: {0}</sys:String>
|
||||
<sys:String x:Key="OneClick:RatelimitSkip">Max tries reached: Skipping {0}</sys:String>
|
||||
<sys:String x:Key="OneClick:RatelimitHit">Ratelimit hit. Resuming in {0}</sys:String>
|
||||
<sys:String x:Key="OneClick:Failed">Download failed: {0}</sys:String>
|
||||
|
||||
<!-- Themes Class -->
|
||||
<sys:String x:Key="Themes:ThemeNotFound">Theme not found, reverting to default theme...</sys:String>
|
||||
|
|
|
@ -78,6 +78,9 @@
|
|||
<Compile Include="Classes\Updater.cs" />
|
||||
<Compile Include="Libs\semver\SemVersion.cs" />
|
||||
<Compile Include="Libs\semver\IntExtensions.cs" />
|
||||
<Compile Include="OneClickStatus.xaml.cs">
|
||||
<DependentUpon>OneClickStatus.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Pages\Intro.xaml.cs">
|
||||
<DependentUpon>Intro.xaml</DependentUpon>
|
||||
</Compile>
|
||||
|
@ -131,6 +134,10 @@
|
|||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="OneClickStatus.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Pages\Intro.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
|
98
ModAssistant/OneClickStatus.xaml
Normal file
98
ModAssistant/OneClickStatus.xaml
Normal file
|
@ -0,0 +1,98 @@
|
|||
<Window x:Class="ModAssistant.OneClickStatus"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:ModAssistant"
|
||||
mc:Ignorable="d"
|
||||
Title="OneClick Installer" Height="800" Width="600" WindowStyle="ToolWindow" ResizeMode="NoResize">
|
||||
|
||||
<Window.Resources>
|
||||
<local:DivideDoubleByTwoConverter x:Key="DivideDoubleByTwoConverter" />
|
||||
<Style x:Key="Spin" TargetType="{x:Type Image}">
|
||||
<Setter Property="RenderTransform">
|
||||
<Setter.Value>
|
||||
<RotateTransform Angle="0" CenterX="{Binding Path=ActualWidth, Converter={StaticResource DivideDoubleByTwoConverter}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Image}}" CenterY="{Binding Path=ActualHeight, Converter={StaticResource DivideDoubleByTwoConverter}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Image}}" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsEnabled" Value="True">
|
||||
<Trigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard x:Name="RotateStarCompass">
|
||||
<DoubleAnimation
|
||||
AutoReverse="False"
|
||||
RepeatBehavior="Forever"
|
||||
Storyboard.TargetProperty="RenderTransform.Angle"
|
||||
From="0"
|
||||
To="360"
|
||||
Duration="0:0:3" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.EnterActions>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle Fill="{DynamicResource ModAssistantBackground}" Grid.RowSpan="2"/>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Image
|
||||
Grid.Row="0"
|
||||
Margin="60,0"
|
||||
VerticalAlignment="Center"
|
||||
Source="{DynamicResource loadingInnerDrawingImage}"
|
||||
Stretch="Uniform" />
|
||||
<Image
|
||||
Grid.Row="0"
|
||||
Margin="60,0"
|
||||
VerticalAlignment="Center"
|
||||
Source="{DynamicResource loadingMiddleDrawingImage}"
|
||||
Stretch="Uniform" />
|
||||
<Image
|
||||
Grid.Row="0"
|
||||
Margin="60,0"
|
||||
VerticalAlignment="Center"
|
||||
Source="{DynamicResource loadingOuterDrawingImage}"
|
||||
Stretch="Uniform"
|
||||
Style="{StaticResource Spin}" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Border
|
||||
Grid.Row="0"
|
||||
Margin="10,0,10,10"
|
||||
BorderBrush="{DynamicResource BottomStatusBarOutline}"
|
||||
BorderThickness="1">
|
||||
<ScrollViewer
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
Background="{DynamicResource BottomStatusBarBackground}"
|
||||
Margin="0">
|
||||
<TextBox
|
||||
Name="HistoryTextBlock"
|
||||
Margin="0"
|
||||
Padding="5"
|
||||
Background="{DynamicResource BottomStatusBarBackground}"
|
||||
BorderThickness="0"
|
||||
Foreground="{DynamicResource TextColor}" />
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
64
ModAssistant/OneClickStatus.xaml.cs
Normal file
64
ModAssistant/OneClickStatus.xaml.cs
Normal file
|
@ -0,0 +1,64 @@
|
|||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace ModAssistant
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for OneClickStatus.xaml
|
||||
/// </summary>
|
||||
public partial class OneClickStatus : Window
|
||||
{
|
||||
public static OneClickStatus Instance;
|
||||
|
||||
public string HistoryText
|
||||
{
|
||||
get
|
||||
{
|
||||
return HistoryTextBlock.Text;
|
||||
}
|
||||
set
|
||||
{
|
||||
Dispatcher.Invoke(new Action(() => { OneClickStatus.Instance.HistoryTextBlock.Text = value; }));
|
||||
}
|
||||
}
|
||||
public string MainText
|
||||
{
|
||||
get
|
||||
{
|
||||
return HistoryTextBlock.Text;
|
||||
}
|
||||
set
|
||||
{
|
||||
Dispatcher.Invoke(new Action(() => {
|
||||
OneClickStatus.Instance.HistoryTextBlock.Text = string.IsNullOrEmpty(MainText) ? $"{value}" : $"{value}\n{MainText}";
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
public OneClickStatus()
|
||||
{
|
||||
InitializeComponent();
|
||||
Instance = this;
|
||||
}
|
||||
}
|
||||
|
||||
[ValueConversion(typeof(double), typeof(double))]
|
||||
public class DivideDoubleByTwoConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
if (targetType != typeof(double))
|
||||
{
|
||||
throw new InvalidOperationException("The target must be a double");
|
||||
}
|
||||
double d = (double)value;
|
||||
return ((double)d) / 2;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -325,7 +325,7 @@ namespace ModAssistant.Pages
|
|||
string playlistFile = Utils.GetManualFile();
|
||||
if (File.Exists(playlistFile))
|
||||
{
|
||||
Task.Run(() => { API.Playlists.DownloadFrom(playlistFile, true).Wait(); });
|
||||
Task.Run(() => { API.Playlists.DownloadFrom(playlistFile).Wait(); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue