wabbajack/Wabbajack.Services.OSIntegrated/ServiceExtensions.cs
Luca fab17a6ae0
Wabbajack 3.3.0.0 Update (#2416)
* added more visible error messages to avoid user confusion

added hard drive free space detection, added red error message text, removed overwrite checkbox, added wiki button link

extended the error text for starting wabbajack in protected location

removed debug code

shortened error message to fit in text box

* restored warning removed in error, updated changelog, removed debug includes

* Update InstallerVM.cs

* Update InstallerVM.cs

* Update MainWindowViewModel.cs

* added json optional flag to only show version number over modlist image in installer view, if the modlist image already contains the title

removed debug code

change to pascal case and match existing code style

update changelog

* Fix manual downloads sometimes launching in browser

* Fix manual downloads from secure servers

* Remove duplicate user agent code

* Create configuration project and performance settings

* Bind new performance settings to UI

* Use performance settings to limit maximum memory per download

* Remove unused settings and related classes

* Updated CHANGELOG.md

* update CHANGELOG.md

* moved the existing files popup to an error message , heralding the return of the overwrite install checkbox

* added newline

* reverted erroneous edit

* gogID for fallout4 added

* update CHANGELOG.md

* Fix deadlock when loading new settings

* change folder/directory check logic

* update CHANGELOG.md

* revert unnecessary change

* update CHANGELOG.md

* Bump Wabbajack to .NET 7

* Bump ReactiveUI packages & deps

* Update GameFinder to 4.0.0

* Update CHANGELOG.md

* Update CHANGELOG.md

---------

Co-authored-by: JanuarySnow <bobfordiscord12@gmail.com>
Co-authored-by: JanuarySnow <85711747+JanuarySnow@users.noreply.github.com>
Co-authored-by: UrbanCMC <UrbanCMC@web.de>
Co-authored-by: trawzified <55751269+tr4wzified@users.noreply.github.com>
2023-10-12 12:33:06 -06:00

268 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Wabbajack.Common;
using Wabbajack.Compiler;
using Wabbajack.Configuration;
using Wabbajack.Downloaders;
using Wabbajack.Downloaders.GameFile;
using Wabbajack.Downloaders.VerificationCache;
using Wabbajack.DTOs;
using Wabbajack.DTOs.Interventions;
using Wabbajack.DTOs.JsonConverters;
using Wabbajack.DTOs.Logins;
using Wabbajack.Hashing.PHash;
using Wabbajack.Installer;
using Wabbajack.Networking.BethesdaNet;
using Wabbajack.Networking.Discord;
using Wabbajack.Networking.Http;
using Wabbajack.Networking.Http.Interfaces;
using Wabbajack.Networking.NexusApi;
using Wabbajack.Networking.Steam;
using Wabbajack.Networking.WabbajackClientApi;
using Wabbajack.Paths;
using Wabbajack.Paths.IO;
using Wabbajack.RateLimiter;
using Wabbajack.Services.OSIntegrated.Services;
using Wabbajack.Services.OSIntegrated.TokenProviders;
using Wabbajack.VFS;
using Wabbajack.VFS.Interfaces;
using Client = Wabbajack.Networking.WabbajackClientApi.Client;
namespace Wabbajack.Services.OSIntegrated;
public static class ServiceExtensions
{
/// <summary>
/// Adds variants of services that integrate into global OS services. These are not testing
/// variants or services that require Environment variables. These are the "full fat" services.
/// </summary>
/// <returns></returns>
public static IServiceCollection AddOSIntegrated(this IServiceCollection service,
Action<OSIntegratedOptions>? cfn = null)
{
// Register app-wide cancellation token source to allow clean termination
service.AddSingleton(new CancellationTokenSource());
service.AddTransient(typeof(CancellationToken), s => s.GetRequiredService<CancellationTokenSource>().Token);
var options = new OSIntegratedOptions();
cfn?.Invoke(options);
var tempBase = KnownFolders.EntryPoint.Combine("temp");
service.AddTransient(s =>
new TemporaryFileManager(tempBase.Combine(Environment.ProcessId + "_" + Guid.NewGuid())));
Task.Run(() => CleanAllTempData(tempBase));
service.AddSingleton(s => options.UseLocalCache
? new FileHashCache(s.GetService<TemporaryFileManager>()!.CreateFile().Path,
s.GetService<IResource<FileHashCache>>()!)
: new FileHashCache(KnownFolders.AppDataLocal.Combine("Wabbajack", "GlobalHashCache.sqlite"),
s.GetService<IResource<FileHashCache>>()!));
service.AddSingleton<IVfsCache>(s =>
{
var diskCache = options.UseLocalCache
? new VFSDiskCache(s.GetService<TemporaryFileManager>()!.CreateFile().Path)
: new VFSDiskCache(KnownFolders.WabbajackAppLocal.Combine("GlobalVFSCache4.sqlite"));
var cesiCache = new CesiVFSCache(s.GetRequiredService<ILogger<CesiVFSCache>>(),
s.GetRequiredService<Client>());
return new FallthroughVFSCache(new IVfsCache[] {diskCache});
});
service.AddSingleton<IBinaryPatchCache>(s => options.UseLocalCache
? new BinaryPatchCache(s.GetRequiredService<ILogger<BinaryPatchCache>>(), s.GetService<TemporaryFileManager>()!.CreateFolder().Path)
: new BinaryPatchCache(s.GetRequiredService<ILogger<BinaryPatchCache>>(),KnownFolders.WabbajackAppLocal.Combine("PatchCache")));
service.AddSingleton<IVerificationCache>(s =>
{
var dtos = s.GetRequiredService<DTOSerializer>();
return options.UseLocalCache
? new VerificationCache(s.GetRequiredService<ILogger<VerificationCache>>(),
s.GetService<TemporaryFileManager>()!.CreateFile().Path,
TimeSpan.FromDays(1),
dtos)
: new VerificationCache(s.GetRequiredService<ILogger<VerificationCache>>(),
KnownFolders.WabbajackAppLocal.Combine("VerificationCacheV2.sqlite"),
TimeSpan.FromDays(1),
dtos);
});
service.AddSingleton(new ParallelOptions {MaxDegreeOfParallelism = Environment.ProcessorCount});
MainSettings GetAppSettings(IServiceProvider provider, string name)
{
var settingsManager = provider.GetRequiredService<SettingsManager>();
var settings = Task.Run(() => settingsManager.Load<MainSettings>(name)).Result;
if (settings.Upgrade())
{
settingsManager.Save(MainSettings.SettingsFileName, settings).FireAndForget();
}
return settings;
}
Func<Task<(int MaxTasks, long MaxThroughput)>> GetResourceSettings(IServiceProvider provider, string name)
{
return async () =>
{
var s = await provider.GetService<ResourceSettingsManager>()!.GetSettings(name);
return ((int) s.MaxTasks, s.MaxThroughput);
};
}
// Settings
service.AddSingleton(s => new Configuration
{
EncryptedDataLocation = KnownFolders.WabbajackAppLocal.Combine("encrypted"),
ModListsDownloadLocation = KnownFolders.EntryPoint.Combine("downloaded_mod_lists"),
SavedSettingsLocation = KnownFolders.WabbajackAppLocal.Combine("saved_settings"),
LogLocation = KnownFolders.LauncherAwarePath.Combine("logs"),
ImageCacheLocation = KnownFolders.WabbajackAppLocal.Combine("image_cache")
});
service.AddSingleton<SettingsManager>();
service.AddSingleton<ResourceSettingsManager>();
service.AddSingleton<MainSettings>(s => GetAppSettings(s, MainSettings.SettingsFileName));
// Resources
service.AddAllSingleton<IResource, IResource<DownloadDispatcher>>(s =>
new Resource<DownloadDispatcher>("Downloads", GetResourceSettings(s, "Downloads"), s.GetRequiredService<CancellationToken>()));
service.AddAllSingleton<IResource, IResource<HttpClient>>(s => new Resource<HttpClient>("Web Requests", GetResourceSettings(s, "Web Requests"), s.GetRequiredService<CancellationToken>()));
service.AddAllSingleton<IResource, IResource<Context>>(s => new Resource<Context>("VFS", GetResourceSettings(s, "VFS"), s.GetRequiredService<CancellationToken>()));
service.AddAllSingleton<IResource, IResource<FileHashCache>>(s =>
new Resource<FileHashCache>("File Hashing", GetResourceSettings(s, "File Hashing"), s.GetRequiredService<CancellationToken>()));
service.AddAllSingleton<IResource, IResource<Client>>(s =>
new Resource<Client>("Wabbajack Client", GetResourceSettings(s, "Wabbajack Client"), s.GetRequiredService<CancellationToken>()));
service.AddAllSingleton<IResource, IResource<FileExtractor.FileExtractor>>(s =>
new Resource<FileExtractor.FileExtractor>("File Extractor", GetResourceSettings(s, "File Extractor"), s.GetRequiredService<CancellationToken>()));
service.AddAllSingleton<IResource, IResource<ACompiler>>(s =>
new Resource<ACompiler>("Compiler", GetResourceSettings(s, "Compiler"), s.GetRequiredService<CancellationToken>()));
service.AddAllSingleton<IResource, IResource<IInstaller>>(s =>
new Resource<IInstaller>("Installer", GetResourceSettings(s, "Installer"), s.GetRequiredService<CancellationToken>()));
service.AddAllSingleton<IResource, IResource<IUserInterventionHandler>>(s =>
new Resource<IUserInterventionHandler>("User Intervention", 1, token: s.GetRequiredService<CancellationToken>()));
service.AddSingleton<LoggingRateLimiterReporter>();
service.AddScoped<Context>();
service.AddSingleton<FileExtractor.FileExtractor>();
service.AddSingleton<ModListDownloadMaintainer>();
// Networking
service.AddSingleton<HttpClient>();
service.AddAllSingleton<IHttpDownloader, SingleThreadedDownloader>();
service.AddSteam();
service.AddSingleton<Client>();
service.AddSingleton<WriteOnlyClient>();
service.AddBethesdaNet();
// Token Providers
service.AddAllSingleton<ITokenProvider<NexusApiState>, EncryptedJsonTokenProvider<NexusApiState>, NexusApiTokenProvider>();
service.AddAllSingleton<ITokenProvider<BethesdaNetLoginState>, EncryptedJsonTokenProvider<BethesdaNetLoginState>, BethesdaNetTokenProvider>();
service
.AddAllSingleton<ITokenProvider<LoversLabLoginState>, EncryptedJsonTokenProvider<LoversLabLoginState>,
LoversLabTokenProvider>();
service
.AddAllSingleton<ITokenProvider<VectorPlexusLoginState>, EncryptedJsonTokenProvider<VectorPlexusLoginState>,
VectorPlexusTokenProvider>();
service
.AddAllSingleton<ITokenProvider<SteamLoginState>, EncryptedJsonTokenProvider<SteamLoginState>,
SteamTokenProvider>();
service.AddAllSingleton<ITokenProvider<WabbajackApiState>, WabbajackApiTokenProvider>();
service
.AddAllSingleton<ITokenProvider<Dictionary<Channel, DiscordWebHookSetting>>,
EncryptedJsonTokenProvider<Dictionary<Channel, DiscordWebHookSetting>>, DiscordTokenProvider>();
service.AddAllSingleton<NexusApi, ProxiedNexusApi>();
service.AddDownloadDispatcher();
if (options.UseStubbedGameFolders)
service.AddAllSingleton<IGameLocator, StubbedGameLocator>();
else
service.AddAllSingleton<IGameLocator, GameLocator>();
// ImageLoader
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
service.AddSingleton<IImageLoader, TexConvImageLoader>();
else
service.AddSingleton<IImageLoader, CrossPlatformImageLoader>();
// Installer/Compiler Configuration
service.AddScoped<InstallerConfiguration>();
service.AddScoped<StandardInstaller>();
service.AddScoped<CompilerSettings>();
service.AddScoped<MO2Compiler>();
service.AddSingleton<CompilerSettingsInferencer>();
// Application Info
var version =
$"{ThisAssembly.Git.SemVer.Major}.{ThisAssembly.Git.SemVer.Major}.{ThisAssembly.Git.SemVer.Patch}{ThisAssembly.Git.SemVer.DashLabel}";
service.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
});
return service;
}
private static void CleanAllTempData(AbsolutePath path)
{
// Get directories first and cache them, this freezes the directories were looking at
// so any new ones don't show up in the middle of our deletes.
var dirs = path.EnumerateDirectories().ToList();
var processIds = Process.GetProcesses().Select(p => p.Id).ToHashSet();
foreach (var dir in dirs)
{
var name = dir.FileName.ToString().Split("_");
if (!int.TryParse(name[0], out var processId)) continue;
if (processIds.Contains(processId)) continue;
try
{
dir.DeleteDirectory();
}
catch (Exception)
{
// ignored
}
}
}
public class OSIntegratedOptions
{
public bool UseLocalCache { get; set; } = false;
public bool UseStubbedGameFolders { get; set; } = false;
}
}