using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net.Http; using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Wabbajack.App.Models; using Wabbajack.Compiler; using Wabbajack.Downloaders; using Wabbajack.Downloaders.GameFile; using Wabbajack.DTOs; using Wabbajack.DTOs.Interventions; using Wabbajack.DTOs.Logins; 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 { /// /// 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. /// /// public static IServiceCollection AddOSIntegrated(this IServiceCollection service, Action? cfn = null) { 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()!.CreateFile().Path, s.GetService>()!) : new FileHashCache(KnownFolders.AppDataLocal.Combine("Wabbajack", "GlobalHashCache.sqlite"), s.GetService>()!)); service.AddSingleton(s => { var diskCache = options.UseLocalCache ? new VFSDiskCache(s.GetService()!.CreateFile().Path) : new VFSDiskCache(KnownFolders.WabbajackAppLocal.Combine("GlobalVFSCache3.sqlite")); var cesiCache = new CesiVFSCache(s.GetRequiredService>(), s.GetRequiredService()); return new FallthroughVFSCache(new IVfsCache[] {diskCache}); }); service.AddSingleton(s => options.UseLocalCache ? new BinaryPatchCache(s.GetService()!.CreateFile().Path) : new BinaryPatchCache(KnownFolders.EntryPoint.Combine("patchCache.sqlite"))); service.AddSingleton(new ParallelOptions {MaxDegreeOfParallelism = Environment.ProcessorCount}); Func> GetSettings(IServiceProvider provider, string name) { return async () => { var s = await provider.GetService()!.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.EntryPoint.Combine("logs"), ImageCacheLocation = KnownFolders.WabbajackAppLocal.Combine("image_cache") }); service.AddSingleton(); service.AddSingleton(); // Resources service.AddAllSingleton>(s => new Resource("Downloads", GetSettings(s, "Downloads"))); service.AddAllSingleton>(s => new Resource("Web Requests", GetSettings(s, "Web Requests"))); service.AddAllSingleton>(s => new Resource("VFS", GetSettings(s, "VFS"))); service.AddAllSingleton>(s => new Resource("File Hashing", GetSettings(s, "File Hashing"))); service.AddAllSingleton>(s => new Resource("Wabbajack Client", GetSettings(s, "Wabbajack Client"))); service.AddAllSingleton>(s => new Resource("File Extractor", GetSettings(s, "File Extractor"))); service.AddAllSingleton>(s => new Resource("Compiler", GetSettings(s, "Compiler"))); service.AddAllSingleton>(s => new Resource("Installer", GetSettings(s, "Installer"))); service.AddAllSingleton>(s => new Resource("User Intervention", 1)); service.AddSingleton(); service.AddScoped(); service.AddSingleton(); service.AddSingleton(); // Networking service.AddSingleton(); service.AddAllSingleton(); service.AddSteam(); service.AddSingleton(); service.AddSingleton(); service.AddBethesdaNet(); // Token Providers service.AddAllSingleton, EncryptedJsonTokenProvider, NexusApiTokenProvider>(); service.AddAllSingleton, EncryptedJsonTokenProvider, BethesdaNetTokenProvider>(); service .AddAllSingleton, EncryptedJsonTokenProvider, LoversLabTokenProvider>(); service .AddAllSingleton, EncryptedJsonTokenProvider, VectorPlexusTokenProvider>(); service .AddAllSingleton, EncryptedJsonTokenProvider, SteamTokenProvider>(); service.AddAllSingleton, WabbajackApiTokenProvider>(); service .AddAllSingleton>, EncryptedJsonTokenProvider>, DiscordTokenProvider>(); service.AddAllSingleton(); service.AddDownloadDispatcher(); if (options.UseStubbedGameFolders) service.AddAllSingleton(); else service.AddAllSingleton(); // Installer/Compiler Configuration service.AddScoped(); service.AddScoped(); service.AddScoped(); service.AddScoped(); service.AddSingleton(); // 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; } }