mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
Update the launcher to try the Nexus for updates before Github
This commit is contained in:
parent
43778f41ea
commit
9e4c272c1c
@ -1,6 +1,7 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Wabbajack.Launcher.ViewModels;
|
using Wabbajack.Launcher.ViewModels;
|
||||||
using Wabbajack.Launcher.Views;
|
using Wabbajack.Launcher.Views;
|
||||||
|
|
||||||
@ -15,10 +16,11 @@ public class App : Application
|
|||||||
|
|
||||||
public override void OnFrameworkInitializationCompleted()
|
public override void OnFrameworkInitializationCompleted()
|
||||||
{
|
{
|
||||||
|
|
||||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
desktop.MainWindow = new MainWindow
|
desktop.MainWindow = new MainWindow
|
||||||
{
|
{
|
||||||
DataContext = new MainWindowViewModel()
|
DataContext = Program.Services.GetRequiredService<MainWindowViewModel>()
|
||||||
};
|
};
|
||||||
|
|
||||||
base.OnFrameworkInitializationCompleted();
|
base.OnFrameworkInitializationCompleted();
|
||||||
|
40
Wabbajack.Launcher/Models/LegacyNexusApiKey.cs
Normal file
40
Wabbajack.Launcher/Models/LegacyNexusApiKey.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Wabbajack.DTOs.Logins;
|
||||||
|
using Wabbajack.Networking.Http.Interfaces;
|
||||||
|
using Wabbajack.Paths;
|
||||||
|
using Wabbajack.Paths.IO;
|
||||||
|
|
||||||
|
namespace Wabbajack.Launcher.Models;
|
||||||
|
|
||||||
|
public class LegacyNexusApiKey : ITokenProvider<NexusApiState>
|
||||||
|
{
|
||||||
|
private AbsolutePath TokenPath => KnownFolders.WabbajackAppLocal.Combine("nexusapikey");
|
||||||
|
public async ValueTask<NexusApiState?> Get()
|
||||||
|
{
|
||||||
|
var data = await TokenPath.ReadAllBytesAsync();
|
||||||
|
var decoded = ProtectedData.Unprotect(data, Encoding.UTF8.GetBytes("nexusapikey"), DataProtectionScope.LocalMachine);
|
||||||
|
var apiKey = JsonSerializer.Deserialize<string>(decoded)!;
|
||||||
|
return new NexusApiState()
|
||||||
|
{
|
||||||
|
ApiKey = apiKey
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask SetToken(NexusApiState val)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask<bool> Delete()
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HaveToken()
|
||||||
|
{
|
||||||
|
return TokenPath.FileExists();
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,22 @@
|
|||||||
using Avalonia;
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Avalonia;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Wabbajack.Downloaders.Http;
|
||||||
|
using Wabbajack.DTOs;
|
||||||
|
using Wabbajack.DTOs.JsonConverters;
|
||||||
|
using Wabbajack.DTOs.Logins;
|
||||||
|
using Wabbajack.Launcher.Models;
|
||||||
|
using Wabbajack.Launcher.ViewModels;
|
||||||
|
using Wabbajack.Networking.Http;
|
||||||
|
using Wabbajack.Networking.Http.Interfaces;
|
||||||
|
using Wabbajack.Networking.NexusApi;
|
||||||
|
using Wabbajack.Paths;
|
||||||
|
using Wabbajack.RateLimiter;
|
||||||
|
|
||||||
namespace Wabbajack.Launcher;
|
namespace Wabbajack.Launcher;
|
||||||
|
|
||||||
@ -11,9 +28,40 @@ internal class Program
|
|||||||
// yet and stuff might break.
|
// yet and stuff might break.
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
|
var host = Host.CreateDefaultBuilder(Array.Empty<string>())
|
||||||
|
.ConfigureLogging(c => { c.ClearProviders(); })
|
||||||
|
.ConfigureServices((host, services) =>
|
||||||
|
{
|
||||||
|
services.AddNexusApi();
|
||||||
|
services.AddDTOConverters();
|
||||||
|
services.AddDTOSerializer();
|
||||||
|
services.AddSingleton<MainWindowViewModel>();
|
||||||
|
services.AddSingleton<HttpClient>();
|
||||||
|
services.AddSingleton<ITokenProvider<NexusApiState>, LegacyNexusApiKey>();
|
||||||
|
services.AddSingleton<HttpDownloader>();
|
||||||
|
services.AddAllSingleton<IResource, IResource<HttpClient>>(s => new Resource<HttpClient>("Web Requests", 4));
|
||||||
|
services.AddAllSingleton<IHttpDownloader, SingleThreadedDownloader>();
|
||||||
|
|
||||||
|
var version =
|
||||||
|
$"{ThisAssembly.Git.SemVer.Major}.{ThisAssembly.Git.SemVer.Major}.{ThisAssembly.Git.SemVer.Patch}{ThisAssembly.Git.SemVer.DashLabel}";
|
||||||
|
services.AddSingleton(s => new ApplicationInfo
|
||||||
|
{
|
||||||
|
ApplicationSlug = "Wabbajack",
|
||||||
|
ApplicationName = Environment.ProcessPath?.ToAbsolutePath().FileName.ToString() ?? "Wabbajack",
|
||||||
|
ApplicationSha = ThisAssembly.Git.Sha,
|
||||||
|
Platform = RuntimeInformation.ProcessArchitecture.ToString(),
|
||||||
|
OperatingSystemDescription = RuntimeInformation.OSDescription,
|
||||||
|
RuntimeIdentifier = RuntimeInformation.RuntimeIdentifier,
|
||||||
|
OSVersion = Environment.OSVersion.VersionString,
|
||||||
|
Version = version
|
||||||
|
});
|
||||||
|
}).Build();
|
||||||
|
Services = host.Services;
|
||||||
|
|
||||||
BuildAvaloniaApp()
|
BuildAvaloniaApp()
|
||||||
.StartWithClassicDesktopLifetime(args);
|
.StartWithClassicDesktopLifetime(args);
|
||||||
}
|
}
|
||||||
|
public static IServiceProvider Services { get; set; }
|
||||||
|
|
||||||
// Avalonia configuration, don't remove; also used by visual designer.
|
// Avalonia configuration, don't remove; also used by visual designer.
|
||||||
public static AppBuilder BuildAvaloniaApp()
|
public static AppBuilder BuildAvaloniaApp()
|
||||||
|
@ -7,8 +7,14 @@ using System.Linq;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ReactiveUI.Fody.Helpers;
|
using ReactiveUI.Fody.Helpers;
|
||||||
|
using Wabbajack.Compression.Zip;
|
||||||
|
using Wabbajack.Downloaders.Http;
|
||||||
|
using Wabbajack.DTOs;
|
||||||
|
using Wabbajack.DTOs.DownloadStates;
|
||||||
|
using Wabbajack.Networking.NexusApi;
|
||||||
using Wabbajack.Paths;
|
using Wabbajack.Paths;
|
||||||
using Wabbajack.Paths.IO;
|
using Wabbajack.Paths.IO;
|
||||||
|
|
||||||
@ -19,12 +25,16 @@ public class MainWindowViewModel : ViewModelBase
|
|||||||
private readonly WebClient _client = new();
|
private readonly WebClient _client = new();
|
||||||
private readonly List<string> _errors = new();
|
private readonly List<string> _errors = new();
|
||||||
|
|
||||||
private Release _version;
|
private (Version Version, long Size, Func<Task<Uri>> Uri) _version;
|
||||||
public Uri GITHUB_REPO = new("https://api.github.com/repos/wabbajack-tools/wabbajack/releases");
|
public Uri GITHUB_REPO = new("https://api.github.com/repos/wabbajack-tools/wabbajack/releases");
|
||||||
|
private readonly NexusApi _nexusApi;
|
||||||
|
private readonly HttpDownloader _downloader;
|
||||||
|
|
||||||
public MainWindowViewModel()
|
public MainWindowViewModel(NexusApi nexusApi, HttpDownloader downloader)
|
||||||
{
|
{
|
||||||
|
_nexusApi = nexusApi;
|
||||||
Status = "Checking for new versions";
|
Status = "Checking for new versions";
|
||||||
|
_downloader = downloader;
|
||||||
var tsk = CheckForUpdates();
|
var tsk = CheckForUpdates();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,13 +47,15 @@ public class MainWindowViewModel : ViewModelBase
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var releases = await GetReleases();
|
var nexusRelease = await GetNexusReleases(CancellationToken.None);
|
||||||
_version = releases.OrderByDescending(r =>
|
if (nexusRelease != default)
|
||||||
|
_version = nexusRelease;
|
||||||
|
else
|
||||||
{
|
{
|
||||||
if (r.Tag.Split(".").Length == 4 && Version.TryParse(r.Tag, out var v))
|
_version = await GetGithubRelease(CancellationToken.None);
|
||||||
return v;
|
}
|
||||||
return new Version(0, 0, 0, 0);
|
|
||||||
}).FirstOrDefault();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -51,32 +63,28 @@ public class MainWindowViewModel : ViewModelBase
|
|||||||
await FinishAndExit();
|
await FinishAndExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_version == null)
|
if (_version == default)
|
||||||
{
|
{
|
||||||
_errors.Add("Unable to parse Github releases");
|
_errors.Add("Unable to find releases");
|
||||||
await FinishAndExit();
|
await FinishAndExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
Status = "Looking for Updates";
|
Status = "Looking for Updates";
|
||||||
|
|
||||||
var base_folder = Path.Combine(Directory.GetCurrentDirectory(), _version.Tag);
|
var baseFolder = KnownFolders.CurrentDirectory.Combine(_version.Version.ToString());
|
||||||
|
|
||||||
if (File.Exists(Path.Combine(base_folder, "Wabbajack.exe"))) await FinishAndExit();
|
if (baseFolder.Combine("Wabbajack.exe").FileExists()) await FinishAndExit();
|
||||||
|
|
||||||
var asset = _version.Assets.FirstOrDefault(a => a.Name == _version.Tag + ".zip");
|
Status = $"Getting download Uri for {_version.Version}";
|
||||||
if (asset == null)
|
var uri = await _version.Uri();
|
||||||
{
|
|
||||||
_errors.Add("No zip file for release " + _version.Tag);
|
|
||||||
await FinishAndExit();
|
|
||||||
}
|
|
||||||
|
|
||||||
var wc = new WebClient();
|
var wc = new WebClient();
|
||||||
wc.DownloadProgressChanged += UpdateProgress;
|
wc.DownloadProgressChanged += UpdateProgress;
|
||||||
Status = $"Downloading {_version.Tag} ...";
|
Status = $"Downloading {_version.Version} ...";
|
||||||
byte[] data;
|
byte[] data;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
data = await wc.DownloadDataTaskAsync(asset.BrowserDownloadUrl);
|
data = await wc.DownloadDataTaskAsync(uri);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -84,7 +92,7 @@ public class MainWindowViewModel : ViewModelBase
|
|||||||
// Something went wrong so fallback to original URL
|
// Something went wrong so fallback to original URL
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
data = await wc.DownloadDataTaskAsync(asset.BrowserDownloadUrl);
|
data = await wc.DownloadDataTaskAsync(uri);
|
||||||
}
|
}
|
||||||
catch (Exception ex2)
|
catch (Exception ex2)
|
||||||
{
|
{
|
||||||
@ -96,21 +104,19 @@ public class MainWindowViewModel : ViewModelBase
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var zip = new ZipArchive(new MemoryStream(data), ZipArchiveMode.Read))
|
using var zip = new ZipArchive(new MemoryStream(data), ZipArchiveMode.Read);
|
||||||
|
foreach (var entry in zip.Entries)
|
||||||
{
|
{
|
||||||
foreach (var entry in zip.Entries)
|
Status = $"Extracting: {entry.Name}";
|
||||||
{
|
var outPath = baseFolder.Combine(entry.FullName.ToRelativePath());
|
||||||
Status = $"Extracting: {entry.Name}";
|
if (!outPath.Parent.DirectoryExists())
|
||||||
var outPath = Path.Combine(base_folder, entry.FullName);
|
outPath.Parent.CreateDirectory();
|
||||||
if (!Directory.Exists(Path.GetDirectoryName(outPath)))
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
|
||||||
|
|
||||||
if (entry.FullName.EndsWith("/") || entry.FullName.EndsWith("\\"))
|
if (entry.FullName.EndsWith("/") || entry.FullName.EndsWith("\\"))
|
||||||
continue;
|
continue;
|
||||||
await using var o = entry.Open();
|
await using var o = entry.Open();
|
||||||
await using var of = File.Create(outPath);
|
await using var of = outPath.Open(FileMode.Create, FileAccess.Write, FileShare.None);
|
||||||
await o.CopyToAsync(of);
|
await o.CopyToAsync(of);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -175,16 +181,64 @@ public class MainWindowViewModel : ViewModelBase
|
|||||||
|
|
||||||
private void UpdateProgress(object sender, DownloadProgressChangedEventArgs e)
|
private void UpdateProgress(object sender, DownloadProgressChangedEventArgs e)
|
||||||
{
|
{
|
||||||
Status = $"Downloading {_version.Tag} ({e.ProgressPercentage}%)...";
|
Status = $"Downloading {_version.Version} ({e.ProgressPercentage}%)...";
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Release[]> GetReleases()
|
private async Task<(Version Version, long Size, Func<Task<Uri>> Uri)> GetGithubRelease(CancellationToken token)
|
||||||
|
{
|
||||||
|
var releases = await GetGithubReleases();
|
||||||
|
|
||||||
|
|
||||||
|
var version = releases.Select(r =>
|
||||||
|
{
|
||||||
|
if (r.Tag.Split(".").Length == 4 && Version.TryParse(r.Tag, out var v))
|
||||||
|
return (v, r);
|
||||||
|
return (new Version(0, 0, 0, 0), r);
|
||||||
|
})
|
||||||
|
.OrderByDescending(r => r.Item1)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
var asset = version.r.Assets.FirstOrDefault(a => a.Name == version.Item1 + ".zip");
|
||||||
|
if (asset == null)
|
||||||
|
{
|
||||||
|
Status = $"Error, no asset found for Github Release {version.r}";
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (version.Item1, asset.Size, async () => asset!.BrowserDownloadUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Release[]> GetGithubReleases()
|
||||||
{
|
{
|
||||||
Status = "Checking GitHub Repository";
|
Status = "Checking GitHub Repository";
|
||||||
var data = await _client.DownloadStringTaskAsync(GITHUB_REPO);
|
var data = await _client.DownloadStringTaskAsync(GITHUB_REPO);
|
||||||
Status = "Parsing Response";
|
Status = "Parsing Response";
|
||||||
return JsonSerializer.Deserialize<Release[]>(data)!;
|
return JsonSerializer.Deserialize<Release[]>(data)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<(Version Version, long Size, Func<Task<Uri>> uri)> GetNexusReleases(CancellationToken token)
|
||||||
|
{
|
||||||
|
Status = "Checking Nexus for updates";
|
||||||
|
if (!await _nexusApi.IsPremium(token))
|
||||||
|
return default;
|
||||||
|
|
||||||
|
var data = await _nexusApi.ModFiles("site", 403, token);
|
||||||
|
Status = "Parsing Response";
|
||||||
|
//return JsonSerializer.Deserialize<Release[]>(data)!;
|
||||||
|
|
||||||
|
var found = data.info.Files.Where(f => f.CategoryId == 5)
|
||||||
|
.Where(f => f.Name.EndsWith(".zip"))
|
||||||
|
.Select(f => Version.TryParse(f.Name[..^4], out var version) ? (version, f.SizeInBytes ?? f.Size, f.FileId) : default)
|
||||||
|
.FirstOrDefault(f => f != default);
|
||||||
|
if (found == default) return default;
|
||||||
|
|
||||||
|
return (found.version, found.Item2, async () =>
|
||||||
|
{
|
||||||
|
var link = await _nexusApi.DownloadLink("site", 403, found.FileId, token);
|
||||||
|
|
||||||
|
return link.info.First().URI;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private class Release
|
private class Release
|
||||||
@ -200,5 +254,8 @@ public class MainWindowViewModel : ViewModelBase
|
|||||||
public Uri BrowserDownloadUrl { get; set; }
|
public Uri BrowserDownloadUrl { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("name")] public string Name { get; set; }
|
[JsonPropertyName("name")] public string Name { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
[JsonPropertyName("size")] public long Size { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,17 +7,24 @@
|
|||||||
<AssemblyName>Wabbajack</AssemblyName>
|
<AssemblyName>Wabbajack</AssemblyName>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Models\" />
|
|
||||||
<AvaloniaResource Include="Assets\**" />
|
<AvaloniaResource Include="Assets\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="GitInfo" Version="2.2.0">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Avalonia" Version="0.10.14" />
|
<PackageReference Include="Avalonia" Version="0.10.14" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.14" />
|
<PackageReference Include="Avalonia.Desktop" Version="0.10.14" />
|
||||||
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.14" />
|
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.14" />
|
||||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.14" />
|
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.14" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
|
||||||
<PackageReference Include="ReactiveUI.Fody" Version="18.0.10" />
|
<PackageReference Include="ReactiveUI.Fody" Version="18.0.10" />
|
||||||
|
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="6.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Wabbajack.Downloaders.Http\Wabbajack.Downloaders.Http.csproj" />
|
||||||
|
<ProjectReference Include="..\Wabbajack.Downloaders.Nexus\Wabbajack.Downloaders.Nexus.csproj" />
|
||||||
<ProjectReference Include="..\Wabbajack.Paths.IO\Wabbajack.Paths.IO.csproj" />
|
<ProjectReference Include="..\Wabbajack.Paths.IO\Wabbajack.Paths.IO.csproj" />
|
||||||
<ProjectReference Include="..\Wabbajack.Paths\Wabbajack.Paths.csproj" />
|
<ProjectReference Include="..\Wabbajack.Paths\Wabbajack.Paths.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
Loading…
Reference in New Issue
Block a user