diff --git a/.editorconfig b/.editorconfig index 5b46bd60..24eda6c4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -184,4 +184,10 @@ dotnet_diagnostic.CS8609.severity = error dotnet_diagnostic.CS8714.severity = error # CS8605: Unboxing a possibly null value. -dotnet_diagnostic.CS8605.severity = error \ No newline at end of file +dotnet_diagnostic.CS8605.severity = error + +# CS8613: Nullability of reference types in return type doesn't match implicitly implemented member. +dotnet_diagnostic.CS8613.severity = error + +# CS8632: The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. +dotnet_diagnostic.CS8632.severity = error \ No newline at end of file diff --git a/Wabbajack.BuildServer.Test/IndexedFilesTests.cs b/Wabbajack.BuildServer.Test/IndexedFilesTests.cs index e8d594bc..c7af0997 100644 --- a/Wabbajack.BuildServer.Test/IndexedFilesTests.cs +++ b/Wabbajack.BuildServer.Test/IndexedFilesTests.cs @@ -53,14 +53,14 @@ namespace Wabbajack.BuildServer.Test public async Task CanNotifyOfInis() { var archive = - new Archive - { - State = new NexusDownloader.State + new Archive( + new NexusDownloader.State { Game = Game.SkyrimSpecialEdition, ModID = long.MaxValue >> 3, FileID = long.MaxValue >> 3, - }, + }) + { Name = Guid.NewGuid().ToString() }; Assert.True(await AuthorAPI.UploadPackagedInis(new[] {archive})); diff --git a/Wabbajack.BuildServer.Test/ModListValidationTests.cs b/Wabbajack.BuildServer.Test/ModListValidationTests.cs index 6afbd7db..34cbd080 100644 --- a/Wabbajack.BuildServer.Test/ModListValidationTests.cs +++ b/Wabbajack.BuildServer.Test/ModListValidationTests.cs @@ -190,19 +190,14 @@ namespace Wabbajack.BuildServer.Test - ModListData = new ModList - { - Archives = new List + ModListData = new ModList(); + ModListData.Archives.Add( + new Archive(new HTTPDownloader.State(MakeURL("test_archive.txt"))) { - new Archive - { - Hash = await test_archive_path.FileHashAsync(), - Name = "test_archive", - Size = test_archive_path.Size, - State = new HTTPDownloader.State {Url = MakeURL("test_archive.txt")} - } - } - }; + Hash = await test_archive_path.FileHashAsync(), + Name = "test_archive", + Size = test_archive_path.Size, + }); var modListPath = "test_modlist.wabbajack".RelativeTo(Fixture.ServerPublicFolder); diff --git a/Wabbajack.BuildServer.Test/ModlistUpdater.cs b/Wabbajack.BuildServer.Test/ModlistUpdater.cs index 3295ea96..3a9a5f5d 100644 --- a/Wabbajack.BuildServer.Test/ModlistUpdater.cs +++ b/Wabbajack.BuildServer.Test/ModlistUpdater.cs @@ -42,13 +42,9 @@ namespace Wabbajack.BuildServer.Test { Payload = new IndexJob { - Archive = new Archive + Archive = new Archive(new HTTPDownloader.State(MakeURL("old_file_data.random"))) { Name = "Oldfile", - State = new HTTPDownloader.State - { - Url = MakeURL("old_file_data.random"), - } } } }); @@ -57,13 +53,9 @@ namespace Wabbajack.BuildServer.Test { Payload = new IndexJob { - Archive = new Archive + Archive = new Archive(new HTTPDownloader.State(MakeURL("new_file_data.random"))) { Name = "Newfile", - State = new HTTPDownloader.State - { - Url = MakeURL("new_file_data.random"), - } } } }); @@ -126,6 +118,5 @@ namespace Wabbajack.BuildServer.Test Assert.True($"{oldDataHash.ToHex()}_{newDataHash.ToHex()}".RelativeTo(Fixture.ServerUpdatesFolder).IsFile); } - } } diff --git a/Wabbajack.BuildServer/Controllers/IndexedFiles.cs b/Wabbajack.BuildServer/Controllers/IndexedFiles.cs index 6db8b742..30587e1a 100644 --- a/Wabbajack.BuildServer/Controllers/IndexedFiles.cs +++ b/Wabbajack.BuildServer/Controllers/IndexedFiles.cs @@ -102,10 +102,9 @@ namespace Wabbajack.BuildServer.Controllers Priority = Job.JobPriority.Low, Payload = new IndexJob { - Archive = new Archive + Archive = new Archive(data) { Name = entry.Name, - State = data } } }); diff --git a/Wabbajack.BuildServer/Controllers/Jobs.cs b/Wabbajack.BuildServer/Controllers/Jobs.cs index 210bf318..875ad9c3 100644 --- a/Wabbajack.BuildServer/Controllers/Jobs.cs +++ b/Wabbajack.BuildServer/Controllers/Jobs.cs @@ -22,7 +22,7 @@ namespace Wabbajack.BuildServer.Controllers public async Task EnqueueJob(string JobName) { var jobtype = AJobPayload.NameToType[JobName]; - var job = new Job{Priority = Job.JobPriority.High, Payload = (AJobPayload)jobtype.GetConstructor(new Type[0]).Invoke(new object?[0])}; + var job = new Job{Priority = Job.JobPriority.High, Payload = (AJobPayload)jobtype.GetConstructor(new Type[0]).Invoke(new object[0])}; await SQL.EnqueueJob(job); return job.Id; } diff --git a/Wabbajack.BuildServer/Controllers/ModlistUpdater.cs b/Wabbajack.BuildServer/Controllers/ModlistUpdater.cs index 9a0fe1a7..b0acf557 100644 --- a/Wabbajack.BuildServer/Controllers/ModlistUpdater.cs +++ b/Wabbajack.BuildServer/Controllers/ModlistUpdater.cs @@ -220,15 +220,19 @@ namespace Wabbajack.BuildServer.Controllers var allMods = await api.GetModFiles(state.Game, state.ModID); var archive = allMods.files.Where(m => !string.IsNullOrEmpty(m.category_name)) .OrderBy(s => Math.Abs((long)s.size - origSize)) - .Select(s => new Archive { - Name = s.file_name, - Size = (long)s.size, - State = new NexusDownloader.State + .Select(s => + new Archive( + new NexusDownloader.State + { + Game = state.Game, + ModID = state.ModID, + FileID = s.file_id + }) { - Game = state.Game, - ModID = state.ModID, - FileID = s.file_id - }}).FirstOrDefault(); + Name = s.file_name, + Size = (long)s.size, + }) + .FirstOrDefault(); if (archive == null) { diff --git a/Wabbajack.BuildServer/Models/Jobs/EnqueueAllGameFiles.cs b/Wabbajack.BuildServer/Models/Jobs/EnqueueAllGameFiles.cs index 003094bf..f3012cd5 100644 --- a/Wabbajack.BuildServer/Models/Jobs/EnqueueAllGameFiles.cs +++ b/Wabbajack.BuildServer/Models/Jobs/EnqueueAllGameFiles.cs @@ -21,13 +21,12 @@ namespace Wabbajack.BuildServer.Models.Jobs { Utils.Log($"Indexing game files"); var states = GameRegistry.Games.Values - .Where(game => game.GameLocation() != null && game.MainExecutable != null) - .SelectMany(game => game.GameLocation().Value.EnumerateFiles() - .Select(file => new GameFileSourceDownloader.State + .Where(game => game.TryGetGameLocation() != null && game.MainExecutable != null) + .SelectMany(game => game.GameLocation().EnumerateFiles() + .Select(file => new GameFileSourceDownloader.State(game.InstalledVersion) { Game = game.Game, - GameVersion = game.InstalledVersion, - GameFile = file.RelativeTo(game.GameLocation().Value), + GameFile = file.RelativeTo(game.GameLocation()), })) .ToList(); @@ -41,7 +40,7 @@ namespace Wabbajack.BuildServer.Models.Jobs await states.PMap(queue, async state => { - var path = state.Game.MetaData().GameLocation().Value.Combine(state.GameFile); + var path = state.Game.MetaData().GameLocation().Combine(state.GameFile); Utils.Log($"Hashing Game file {path}"); try { @@ -55,16 +54,15 @@ namespace Wabbajack.BuildServer.Models.Jobs var with_hash = states.Where(state => state.Hash != default).ToList(); Utils.Log($"Inserting {with_hash.Count} jobs."); - var jobs = states.Select(state => new IndexJob {Archive = new Archive {Name = state.GameFile.FileName.ToString(), State = state}}) + var jobs = states.Select(state => new IndexJob {Archive = new Archive(state) { Name = state.GameFile.FileName.ToString()}}) .Select(j => new Job {Payload = j, RequiresNexus = j.UsesNexus}) .ToList(); foreach (var job in jobs) - await sql.EnqueueJob(job); + await sql.EnqueueJob(job); return JobResult.Success(); } - } protected override IEnumerable PrimaryKey => new object[0]; diff --git a/Wabbajack.BuildServer/Models/Jobs/IndexDynDOLOD.cs b/Wabbajack.BuildServer/Models/Jobs/IndexDynDOLOD.cs index b61450f2..c04a4af0 100644 --- a/Wabbajack.BuildServer/Models/Jobs/IndexDynDOLOD.cs +++ b/Wabbajack.BuildServer/Models/Jobs/IndexDynDOLOD.cs @@ -39,13 +39,9 @@ namespace Wabbajack.BuildServer.Models.Jobs { Payload = new IndexJob { - Archive = new Archive + Archive = new Archive(new MegaDownloader.State(url.ToString())) { Name = Guid.NewGuid() + ".7z", - State = new MegaDownloader.State - { - Url = url.ToString() - } } } }) diff --git a/Wabbajack.BuildServer/Models/Jobs/UploadToCDN.cs b/Wabbajack.BuildServer/Models/Jobs/UploadToCDN.cs index 1460a233..c80c2c91 100644 --- a/Wabbajack.BuildServer/Models/Jobs/UploadToCDN.cs +++ b/Wabbajack.BuildServer/Models/Jobs/UploadToCDN.cs @@ -57,15 +57,11 @@ namespace Wabbajack.BuildServer.Models.Jobs Priority = Job.JobPriority.High, Payload = new IndexJob { - Archive = new Archive + Archive = new Archive(new HTTPDownloader.State(file.Uri)) { Name = file.MungedName, Size = file.Size, Hash = file.Hash, - State = new HTTPDownloader.State - { - Url = file.Uri - } } } }); diff --git a/Wabbajack.BuildServer/Models/Sql/SqlService.cs b/Wabbajack.BuildServer/Models/Sql/SqlService.cs index 4a541cb8..8b2107ad 100644 --- a/Wabbajack.BuildServer/Models/Sql/SqlService.cs +++ b/Wabbajack.BuildServer/Models/Sql/SqlService.cs @@ -541,9 +541,8 @@ namespace Wabbajack.BuildServer.Model.Models var result = await conn.QueryFirstOrDefaultAsync(@"SELECT JsonState, Size FROM dbo.DownloadStates WHERE Hash = @hash AND PrimaryKey like 'NexusDownloader+State|%'", new {Hash = (long)startingHash}); - return result == null ? null : new Archive + return result == null ? null : new Archive(result.FromJsonString()) { - State = result.FromJsonString(), Hash = startingHash }; } @@ -555,9 +554,8 @@ namespace Wabbajack.BuildServer.Model.Models LEFT JOIN dbo.IndexedFile indexed ON indexed.Hash = state.Hash WHERE state.Hash = @hash", new {Hash = (long)startingHash}); - return result == default ? null : new Archive + return result == default ? null : new Archive(result.Item1.FromJsonString()) { - State = result.Item1.FromJsonString(), Hash = startingHash, Size = result.Item2 }; @@ -568,9 +566,8 @@ namespace Wabbajack.BuildServer.Model.Models await using var conn = await Open(); var result = await conn.QueryFirstOrDefaultAsync<(long Hash, string State)>(@"SELECT Hash, JsonState FROM dbo.DownloadStates WHERE PrimaryKey = @PrimaryKey", new {PrimaryKey = primaryKey}); - return result == default ? null : new Archive + return result == default ? null : new Archive(result.State.FromJsonString()) { - State = result.State.FromJsonString(), Hash = Hash.FromLong(result.Hash) }; } @@ -688,10 +685,11 @@ namespace Wabbajack.BuildServer.Model.Models await using var conn = await Open(); var results = await conn.QueryAsync<(Hash Hash, long Size, string State)>( @"SELECT Hash, Size, State FROM dbo.ModListArchives WHERE PrimaryKeyString NOT LIKE 'NexusDownloader+State|%'"); - return results.Select(r => new Archive { + return results.Select(r => new Archive (r.State.FromJsonString()) + { Size = r.Size, Hash = r.Hash, - State = r.State.FromJsonString() + }).ToList();} public async Task UpdateNonNexusModlistArchivesStatus(IEnumerable<(Archive Archive, bool IsValid)> results) diff --git a/Wabbajack.CLI/Verbs/Changelog.cs b/Wabbajack.CLI/Verbs/Changelog.cs index 8ed0462d..63549a01 100644 --- a/Wabbajack.CLI/Verbs/Changelog.cs +++ b/Wabbajack.CLI/Verbs/Changelog.cs @@ -318,7 +318,7 @@ namespace Wabbajack.CLI.Verbs private static async Task GetTextFileFromModlist(AbsolutePath archive, ModList modlist, RelativePath sourceID) { - var installer = new MO2Installer(archive, modlist, default, default, null); + var installer = new MO2Installer(archive, modlist, default, default, parameters: null!); byte[] bytes = await installer.LoadBytesFromPath(sourceID); return Encoding.Default.GetString(bytes); } @@ -328,9 +328,9 @@ namespace Wabbajack.CLI.Verbs return header.Trim().Replace(" ", "").Replace(".", ""); } - private static string GetModName(Archive a) + private static string? GetModName(Archive a) { - var result = a.Name; + string? result = a.Name; if (a.State is IMetaState metaState) { diff --git a/Wabbajack.CLI/Verbs/DeleteFile.cs b/Wabbajack.CLI/Verbs/DeleteFile.cs index 2b48617e..a0de0efa 100644 --- a/Wabbajack.CLI/Verbs/DeleteFile.cs +++ b/Wabbajack.CLI/Verbs/DeleteFile.cs @@ -9,7 +9,7 @@ namespace Wabbajack.CLI.Verbs public class DeleteFile : AVerb { [Option('n', "name", Required = true, HelpText = @"Full name (as returned by my-files) of the file")] - public string? Name { get; set; } + public string Name { get; set; } = null!; protected override async Task Run() { diff --git a/Wabbajack.CLI/Verbs/DownloadUrl.cs b/Wabbajack.CLI/Verbs/DownloadUrl.cs index f6a849d8..6ac806c4 100644 --- a/Wabbajack.CLI/Verbs/DownloadUrl.cs +++ b/Wabbajack.CLI/Verbs/DownloadUrl.cs @@ -35,7 +35,7 @@ namespace Wabbajack.CLI.Verbs new[] {state} .PMap(queue, async s => { - await s.Download(new Archive {Name = Path.GetFileName(Output)}, (AbsolutePath)Output); + await s.Download(new Archive(state: null!) {Name = Path.GetFileName(Output)}, (AbsolutePath)Output); }).Wait(); File.WriteAllLines(Output + ".meta", state.GetMetaIni()); diff --git a/Wabbajack.Common/Extensions/DictionaryExt.cs b/Wabbajack.Common/Extensions/DictionaryExt.cs index 64ea3f03..89af60a1 100644 --- a/Wabbajack.Common/Extensions/DictionaryExt.cs +++ b/Wabbajack.Common/Extensions/DictionaryExt.cs @@ -21,6 +21,9 @@ namespace Wabbajack return ret; } + /// + /// Adds the given values to the dictionary. If a key already exists, it will throw an exception + /// public static void Add(this IDictionary dict, IEnumerable> vals) where K : notnull { @@ -30,6 +33,9 @@ namespace Wabbajack } } + /// + /// Adds the given values to the dictionary. If a key already exists, it will be replaced + /// public static void Set(this IDictionary dict, IEnumerable> vals) where K : notnull { @@ -38,5 +44,15 @@ namespace Wabbajack dict[val.Key] = val.Value; } } + + /// + /// Clears the dictionary and adds the given values + /// + public static void SetTo(this IDictionary dict, IEnumerable> vals) + where K : notnull + { + dict.Clear(); + dict.Set(vals); + } } } diff --git a/Wabbajack.Common/Extensions/EnumerableExt.cs b/Wabbajack.Common/Extensions/EnumerableExt.cs index e93b6c03..50dfb778 100644 --- a/Wabbajack.Common/Extensions/EnumerableExt.cs +++ b/Wabbajack.Common/Extensions/EnumerableExt.cs @@ -33,5 +33,32 @@ namespace Wabbajack yield return next; foreach (var itm in coll) yield return itm; } + + /// + /// Converts and filters a nullable enumerable to a non-nullable enumerable + /// + public static IEnumerable NotNull(this IEnumerable e) + where T : class + { + // Filter out nulls + return e.Where(e => e != null) + // Cast to non nullable type + .Select(e => e!); + } + + /// + /// Selects items that are castable to the desired type + /// + /// Type of the original enumerable to cast from + /// Type to attempt casting to + /// Enumerable to process + /// Enumerable with only objects that were castable + public static IEnumerable WhereCastable(this IEnumerable e) + where T : class + where R : T + { + return e.Where(e => e is R) + .Select(e => (R)e); + } } } diff --git a/Wabbajack.Common/Extensions/ListExt.cs b/Wabbajack.Common/Extensions/ListExt.cs new file mode 100644 index 00000000..0aa872c5 --- /dev/null +++ b/Wabbajack.Common/Extensions/ListExt.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Wabbajack.Common +{ + public static class ListExt + { + public static void SetTo(this List list, IEnumerable rhs) + { + list.Clear(); + list.AddRange(rhs); + } + } +} diff --git a/Wabbajack.Common/GameMetaData.cs b/Wabbajack.Common/GameMetaData.cs index 80b70bb5..752419b0 100644 --- a/Wabbajack.Common/GameMetaData.cs +++ b/Wabbajack.Common/GameMetaData.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Alphaleonis.Win32.Filesystem; using Microsoft.Win32; @@ -20,6 +21,7 @@ namespace Wabbajack.Common FalloutNewVegas, [Description("Skyrim Legendary Edition")] Skyrim, + Enderal, [Description("Skyrim Special Edition")] SkyrimSpecialEdition, [Description("Fallout 4")] @@ -85,23 +87,44 @@ namespace Wabbajack.Common { get { - AbsolutePath? gameLoc = GameLocation(); - if (gameLoc == null) + if (!TryGetGameLocation(out var gameLoc)) throw new GameNotInstalledException(this); if (MainExecutable == null) throw new NotImplementedException(); - return FileVersionInfo.GetVersionInfo((string)gameLoc.Value.Combine(MainExecutable)).ProductVersion; + return FileVersionInfo.GetVersionInfo((string)gameLoc.Combine(MainExecutable)).ProductVersion; } } - public bool IsInstalled => GameLocation() != null; + public bool IsInstalled => TryGetGameLocation() != null; public string? MainExecutable { get; internal set; } - public AbsolutePath? GameLocation() + public AbsolutePath? TryGetGameLocation() { - return Consts.TestMode ? AbsolutePath.GetCurrentDirectory() : StoreHandler.Instance.GetGamePath(Game); + return Consts.TestMode ? AbsolutePath.GetCurrentDirectory() : StoreHandler.Instance.TryGetGamePath(Game); + } + + public bool TryGetGameLocation(out AbsolutePath path) + { + var ret = TryGetGameLocation(); + if (ret != null) + { + path = ret.Value; + return true; + } + else + { + path = default; + return false; + } + } + + public AbsolutePath GameLocation() + { + var ret = TryGetGameLocation(); + if (ret == null) throw new ArgumentNullException(); + return ret.Value; } } @@ -156,16 +179,24 @@ namespace Wabbajack.Common } /// - /// Tries to parse game data from an arbitrary string. Tries first via parsing as a game Enum, then by Nexus name, - /// - /// - public static GameMetaData? GetByFuzzyName(string someName) + /// Parse game data from an arbitrary string. Tries first via parsing as a game Enum, then by Nexus name. + /// Name to query + /// GameMetaData found + /// If string could not be translated to a game + public static GameMetaData GetByFuzzyName(string someName) + { + return TryGetByFuzzyName(someName) ?? throw new ArgumentNullException($"{someName} could not be translated to a game"); + } + + /// + /// Tries to parse game data from an arbitrary string. Tries first via parsing as a game Enum, then by Nexus name. + /// Name to query + /// GameMetaData if found, otherwise null + public static GameMetaData? TryGetByFuzzyName(string someName) { if (Enum.TryParse(typeof(Game), someName, true, out var metadata)) return ((Game)metadata!).MetaData(); - GameMetaData? result = null; - - result = GetByNexusName(someName); + GameMetaData? result = GetByNexusName(someName); if (result != null) return result; result = GetByMO2ArchiveName(someName); @@ -174,6 +205,19 @@ namespace Wabbajack.Common return int.TryParse(someName, out int id) ? GetBySteamID(id) : null; } + public static bool TryGetByFuzzyName(string someName, [MaybeNullWhen(false)] out GameMetaData gameMetaData) + { + var result = TryGetByFuzzyName(someName); + if (result == null) + { + gameMetaData = Games.Values.First(); + return false; + } + + gameMetaData = result; + return true; + } + public static IReadOnlyDictionary Games = new Dictionary { { @@ -497,6 +541,22 @@ namespace Wabbajack.Common "Stardew Valley.exe" } } + }, + { + Game.Enderal, new GameMetaData + { + SupportedModManager = ModManager.MO2, + Game = Game.Enderal, + NexusName = "enderal", + MO2Name = "Enderal", + MO2ArchiveName = "enderal", + SteamIDs = new List{1027920}, + RequiredFiles = new List + { + "TESV.exe" + }, + MainExecutable = "TESV.exe" + } } }; diff --git a/Wabbajack.Common/Http/Client.cs b/Wabbajack.Common/Http/Client.cs index 098f8866..3a7bfb0a 100644 --- a/Wabbajack.Common/Http/Client.cs +++ b/Wabbajack.Common/Http/Client.cs @@ -11,7 +11,7 @@ namespace Wabbajack.Common.Http { public class Client { - public List<(string, string)> Headers = new List<(string, string)>(); + public List<(string, string?)> Headers = new List<(string, string?)>(); public List Cookies = new List(); public async Task GetAsync(string url, HttpCompletionOption responseHeadersRead = HttpCompletionOption.ResponseHeadersRead) { diff --git a/Wabbajack.Common/Json.cs b/Wabbajack.Common/Json.cs index afde444d..0963475f 100644 --- a/Wabbajack.Common/Json.cs +++ b/Wabbajack.Common/Json.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.IO; using System.Linq; using System.Reflection; using System.Text; -using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Wabbajack.Common.Serialization.Json; @@ -19,7 +17,7 @@ namespace Wabbajack.Common { new HashJsonConverter(), new RelativePathConverter(), - new AbolutePathConverter(), + new AbsolutePathConverter(), new HashRelativePathConverter(), new FullPathConverter(), new GameConverter(), @@ -33,7 +31,7 @@ namespace Wabbajack.Common Converters = Converters}; public static JsonSerializerSettings GenericJsonSettings => - new JsonSerializerSettings { }; + new JsonSerializerSettings(); public static void ToJson(this T obj, string filename) @@ -62,16 +60,12 @@ namespace Wabbajack.Common return JsonConvert.SerializeObject(obj, JsonSettings); } - public static T FromJson(this AbsolutePath filename, - TypeNameHandling handling = TypeNameHandling.All, - TypeNameAssemblyFormatHandling format = TypeNameAssemblyFormatHandling.Full) + public static T FromJson(this AbsolutePath filename) { return JsonConvert.DeserializeObject(filename.ReadAllText(), JsonSettings)!; } - public static T FromJsonString(this string data, - TypeNameHandling handling = TypeNameHandling.Objects, - TypeNameAssemblyFormatHandling format = TypeNameAssemblyFormatHandling.Full) + public static T FromJsonString(this string data) { return JsonConvert.DeserializeObject(data, JsonSettings)!; } @@ -86,8 +80,6 @@ namespace Wabbajack.Common throw new JsonException("Type deserialized into null"); return result; } - - private class HashJsonConverter : JsonConverter { @@ -118,7 +110,7 @@ namespace Wabbajack.Common } } - private class AbolutePathConverter : JsonConverter + private class AbsolutePathConverter : JsonConverter { public override void WriteJson(JsonWriter writer, AbsolutePath value, JsonSerializer serializer) { @@ -232,8 +224,7 @@ namespace Wabbajack.Common return (Game)i; } - GameMetaData? game = GameRegistry.GetByFuzzyName(str); - if (game == null) + if (!GameRegistry.TryGetByFuzzyName(str, out var game)) { throw new ArgumentException($"Could not convert {str} to a Game type."); } @@ -248,11 +239,11 @@ namespace Wabbajack.Common { private static Dictionary _nameToType = new Dictionary(); private static Dictionary _typeToName = new Dictionary(); - private static bool _inited = false; + private static bool _init; public JsonNameSerializationBinder() { - if (_inited) + if (_init) return; var customDisplayNameTypes = @@ -282,7 +273,7 @@ namespace Wabbajack.Common _typeToName = _nameToType.ToDictionary( t => t.Value, t => t.Key); - _inited = true; + _init = true; } diff --git a/Wabbajack.Common/StoreHandlers/SteamHandler.cs b/Wabbajack.Common/StoreHandlers/SteamHandler.cs index c84e7794..6f9af723 100644 --- a/Wabbajack.Common/StoreHandlers/SteamHandler.cs +++ b/Wabbajack.Common/StoreHandlers/SteamHandler.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Security; using DynamicData; using Microsoft.Win32; +#nullable enable namespace Wabbajack.Common.StoreHandlers { @@ -21,9 +22,14 @@ namespace Wabbajack.Common.StoreHandlers public class SteamWorkshopItem { - public SteamGame? Game; + public readonly SteamGame Game; public int ItemID; public int Size; + + public SteamWorkshopItem(SteamGame game) + { + Game = game; + } } public class SteamHandler : AStoreHandler @@ -202,14 +208,14 @@ namespace Wabbajack.Common.StoreHandlers var bracketStart = 0; var bracketEnd = 0; - SteamWorkshopItem? currentItem = new SteamWorkshopItem(); + SteamWorkshopItem? currentItem = new SteamWorkshopItem(game); lines.Do(l => { if (end) return; if (currentItem == null) - currentItem = new SteamWorkshopItem(); + currentItem = new SteamWorkshopItem(game); var currentLine = lines.IndexOf(l); if (l.ContainsCaseInsensitive("\"appid\"") && !foundAppID) @@ -271,7 +277,6 @@ namespace Wabbajack.Common.StoreHandlers bracketStart = 0; bracketEnd = 0; - currentItem.Game = game; game.WorkshopItems.Add(currentItem); Utils.Log($"Found Steam Workshop item {currentItem.ItemID}"); diff --git a/Wabbajack.Common/StoreHandlers/StoreHandler.cs b/Wabbajack.Common/StoreHandlers/StoreHandler.cs index 52aaf5c5..0547aa28 100644 --- a/Wabbajack.Common/StoreHandlers/StoreHandler.cs +++ b/Wabbajack.Common/StoreHandlers/StoreHandler.cs @@ -52,7 +52,7 @@ namespace Wabbajack.Common.StoreHandlers } } - public AbsolutePath? GetGamePath(Game game) + public AbsolutePath? TryGetGamePath(Game game) { return StoreGames.FirstOrDefault(g => g.Game == game)?.Path; } diff --git a/Wabbajack.Common/WorkQueue.cs b/Wabbajack.Common/WorkQueue.cs index ceab2e05..d9667669 100644 --- a/Wabbajack.Common/WorkQueue.cs +++ b/Wabbajack.Common/WorkQueue.cs @@ -49,7 +49,7 @@ namespace Wabbajack.Common private readonly BehaviorSubject<(int DesiredCPUs, int CurrentCPUs)> _cpuCountSubj = new BehaviorSubject<(int DesiredCPUs, int CurrentCPUs)>((0, 0)); public IObservable<(int CurrentCPUs, int DesiredCPUs)> CurrentCpuCount => _cpuCountSubj; - private readonly Subject> _activeNumThreadsObservable = new Subject>(); + private readonly Subject?> _activeNumThreadsObservable = new Subject?>(); public static TimeSpan PollMS = TimeSpan.FromMilliseconds(200); @@ -66,7 +66,7 @@ namespace Wabbajack.Common /// Creates a WorkQueue whos number of threads is determined by the given observable /// /// Driving observable that determines how many threads should be actively pulling jobs from the queue - public WorkQueue(IObservable numThreads) + public WorkQueue(IObservable? numThreads) { // Hook onto the number of active threads subject, and subscribe to it for changes _activeNumThreadsObservable @@ -86,7 +86,7 @@ namespace Wabbajack.Common /// Sets the driving observable that determines how many threads should be actively pulling jobs from the queue /// /// Driving observable that determines how many threads should be actively pulling jobs from the queue - public void SetActiveThreadsObservable(IObservable numThreads) + public void SetActiveThreadsObservable(IObservable? numThreads) { _activeNumThreadsObservable.OnNext(numThreads); } diff --git a/Wabbajack.Lib/ABatchProcessor.cs b/Wabbajack.Lib/ABatchProcessor.cs index af0ecf46..12a0aab8 100644 --- a/Wabbajack.Lib/ABatchProcessor.cs +++ b/Wabbajack.Lib/ABatchProcessor.cs @@ -15,9 +15,9 @@ namespace Wabbajack.Lib { public WorkQueue Queue { get; } = new WorkQueue(); - public Context VFS { get; private set; } + public Context VFS { get; } - protected StatusUpdateTracker UpdateTracker { get; private set; } + protected StatusUpdateTracker UpdateTracker { get; } private Subject _percentCompleted { get; } = new Subject(); @@ -42,7 +42,6 @@ namespace Wabbajack.Lib private Subject _isRunning { get; } = new Subject(); public IObservable IsRunning => _isRunning; - private int _configured; private int _started; private readonly CancellationTokenSource _cancel = new CancellationTokenSource(); @@ -53,21 +52,16 @@ namespace Wabbajack.Lib public BehaviorSubject MaxCores = new BehaviorSubject(byte.MaxValue); public BehaviorSubject TargetUsagePercent = new BehaviorSubject(Percent.One); - protected void ConfigureProcessor(int steps, IObservable numThreads = null) + public ABatchProcessor(int steps) { - if (1 == Interlocked.CompareExchange(ref _configured, 1, 1)) - { - throw new InvalidDataException("Can't configure a processor twice"); - } - Queue.SetActiveThreadsObservable(numThreads); UpdateTracker = new StatusUpdateTracker(steps); + VFS = new Context(Queue) { UpdateTracker = UpdateTracker }; Queue.Status.Subscribe(_queueStatus) .DisposeWith(_subs); Queue.LogMessages.Subscribe(_logMessages) .DisposeWith(_subs); UpdateTracker.Progress.Subscribe(_percentCompleted); UpdateTracker.StepName.Subscribe(_textStatus); - VFS = new Context(Queue) { UpdateTracker = UpdateTracker }; } /// diff --git a/Wabbajack.Lib/ACompiler.cs b/Wabbajack.Lib/ACompiler.cs index e368db17..45f2d445 100644 --- a/Wabbajack.Lib/ACompiler.cs +++ b/Wabbajack.Lib/ACompiler.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; @@ -19,10 +19,10 @@ namespace Wabbajack.Lib { public abstract class ACompiler : ABatchProcessor { - public string ModListName, ModListAuthor, ModListDescription, ModListWebsite; + public string? ModListName, ModListAuthor, ModListDescription, ModListWebsite; public AbsolutePath ModListImage, ModListReadme; public bool ReadmeIsWebsite; - protected Version WabbajackVersion; + protected Version? WabbajackVersion; public abstract AbsolutePath VFSCacheName { get; } //protected string VFSCacheName => Path.Combine(Consts.LocalAppDataPath, $"vfs_compile_cache.bin"); @@ -42,14 +42,19 @@ namespace Wabbajack.Lib public bool IgnoreMissingFiles { get; set; } - public ICollection SelectedArchives = new List(); - public List InstallDirectives = new List(); - public List AllFiles = new List(); + public List SelectedArchives { get; protected set; } = new List(); + public List InstallDirectives { get; protected set; } = new List(); + public List AllFiles { get; protected set; } = new List(); public ModList ModList = new ModList(); public List IndexedArchives = new List(); public Dictionary> IndexedFiles = new Dictionary>(); + public ACompiler(int steps) + : base(steps) + { + } + public static void Info(string msg) { Utils.Log(msg); @@ -216,7 +221,7 @@ namespace Wabbajack.Lib .GroupBy(f => f.File.Hash) .ToDictionary(f => f.Key, f => f.First()); - SelectedArchives = await hashes.PMap(Queue, hash => ResolveArchive(hash, archives)); + SelectedArchives.SetTo(await hashes.PMap(Queue, hash => ResolveArchive(hash, archives))); } public async Task ResolveArchive(Hash hash, IDictionary archives) @@ -226,8 +231,7 @@ namespace Wabbajack.Lib return await ResolveArchive(found); } - Error($"No match found for Archive sha: {hash.ToBase64()} this shouldn't happen"); - return null; + throw new ArgumentException($"No match found for Archive sha: {hash.ToBase64()} this shouldn't happen"); } public async Task ResolveArchive(IndexedArchive archive) @@ -238,10 +242,7 @@ namespace Wabbajack.Lib Error( $"No download metadata found for {archive.Name}, please use MO2 to query info or add a .meta file and try again."); - var result = new Archive - { - State = await DownloadDispatcher.ResolveArchive(archive.IniData) - }; + var result = new Archive(await DownloadDispatcher.ResolveArchive(archive.IniData)); if (result.State == null) Error($"{archive.Name} could not be handled by any of the downloaders"); diff --git a/Wabbajack.Lib/AInstaller.cs b/Wabbajack.Lib/AInstaller.cs index 497ffd33..b0cd0536 100644 --- a/Wabbajack.Lib/AInstaller.cs +++ b/Wabbajack.Lib/AInstaller.cs @@ -27,11 +27,12 @@ namespace Wabbajack.Lib public AbsolutePath ModListArchive { get; private set; } public ModList ModList { get; private set; } - public Dictionary HashedArchives { get; set; } + public Dictionary HashedArchives { get; } = new Dictionary(); - public SystemParameters SystemParameters { get; set; } + public SystemParameters? SystemParameters { get; set; } - public AInstaller(AbsolutePath archive, ModList modList, AbsolutePath outputFolder, AbsolutePath downloadFolder, SystemParameters parameters) + public AInstaller(AbsolutePath archive, ModList modList, AbsolutePath outputFolder, AbsolutePath downloadFolder, SystemParameters? parameters, int steps) + : base(steps) { ModList = modList; ModListArchive = archive; @@ -166,6 +167,10 @@ namespace Wabbajack.Lib .PDoIndexed(queue, async (idx, group) => { Utils.Status("Installing files", Percent.FactoryPutInRange(idx, vFiles.Count)); + if (group.Key == null) + { + throw new ArgumentNullException("FromFile was null"); + } var firstDest = OutputFolder.Combine(group.First().To); await CopyFile(group.Key.StagedPath, firstDest, true); @@ -173,11 +178,10 @@ namespace Wabbajack.Lib { await CopyFile(firstDest, OutputFolder.Combine(copy.To), false); } - }); Status("Unstaging files"); - onFinish(); + await onFinish(); // Now patch all the files from this archive await grouping.OfType() @@ -278,11 +282,11 @@ namespace Wabbajack.Lib var hashResults = await DownloadFolder.EnumerateFiles() .Where(e => e.Extension != Consts.HashFileExtension) .PMap(Queue, async e => (await e.FileHashCachedAsync(), e)); - HashedArchives = hashResults + HashedArchives.SetTo(hashResults .OrderByDescending(e => e.Item2.LastModified) .GroupBy(e => e.Item1) .Select(e => e.First()) - .ToDictionary(e => e.Item1, e => e.Item2); + .Select(e => new KeyValuePair(e.Item1, e.Item2))); } /// @@ -391,8 +395,13 @@ namespace Wabbajack.Lib return await path.FileHashAsync() == d.Hash ? d : null; })) - .Where(d => d != null) - .Do(d => indexed.Remove(d.To)); + .Do(d => + { + if (d != null) + { + indexed.Remove(d.To); + } + }); UpdateTracker.NextStep("Updating ModList"); Utils.Log($"Optimized {ModList.Directives.Count} directives to {indexed.Count} required"); diff --git a/Wabbajack.Lib/ClientAPI.cs b/Wabbajack.Lib/ClientAPI.cs index c750782b..959fbbf7 100644 --- a/Wabbajack.Lib/ClientAPI.cs +++ b/Wabbajack.Lib/ClientAPI.cs @@ -14,7 +14,7 @@ namespace Wabbajack.Lib return client; } - public static async Task GetModUpgrade(Hash hash) + public static async Task GetModUpgrade(Hash hash) { using var response = await GetClient() .GetAsync($"{Consts.WabbajackBuildServerUri}alternative/{hash.ToHex()}"); @@ -33,7 +33,7 @@ namespace Wabbajack.Lib /// /// /// - public static async Task GetModIni(Hash hash) + public static async Task GetModIni(Hash hash) { var client = new Common.Http.Client(); try diff --git a/Wabbajack.Lib/CompilationSteps/ACompilationStep.cs b/Wabbajack.Lib/CompilationSteps/ACompilationStep.cs index 9dc0e547..e00ac6e7 100644 --- a/Wabbajack.Lib/CompilationSteps/ACompilationStep.cs +++ b/Wabbajack.Lib/CompilationSteps/ACompilationStep.cs @@ -11,7 +11,7 @@ namespace Wabbajack.Lib.CompilationSteps _compiler = compiler; } - public abstract ValueTask Run(RawSourceFile source); + public abstract ValueTask Run(RawSourceFile source); public abstract IState GetState(); } } diff --git a/Wabbajack.Lib/CompilationSteps/DeconstructBSAs.cs b/Wabbajack.Lib/CompilationSteps/DeconstructBSAs.cs index b5adf2f0..a12e8eff 100644 --- a/Wabbajack.Lib/CompilationSteps/DeconstructBSAs.cs +++ b/Wabbajack.Lib/CompilationSteps/DeconstructBSAs.cs @@ -51,7 +51,7 @@ namespace Wabbajack.Lib.CompilationSteps return new State(); } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { if (!Consts.SupportedBSAs.Contains(source.Path.Extension)) return null; @@ -79,12 +79,12 @@ namespace Wabbajack.Lib.CompilationSteps CreateBSA directive; using (var bsa = BSADispatch.OpenRead(source.AbsolutePath)) { - directive = new CreateBSA + directive = new CreateBSA( + state: bsa.State, + items: bsa.Files.Select(f => f.State).ToList()) { To = source.Path, TempID = (RelativePath)id, - State = bsa.State, - FileStates = bsa.Files.Select(f => f.State).ToList() }; } diff --git a/Wabbajack.Lib/CompilationSteps/DirectMatch.cs b/Wabbajack.Lib/CompilationSteps/DirectMatch.cs index 807184f4..0f57cee3 100644 --- a/Wabbajack.Lib/CompilationSteps/DirectMatch.cs +++ b/Wabbajack.Lib/CompilationSteps/DirectMatch.cs @@ -11,7 +11,7 @@ namespace Wabbajack.Lib.CompilationSteps { } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { if (!_compiler.IndexedFiles.TryGetValue(source.Hash, out var found)) return null; var result = source.EvolveTo(); diff --git a/Wabbajack.Lib/CompilationSteps/DropAll.cs b/Wabbajack.Lib/CompilationSteps/DropAll.cs index 2d5c65a1..bcbc32f3 100644 --- a/Wabbajack.Lib/CompilationSteps/DropAll.cs +++ b/Wabbajack.Lib/CompilationSteps/DropAll.cs @@ -10,7 +10,7 @@ namespace Wabbajack.Lib.CompilationSteps { } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { var result = source.EvolveTo(); result.Reason = "No Match in Stack"; diff --git a/Wabbajack.Lib/CompilationSteps/IStackStep.cs b/Wabbajack.Lib/CompilationSteps/ICompilationStep.cs similarity index 81% rename from Wabbajack.Lib/CompilationSteps/IStackStep.cs rename to Wabbajack.Lib/CompilationSteps/ICompilationStep.cs index 1c427edc..eafa5914 100644 --- a/Wabbajack.Lib/CompilationSteps/IStackStep.cs +++ b/Wabbajack.Lib/CompilationSteps/ICompilationStep.cs @@ -4,7 +4,7 @@ namespace Wabbajack.Lib.CompilationSteps { public interface ICompilationStep { - ValueTask Run(RawSourceFile source); + ValueTask Run(RawSourceFile source); IState GetState(); } diff --git a/Wabbajack.Lib/CompilationSteps/IgnoreDisabledMods.cs b/Wabbajack.Lib/CompilationSteps/IgnoreDisabledMods.cs index e066fc66..3fd9912b 100644 --- a/Wabbajack.Lib/CompilationSteps/IgnoreDisabledMods.cs +++ b/Wabbajack.Lib/CompilationSteps/IgnoreDisabledMods.cs @@ -25,7 +25,7 @@ namespace Wabbajack.Lib.CompilationSteps .ToList(); } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { if (!source.AbsolutePath.InFolder(_mo2Compiler.MO2ModsFolder)) return null; if (_allEnabledMods.Any(mod => source.AbsolutePath.InFolder(mod))) diff --git a/Wabbajack.Lib/CompilationSteps/IgnoreEndsWith.cs b/Wabbajack.Lib/CompilationSteps/IgnoreEndsWith.cs index adec2b87..b9918d95 100644 --- a/Wabbajack.Lib/CompilationSteps/IgnoreEndsWith.cs +++ b/Wabbajack.Lib/CompilationSteps/IgnoreEndsWith.cs @@ -14,7 +14,7 @@ namespace Wabbajack.Lib.CompilationSteps _reason = $"Ignored because path ends with {postfix}"; } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { if (!((string)source.Path).EndsWith(_postfix)) return null; var result = source.EvolveTo(); @@ -30,17 +30,13 @@ namespace Wabbajack.Lib.CompilationSteps [JsonObject("IgnoreEndsWith")] public class State : IState { + public string Postfix { get; set; } + public State(string postfix) { Postfix = postfix; } - public State() - { - } - - public string Postfix { get; set; } - public ICompilationStep CreateStep(ACompiler compiler) { return new IgnoreEndsWith(compiler, Postfix); diff --git a/Wabbajack.Lib/CompilationSteps/IgnoreGameFiles.cs b/Wabbajack.Lib/CompilationSteps/IgnoreGameFiles.cs index 30889a77..7581c566 100644 --- a/Wabbajack.Lib/CompilationSteps/IgnoreGameFiles.cs +++ b/Wabbajack.Lib/CompilationSteps/IgnoreGameFiles.cs @@ -13,7 +13,7 @@ namespace Wabbajack.Lib.CompilationSteps _startDir = Consts.GameFolderFilesDir + "\\"; } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { if (!((string)source.Path).StartsWith(_startDir)) return null; var i = source.EvolveTo(); diff --git a/Wabbajack.Lib/CompilationSteps/IgnoreGameFilesIfGameFolderFilesExist.cs b/Wabbajack.Lib/CompilationSteps/IgnoreGameFilesIfGameFolderFilesExist.cs index 420a17a2..6f86f783 100644 --- a/Wabbajack.Lib/CompilationSteps/IgnoreGameFilesIfGameFolderFilesExist.cs +++ b/Wabbajack.Lib/CompilationSteps/IgnoreGameFilesIfGameFolderFilesExist.cs @@ -15,7 +15,7 @@ namespace Wabbajack.Lib.CompilationSteps _gameFolder = compiler.GamePath; } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { if (_gameFolderFilesExists) { diff --git a/Wabbajack.Lib/CompilationSteps/IgnorePathContains.cs b/Wabbajack.Lib/CompilationSteps/IgnorePathContains.cs index 25094e16..eac01fe7 100644 --- a/Wabbajack.Lib/CompilationSteps/IgnorePathContains.cs +++ b/Wabbajack.Lib/CompilationSteps/IgnorePathContains.cs @@ -14,7 +14,7 @@ namespace Wabbajack.Lib.CompilationSteps _reason = $"Ignored because path contains {_pattern}"; } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { if (!((string)source.Path).Contains(_pattern)) return null; var result = source.EvolveTo(); @@ -30,17 +30,13 @@ namespace Wabbajack.Lib.CompilationSteps [JsonObject("IgnorePathContains")] public class State : IState { - public State() - { - } + public string Pattern { get; set; } public State(string pattern) { Pattern = pattern; } - public string Pattern { get; set; } - public ICompilationStep CreateStep(ACompiler compiler) { return new IgnorePathContains(compiler, Pattern); diff --git a/Wabbajack.Lib/CompilationSteps/IgnoreRegex.cs b/Wabbajack.Lib/CompilationSteps/IgnoreRegex.cs index 7c004d2a..739ad00c 100644 --- a/Wabbajack.Lib/CompilationSteps/IgnoreRegex.cs +++ b/Wabbajack.Lib/CompilationSteps/IgnoreRegex.cs @@ -17,7 +17,7 @@ namespace Wabbajack.Lib.CompilationSteps _regex = new Regex(pattern); } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { if (!_regex.IsMatch((string)source.Path)) return null; var result = source.EvolveTo(); @@ -33,17 +33,13 @@ namespace Wabbajack.Lib.CompilationSteps [JsonObject("IgnorePattern")] public class State : IState { - public State() - { - } + public string Pattern { get; set; } public State(string pattern) { Pattern = pattern; } - public string Pattern { get; set; } - public ICompilationStep CreateStep(ACompiler compiler) { return new IgnoreRegex(compiler, Pattern); diff --git a/Wabbajack.Lib/CompilationSteps/IgnoreStartsWith.cs b/Wabbajack.Lib/CompilationSteps/IgnoreStartsWith.cs index ce345a53..5abad699 100644 --- a/Wabbajack.Lib/CompilationSteps/IgnoreStartsWith.cs +++ b/Wabbajack.Lib/CompilationSteps/IgnoreStartsWith.cs @@ -14,7 +14,7 @@ namespace Wabbajack.Lib.CompilationSteps _reason = string.Format("Ignored because path starts with {0}", _prefix); } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { if (!((string)source.Path).StartsWith(_prefix)) { @@ -35,17 +35,13 @@ namespace Wabbajack.Lib.CompilationSteps [JsonObject("IgnoreStartsWith")] public class State : IState { - public State() - { - } + public string Prefix { get; set; } public State(string prefix) { Prefix = prefix; } - public string Prefix { get; set; } - public ICompilationStep CreateStep(ACompiler compiler) { return new IgnoreStartsWith(compiler, Prefix); diff --git a/Wabbajack.Lib/CompilationSteps/IgnoreWabbajackInstallCruft.cs b/Wabbajack.Lib/CompilationSteps/IgnoreWabbajackInstallCruft.cs index ae46fa03..e8e71212 100644 --- a/Wabbajack.Lib/CompilationSteps/IgnoreWabbajackInstallCruft.cs +++ b/Wabbajack.Lib/CompilationSteps/IgnoreWabbajackInstallCruft.cs @@ -19,7 +19,7 @@ namespace Wabbajack.Lib.CompilationSteps }; } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { if (!_cruftFiles.Any(f => source.Path.StartsWith(f))) return null; var result = source.EvolveTo(); diff --git a/Wabbajack.Lib/CompilationSteps/IncludeAll.cs b/Wabbajack.Lib/CompilationSteps/IncludeAll.cs index 9f836158..db63b34b 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludeAll.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludeAll.cs @@ -10,7 +10,7 @@ namespace Wabbajack.Lib.CompilationSteps { } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { var inline = source.EvolveTo(); inline.SourceDataID = await _compiler.IncludeFile(await source.AbsolutePath.ReadAllBytesAsync()); diff --git a/Wabbajack.Lib/CompilationSteps/IncludeAllConfigs.cs b/Wabbajack.Lib/CompilationSteps/IncludeAllConfigs.cs index 10ae1e26..9bf53920 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludeAllConfigs.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludeAllConfigs.cs @@ -11,7 +11,7 @@ namespace Wabbajack.Lib.CompilationSteps { } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { if (!Consts.ConfigFileExtensions.Contains(source.Path.Extension)) return null; var result = source.EvolveTo(); diff --git a/Wabbajack.Lib/CompilationSteps/IncludeDummyESPs.cs b/Wabbajack.Lib/CompilationSteps/IncludeDummyESPs.cs index fc11f20c..f251e625 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludeDummyESPs.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludeDummyESPs.cs @@ -11,7 +11,7 @@ namespace Wabbajack.Lib.CompilationSteps { } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { if (source.AbsolutePath.Extension != Consts.ESP && source.AbsolutePath.Extension != Consts.ESM) return null; diff --git a/Wabbajack.Lib/CompilationSteps/IncludeLOOTFiles.cs b/Wabbajack.Lib/CompilationSteps/IncludeLOOTFiles.cs index 7bc05446..5a89a131 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludeLOOTFiles.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludeLOOTFiles.cs @@ -14,7 +14,7 @@ namespace Wabbajack.Lib.CompilationSteps _prefix = Consts.LOOTFolderFilesDir + "\\"; } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { if (!source.Path.StartsWith(_prefix)) return null; var result = source.EvolveTo(); diff --git a/Wabbajack.Lib/CompilationSteps/IncludeModIniData.cs b/Wabbajack.Lib/CompilationSteps/IncludeModIniData.cs index c5d3614a..f1def248 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludeModIniData.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludeModIniData.cs @@ -11,7 +11,7 @@ namespace Wabbajack.Lib.CompilationSteps { } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { if (!source.Path.StartsWith("mods\\") || source.Path.FileName != Consts.MetaIni) return null; var e = source.EvolveTo(); diff --git a/Wabbajack.Lib/CompilationSteps/IncludeOtherProfiles.cs b/Wabbajack.Lib/CompilationSteps/IncludeOtherProfiles.cs index c643b2eb..9c89d361 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludeOtherProfiles.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludeOtherProfiles.cs @@ -23,7 +23,7 @@ namespace Wabbajack.Lib.CompilationSteps .ToList(); } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { if (!source.AbsolutePath.InFolder(_modProfilesFolder)) return null; if (_profiles.Any(profile => source.AbsolutePath.InFolder(profile))) return null; diff --git a/Wabbajack.Lib/CompilationSteps/IncludePatches.cs b/Wabbajack.Lib/CompilationSteps/IncludePatches.cs index e99fcf9a..65cb6b40 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludePatches.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludePatches.cs @@ -12,11 +12,11 @@ namespace Wabbajack.Lib.CompilationSteps public class IncludePatches : ACompilationStep { private readonly Dictionary> _indexed; - private VirtualFile _bsa; + private VirtualFile? _bsa; private Dictionary _indexedByName; private MO2Compiler _mo2Compiler; - public IncludePatches(ACompiler compiler, VirtualFile constructingFromBSA = null) : base(compiler) + public IncludePatches(ACompiler compiler, VirtualFile? constructingFromBSA = null) : base(compiler) { _bsa = constructingFromBSA; _mo2Compiler = (MO2Compiler)compiler; @@ -30,9 +30,8 @@ namespace Wabbajack.Lib.CompilationSteps .ToDictionary(f => f.FullPath.FileName); } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { - var name = source.File.Name.FileName; RelativePath nameWithoutExt = name; if (name.Extension == Consts.MOHIDDEN) @@ -41,7 +40,7 @@ namespace Wabbajack.Lib.CompilationSteps if (!_indexed.TryGetValue(name, out var choices)) _indexed.TryGetValue(nameWithoutExt, out choices); - dynamic modIni = null; + dynamic? modIni = null; if (source.AbsolutePath.InFolder(_mo2Compiler.MO2ModsFolder)) { if (_bsa == null) @@ -55,7 +54,7 @@ namespace Wabbajack.Lib.CompilationSteps var installationFile = (RelativePath)modIni?.General?.installationFile; - VirtualFile found = null; + VirtualFile? found = null; // Find based on exact file name + ext if (choices != null) diff --git a/Wabbajack.Lib/CompilationSteps/IncludePropertyFiles.cs b/Wabbajack.Lib/CompilationSteps/IncludePropertyFiles.cs index 3ccd5e54..3add0f20 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludePropertyFiles.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludePropertyFiles.cs @@ -14,7 +14,7 @@ namespace Wabbajack.Lib.CompilationSteps { } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { var files = new HashSet { diff --git a/Wabbajack.Lib/CompilationSteps/IncludeRegex.cs b/Wabbajack.Lib/CompilationSteps/IncludeRegex.cs index a7482285..edd9128c 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludeRegex.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludeRegex.cs @@ -16,7 +16,7 @@ namespace Wabbajack.Lib.CompilationSteps _regex = new Regex(pattern); } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { if (!_regex.IsMatch((string)source.Path)) return null; @@ -33,17 +33,13 @@ namespace Wabbajack.Lib.CompilationSteps [JsonObject("IncludeRegex")] public class State : IState { - public State() - { - } + public string Pattern { get; set; } public State(string pattern) { Pattern = pattern; } - public string Pattern { get; set; } - public ICompilationStep CreateStep(ACompiler compiler) { return new IncludeRegex(compiler, Pattern); diff --git a/Wabbajack.Lib/CompilationSteps/IncludeSteamWorkshopItems.cs b/Wabbajack.Lib/CompilationSteps/IncludeSteamWorkshopItems.cs index ad6b3200..1bcbef60 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludeSteamWorkshopItems.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludeSteamWorkshopItems.cs @@ -18,7 +18,7 @@ namespace Wabbajack.Lib.CompilationSteps _game = steamGame; } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { if (!_regex.IsMatch((string)source.Path)) return null; @@ -31,7 +31,7 @@ namespace Wabbajack.Lib.CompilationSteps if (id == 0) return null; - SteamWorkshopItem item = null; + SteamWorkshopItem? item = null; _game.WorkshopItems.Where(i => i.ItemID == id).Do(i => item = i); if (item == null) return null; diff --git a/Wabbajack.Lib/CompilationSteps/IncludeStubbedConfigfiles.cs b/Wabbajack.Lib/CompilationSteps/IncludeStubbedConfigfiles.cs index 32e41955..392cb1b3 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludeStubbedConfigfiles.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludeStubbedConfigfiles.cs @@ -15,7 +15,7 @@ namespace Wabbajack.Lib.CompilationSteps _mo2Compiler = (MO2Compiler) compiler; } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { return Consts.ConfigFileExtensions.Contains(source.Path.Extension) ? await RemapFile(source) : null; } @@ -25,7 +25,7 @@ namespace Wabbajack.Lib.CompilationSteps return new State(); } - private async Task RemapFile(RawSourceFile source) + private async Task RemapFile(RawSourceFile source) { var data = await source.AbsolutePath.ReadAllTextAsync(); var originalData = data; diff --git a/Wabbajack.Lib/CompilationSteps/IncludeTaggedMods.cs b/Wabbajack.Lib/CompilationSteps/IncludeTaggedMods.cs index d88deece..d2d8a06f 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludeTaggedMods.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludeTaggedMods.cs @@ -26,7 +26,7 @@ namespace Wabbajack.Lib.CompilationSteps }).Select(kv => $"mods\\{kv.Key}\\"); } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { if (!source.Path.StartsWith(Consts.MO2ModFolderName)) return null; foreach (var modpath in _includeDirectly) @@ -48,17 +48,13 @@ namespace Wabbajack.Lib.CompilationSteps [JsonObject("IncludeTaggedMods")] public class State : IState { - public State() - { - } + public string Tag { get; set; } public State(string tag) { Tag = tag; } - public string Tag { get; set; } - public ICompilationStep CreateStep(ACompiler compiler) { return new IncludeTaggedMods(compiler, Tag); diff --git a/Wabbajack.Lib/CompilationSteps/IncludeThisProfile.cs b/Wabbajack.Lib/CompilationSteps/IncludeThisProfile.cs index 2200c2fb..4d0141a3 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludeThisProfile.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludeThisProfile.cs @@ -19,7 +19,7 @@ namespace Wabbajack.Lib.CompilationSteps _correctProfiles = _mo2Compiler.SelectedProfiles.Select(p => _mo2Compiler.MO2ProfileDir.Parent.Combine(p)).ToList(); } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { if (!_correctProfiles.Any(p => source.AbsolutePath.InFolder(p))) return null; diff --git a/Wabbajack.Lib/CompilationSteps/IncludeVortexDeployment.cs b/Wabbajack.Lib/CompilationSteps/IncludeVortexDeployment.cs index e9a54343..19a386dc 100644 --- a/Wabbajack.Lib/CompilationSteps/IncludeVortexDeployment.cs +++ b/Wabbajack.Lib/CompilationSteps/IncludeVortexDeployment.cs @@ -12,7 +12,7 @@ namespace Wabbajack.Lib.CompilationSteps { } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { // * TODO I don't know what this does /* diff --git a/Wabbajack.Lib/CompilationSteps/PatchStockESMs.cs b/Wabbajack.Lib/CompilationSteps/PatchStockESMs.cs index e71dfc0c..2a8f9d12 100644 --- a/Wabbajack.Lib/CompilationSteps/PatchStockESMs.cs +++ b/Wabbajack.Lib/CompilationSteps/PatchStockESMs.cs @@ -16,7 +16,7 @@ namespace Wabbajack.Lib.CompilationSteps _mo2Compiler = (MO2Compiler) compiler; } - public override async ValueTask Run(RawSourceFile source) + public override async ValueTask Run(RawSourceFile source) { var filename = source.Path.FileName; var gameFile = _mo2Compiler.GamePath.Combine((RelativePath)"Data", filename); diff --git a/Wabbajack.Lib/Data.cs b/Wabbajack.Lib/Data.cs index c48e75ac..4fe5a4c5 100644 --- a/Wabbajack.Lib/Data.cs +++ b/Wabbajack.Lib/Data.cs @@ -43,22 +43,22 @@ namespace Wabbajack.Lib /// /// Archives required by this modlist /// - public List Archives; + public List Archives = new List(); /// /// Author of the ModList /// - public string Author; + public string Author = string.Empty; /// /// Description of the ModList /// - public string Description; + public string Description = string.Empty; /// /// Install directives /// - public List Directives; + public List Directives = new List(); /// /// The game variant to which this game applies @@ -78,12 +78,12 @@ namespace Wabbajack.Lib /// /// Name of the ModList /// - public string Name; + public string Name = string.Empty; /// /// readme path or website /// - public string Readme; + public string Readme = string.Empty; /// /// Whether readme is a website @@ -93,12 +93,12 @@ namespace Wabbajack.Lib /// /// The build version of Wabbajack used when compiling the Modlist /// - public Version WabbajackVersion; + public Version? WabbajackVersion; /// /// Website of the ModList /// - public Uri Website; + public Uri? Website; /// /// The size of all the archives once they're downloaded @@ -134,7 +134,7 @@ namespace Wabbajack.Lib public class IgnoredDirectly : Directive { - public string Reason; + public string Reason = string.Empty; } public class NoMatch : IgnoredDirectly @@ -190,12 +190,12 @@ namespace Wabbajack.Lib [JsonName("FromArchive")] public class FromArchive : Directive { - private string _fullPath; + private string? _fullPath; public HashRelativePath ArchiveHashPath { get; set; } [JsonIgnore] - public VirtualFile FromFile { get; set; } + public VirtualFile? FromFile { get; set; } [JsonIgnore] public string FullPath => _fullPath ??= string.Join("|", ArchiveHashPath); @@ -205,8 +205,17 @@ namespace Wabbajack.Lib public class CreateBSA : Directive { public RelativePath TempID { get; set; } - public ArchiveStateObject State { get; set; } - public List FileStates { get; set; } + public ArchiveStateObject State { get; } + public List FileStates { get; set; } = new List(); + + public CreateBSA(ArchiveStateObject state, IEnumerable? items = null) + { + State = state; + if (items != null) + { + FileStates.AddRange(items); + } + } } [JsonName("PatchedFromArchive")] @@ -231,7 +240,7 @@ namespace Wabbajack.Lib public class MergedPatch : Directive { public RelativePath PatchID { get; set; } - public List Sources { get; set; } + public List Sources { get; set; } = new List(); } [JsonName("Archive")] @@ -245,49 +254,33 @@ namespace Wabbajack.Lib /// /// Meta INI for the downloaded archive /// - public string Meta { get; set; } + public string? Meta { get; set; } /// /// Human friendly name of this archive /// - public string Name { get; set; } + public string Name { get; set; } = string.Empty; public long Size { get; set; } - public AbstractDownloadState State { get; set; } + public AbstractDownloadState State { get; } + + public Archive(AbstractDownloadState state) + { + State = state; + } } public class IndexedArchive { - public dynamic IniData; - public string Meta; - public string Name; - public VirtualFile File { get; internal set; } - } + public dynamic? IniData; + public string Meta = string.Empty; + public string Name = string.Empty; + public VirtualFile File { get; } - /// - /// A archive entry - /// - public class IndexedEntry - { - /// - /// MurMur3 hash of this file - /// - public string Hash; - - /// - /// Path in the archive to this file - /// - public string Path; - - /// - /// Size of the file (uncompressed) - /// - public long Size; - } - - public class IndexedArchiveEntry : IndexedEntry - { - public string[] HashPath; + public IndexedArchive(VirtualFile file) + { + File = file; + } } } diff --git a/Wabbajack.Lib/Downloaders/AbstractDownloadState.cs b/Wabbajack.Lib/Downloaders/AbstractDownloadState.cs index e0b519c9..d3677b1b 100644 --- a/Wabbajack.Lib/Downloaders/AbstractDownloadState.cs +++ b/Wabbajack.Lib/Downloaders/AbstractDownloadState.cs @@ -11,19 +11,18 @@ namespace Wabbajack.Lib.Downloaders public interface IMetaState { Uri URL { get; } - string Name { get; set; } - string Author { get; set; } - string Version { get; set; } - Uri ImageURL { get; set; } + string? Name { get; set; } + string? Author { get; set; } + string? Version { get; set; } + Uri? ImageURL { get; set; } bool IsNSFW { get; set; } - string Description { get; set; } + string? Description { get; set; } Task LoadMetaData(); } public abstract class AbstractDownloadState { - public static List KnownSubTypes = new List { typeof(HTTPDownloader.State), @@ -48,7 +47,7 @@ namespace Wabbajack.Lib.Downloaders static AbstractDownloadState() { - NameToType = KnownSubTypes.ToDictionary(t => t.FullName.Substring(t.Namespace.Length + 1), t => t); + NameToType = KnownSubTypes.ToDictionary(t => t.FullName!.Substring(t.Namespace!.Length + 1), t => t); TypeToName = NameToType.ToDictionary(k => k.Value, k => k.Key); } @@ -67,7 +66,6 @@ namespace Wabbajack.Lib.Downloaders } } - /// /// Returns true if this file is allowed to be downloaded via whitelist /// @@ -84,7 +82,7 @@ namespace Wabbajack.Lib.Downloaders public async Task Download(AbsolutePath destination) { destination.Parent.CreateDirectory(); - return await Download(new Archive {Name = (string)destination.FileName}, destination); + return await Download(new Archive(this) {Name = (string)destination.FileName}, destination); } /// @@ -95,7 +93,7 @@ namespace Wabbajack.Lib.Downloaders public abstract IDownloader GetDownloader(); - public abstract string GetManifestURL(Archive a); + public abstract string? GetManifestURL(Archive a); public abstract string[] GetMetaIni(); } } diff --git a/Wabbajack.Lib/Downloaders/AbstractIPS4Downloader.cs b/Wabbajack.Lib/Downloaders/AbstractIPS4Downloader.cs index 059fdee1..0c7fc4a7 100644 --- a/Wabbajack.Lib/Downloaders/AbstractIPS4Downloader.cs +++ b/Wabbajack.Lib/Downloaders/AbstractIPS4Downloader.cs @@ -18,10 +18,12 @@ namespace Wabbajack.Lib.Downloaders where TState : AbstractIPS4Downloader.State, new() where TDownloader : IDownloader { - public override string SiteName { get; } - public override Uri SiteURL { get; } + protected AbstractIPS4Downloader(Uri loginUri, string encryptedKeyName, string cookieDomain) + : base(loginUri, encryptedKeyName, cookieDomain, "ips4_member_id") + { + } - public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) + public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) { Uri url = DownloaderUtils.GetDirectURL(archiveINI); @@ -70,23 +72,23 @@ namespace Wabbajack.Lib.Downloaders }; } - - public class State : AbstractDownloadState, IMetaState where TStateDownloader : IDownloader + public class State : AbstractDownloadState, IMetaState + where TStateDownloader : IDownloader { - public string FullURL { get; set; } + public string FullURL { get; set; } = string.Empty; public bool IsAttachment { get; set; } - public string FileID { get; set; } - - public string FileName { get; set; } + public string FileID { get; set; } = string.Empty; + + public string FileName { get; set; } = string.Empty; // from IMetaState public Uri URL => new Uri($"{Site}/files/file/{FileName}"); - public string Name { get; set; } - public string Author { get; set; } - public string Version { get; set; } - public Uri ImageURL { get; set; } + public string? Name { get; set; } + public string? Author { get; set; } + public string? Version { get; set; } + public Uri? ImageURL { get; set; } public virtual bool IsNSFW { get; set; } - public string Description { get; set; } + public string? Description { get; set; } private static bool IsHTTPS => Downloader.SiteURL.AbsolutePath.StartsWith("https://"); private static string URLPrefix => IsHTTPS ? "https://" : "http://"; @@ -119,6 +121,7 @@ namespace Wabbajack.Lib.Downloaders public override async Task Download(Archive a, AbsolutePath destination) { await using var stream = await ResolveDownloadStream(); + if (stream == null) return false; await using (var file = destination.Create()) { await stream.CopyToAsync(file); @@ -126,7 +129,7 @@ namespace Wabbajack.Lib.Downloaders return true; } - private async Task ResolveDownloadStream() + private async Task ResolveDownloadStream() { TOP: string url; @@ -236,12 +239,5 @@ namespace Wabbajack.Lib.Downloaders return false; } } - - protected AbstractIPS4Downloader(Uri loginUri, string encryptedKeyName, string cookieDomain) : - base(loginUri, encryptedKeyName, cookieDomain, "ips4_member_id") - { - } - - } } diff --git a/Wabbajack.Lib/Downloaders/AbstractNeedsLoginDownloader.cs b/Wabbajack.Lib/Downloaders/AbstractNeedsLoginDownloader.cs index 601b653f..a6a4e681 100644 --- a/Wabbajack.Lib/Downloaders/AbstractNeedsLoginDownloader.cs +++ b/Wabbajack.Lib/Downloaders/AbstractNeedsLoginDownloader.cs @@ -21,7 +21,17 @@ namespace Wabbajack.Lib.Downloaders private readonly string _encryptedKeyName; private readonly string _cookieDomain; private readonly string _cookieName; - internal Common.Http.Client AuthedClient; + // ToDo + // Remove null assignment. Either add nullability to type, or figure way to prepare it safely + public Common.Http.Client AuthedClient { get; private set; } = null!; + + public ReactiveCommand TriggerLogin { get; } + public ReactiveCommand ClearLogin { get; } + public IObservable IsLoggedIn => Utils.HaveEncryptedJsonObservable(_encryptedKeyName); + public abstract string SiteName { get; } + public virtual IObservable? MetaInfo { get; } + public abstract Uri SiteURL { get; } + public virtual Uri? IconUri { get; } /// /// Sets up all the login facilites needed for a INeedsLogin downloader based on having the user log @@ -48,14 +58,6 @@ namespace Wabbajack.Lib.Downloaders execute: () => Utils.CatchAndLog(() => Utils.DeleteEncryptedJson(_encryptedKeyName)), canExecute: IsLoggedIn.ObserveOnGuiThread()); } - - public ReactiveCommand TriggerLogin { get; } - public ReactiveCommand ClearLogin { get; } - public IObservable IsLoggedIn => Utils.HaveEncryptedJsonObservable(_encryptedKeyName); - public abstract string SiteName { get; } - public virtual IObservable MetaInfo { get; } - public abstract Uri SiteURL { get; } - public virtual Uri IconUri { get; } protected virtual async Task WhileWaiting(IWebDriver browser) { @@ -120,7 +122,7 @@ namespace Wabbajack.Lib.Downloaders Downloader = downloader; } public override string ShortDescription => $"Getting {Downloader.SiteName} Login"; - public override string ExtendedDescription { get; } + public override string ExtendedDescription { get; } = string.Empty; private readonly TaskCompletionSource _source = new TaskCompletionSource(); public Task Task => _source.Task; @@ -138,6 +140,4 @@ namespace Wabbajack.Lib.Downloaders } } } - - } diff --git a/Wabbajack.Lib/Downloaders/BethesdaNetDownloader.cs b/Wabbajack.Lib/Downloaders/BethesdaNetDownloader.cs index aae2c848..4602c126 100644 --- a/Wabbajack.Lib/Downloaders/BethesdaNetDownloader.cs +++ b/Wabbajack.Lib/Downloaders/BethesdaNetDownloader.cs @@ -30,6 +30,15 @@ namespace Wabbajack.Lib.Downloaders public class BethesdaNetDownloader : IUrlDownloader, INeedsLogin { public const string DataName = "bethesda-net-data"; + + public ReactiveCommand TriggerLogin { get; } + public ReactiveCommand ClearLogin { get; } + public IObservable IsLoggedIn => Utils.HaveEncryptedJsonObservable(DataName); + public string SiteName => "Bethesda.NET"; + public IObservable MetaInfo => Observable.Return(""); //"Wabbajack will start the game, then exit once you enter the Mods page"; + public Uri SiteURL => new Uri("https://bethesda.net"); + public Uri? IconUri { get; } + public BethesdaNetDownloader() { TriggerLogin = ReactiveCommand.CreateFromTask(() => Utils.CatchAndLog(RequestLoginAndCache), IsLoggedIn.Select(b => !b).ObserveOn(RxApp.MainThreadScheduler)); @@ -38,23 +47,23 @@ namespace Wabbajack.Lib.Downloaders private static async Task RequestLoginAndCache() { - var result = await Utils.Log(new RequestBethesdaNetLogin()).Task; + await Utils.Log(new RequestBethesdaNetLogin()).Task; } - public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) + public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) { var url = (Uri)DownloaderUtils.GetDirectURL(archiveINI); return StateFromUrl(url); } - internal static AbstractDownloadState StateFromUrl(Uri url) + internal static AbstractDownloadState? StateFromUrl(Uri url) { if (url != null && url.Host == "bethesda.net" && url.AbsolutePath.StartsWith("/en/mods/")) { var split = url.AbsolutePath.Split('/'); var game = split[3]; var modId = split[5]; - return new State {GameName = game, ContentId = modId}; + return new State(gameName: game, contentId: modId); } return null; } @@ -65,10 +74,11 @@ namespace Wabbajack.Lib.Downloaders await Utils.Log(new RequestBethesdaNetLogin()).Task; } - public static async Task Login(Game game) + public static async Task Login(Game game) { var metadata = game.MetaData(); - var gamePath = metadata.GameLocation()?.Combine(metadata.MainExecutable); + if (metadata.MainExecutable == null) throw new NotImplementedException(); + var gamePath = metadata.GameLocation().Combine(metadata.MainExecutable); var info = new ProcessStartInfo { FileName = @"Downloaders\BethesdaNet\bethnetlogin.exe", @@ -103,28 +113,25 @@ namespace Wabbajack.Lib.Downloaders } } - public AbstractDownloadState GetDownloaderState(string url) + public AbstractDownloadState? GetDownloaderState(string url) { return StateFromUrl(new Uri(url)); } - public ReactiveCommand TriggerLogin { get; } - public ReactiveCommand ClearLogin { get; } - public IObservable IsLoggedIn => Utils.HaveEncryptedJsonObservable(DataName); - public string SiteName => "Bethesda.NET"; - public IObservable MetaInfo => Observable.Return(""); //"Wabbajack will start the game, then exit once you enter the Mods page"; - public Uri SiteURL => new Uri("https://bethesda.net"); - public Uri IconUri { get; } - - [JsonName("BethesdaNetDownloader")] public class State : AbstractDownloadState { - public string GameName { get; set; } - public string ContentId { get; set; } - + public string GameName { get; } + public string ContentId { get; } + [JsonIgnore] - public override object[] PrimaryKey => new object[] {GameName, ContentId}; + public override object[] PrimaryKey => new object[] { GameName, ContentId }; + + public State(string gameName, string contentId) + { + GameName = gameName; + ContentId = contentId; + } public override bool IsWhitelisted(ServerWhitelist whitelist) { @@ -143,8 +150,8 @@ namespace Wabbajack.Lib.Downloaders using var got = await client.GetAsync( $"https://content.cdp.bethesda.net/{collected.CDPProductId}/{collected.CDPPropertiesId}/{chunk.sha}"); var data = await got.Content.ReadAsByteArrayAsync(); - if (collected.AESKey != null) - AESCTRDecrypt(collected.AESKey, collected.AESIV, data); + if (collected.AESKey != null) + AESCTRDecrypt(collected.AESKey, collected.AESIV!, data); if (chunk.uncompressed_size == chunk.chunk_size) await file.WriteAsync(data, 0, data.Length); @@ -198,7 +205,7 @@ namespace Wabbajack.Lib.Downloaders public override async Task Verify(Archive archive) { - var info = await ResolveDownloadInfo(); + await ResolveDownloadInfo(); return true; } @@ -222,7 +229,7 @@ namespace Wabbajack.Lib.Downloaders client.Headers.Add(("x-cdp-app", "UGC SDK")); client.Headers.Add(("x-cdp-app-ver", "0.9.11314/debug")); client.Headers.Add(("x-cdp-lib-ver", "0.9.11314/debug")); - client.Headers.Add(("x-cdp-platform","Win/32")); + client.Headers.Add(("x-cdp-platform", "Win/32")); posted = await client.PostAsync("https://api.bethesda.net/cdp-user/auth", new StringContent("{\"access_token\": \"" + info.AccessToken + "\"}", Encoding.UTF8, @@ -233,10 +240,10 @@ namespace Wabbajack.Lib.Downloaders var got = await client.GetAsync($"https://api.bethesda.net/mods/ugc-workshop/content/get?content_id={ContentId}"); JObject data = JObject.Parse(await got.Content.ReadAsStringAsync()); - var content = data["platform"]["response"]["content"]; + var content = data["platform"]!["response"]!["content"]!; - info.CDPBranchId = (int)content["cdp_branch_id"]; - info.CDPProductId = (int)content["cdp_product_id"]; + info.CDPBranchId = (int)content["cdp_branch_id"]!; + info.CDPProductId = (int)content["cdp_product_id"]!; client.Headers.Add(("Authorization", $"Token {info.CDPToken}")); client.Headers.Add(("Accept", "application/json")); @@ -246,7 +253,7 @@ namespace Wabbajack.Lib.Downloaders $"https://api.bethesda.net/cdp-user/projects/{info.CDPProductId}/branches/{info.CDPBranchId}/tree/.json"); var tree = (await got.Content.ReadAsStringAsync()).FromJsonString(); - + got.Dispose(); got = await client.PostAsync($"https://api.bethesda.net/mods/ugc-content/add-subscription", new StringContent($"{{\"content_id\": \"{ContentId}\"}}", Encoding.UTF8, "application/json")); @@ -255,14 +262,14 @@ namespace Wabbajack.Lib.Downloaders $"https://api.bethesda.net/cdp-user/projects/{info.CDPProductId}/branches/{info.CDPBranchId}/depots/.json"); var props_obj = JObject.Parse(await got.Content.ReadAsStringAsync()).Properties().First(); - info.CDPPropertiesId = (int)props_obj.Value["properties_id"]; - + info.CDPPropertiesId = (int)props_obj.Value["properties_id"]!; + info.AESKey = props_obj.Value["ex_info_A"].Select(e => (byte)e).ToArray(); info.AESIV = props_obj.Value["ex_info_B"].Select(e => (byte)e).Take(16).ToArray(); return (client, tree, info); } - + static int AESCTRDecrypt(byte[] Key, byte[] IV, byte[] Data) { IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CTR/NoPadding"); @@ -283,27 +290,26 @@ namespace Wabbajack.Lib.Downloaders public override string[] GetMetaIni() { - return new[] {"[General]", $"directURL=https://bethesda.net/en/mods/{GameName}/mod-detail/{ContentId}"}; + return new[] { "[General]", $"directURL=https://bethesda.net/en/mods/{GameName}/mod-detail/{ContentId}" }; } private class BeamLoginResponse { - public string access_token { get; set; } - + public string access_token { get; set; } = string.Empty; } private class CDPLoginResponse { - public string token { get; set; } + public string token { get; set; } = string.Empty; } private class CollectedBNetInfo { - public byte[] AESKey { get; set; } - public byte[] AESIV { get; set; } - public string AccessToken { get; set; } - public string CDPToken { get; set; } + public byte[] AESKey { get; set; } = null!; + public byte[] AESIV { get; set; } = null!; + public string AccessToken { get; set; } = string.Empty; + public string CDPToken { get; set; } = string.Empty; public int CDPBranchId { get; set; } public int CDPProductId { get; set; } public int CDPPropertiesId { get; set; } @@ -311,24 +317,24 @@ namespace Wabbajack.Lib.Downloaders public class CDPTree { - public List depot_list { get; set; } + public List depot_list { get; set; } = null!; public class Depot { - public List file_list { get; set; } + public List file_list { get; set; } = null!; public class CDPFile { public int chunk_count { get; set; } - public List chunk_list { get; set; } + public List chunk_list { get; set; } = null!; - public string name { get; set; } + public string? name { get; set; } public class Chunk { public int chunk_size { get; set; } public int index { get; set; } - public string sha { get; set; } + public string sha { get; set; } = string.Empty; public int uncompressed_size { get; set; } } } @@ -345,7 +351,7 @@ namespace Wabbajack.Lib.Downloaders public class RequestBethesdaNetLogin : AUserIntervention { public override string ShortDescription => "Logging into Bethesda.NET"; - public override string ExtendedDescription { get; } + public override string ExtendedDescription { get; } = string.Empty; private readonly TaskCompletionSource _source = new TaskCompletionSource(); public Task Task => _source.Task; @@ -367,8 +373,7 @@ namespace Wabbajack.Lib.Downloaders [JsonName("BethesdaNetData")] public class BethesdaNetData { - public string body { get; set; } + public string body { get; set; } = string.Empty; public Dictionary headers = new Dictionary(); } - } diff --git a/Wabbajack.Lib/Downloaders/DownloadDispatcher.cs b/Wabbajack.Lib/Downloaders/DownloadDispatcher.cs index e471cc0b..7a2a35e9 100644 --- a/Wabbajack.Lib/Downloaders/DownloadDispatcher.cs +++ b/Wabbajack.Lib/Downloaders/DownloadDispatcher.cs @@ -43,7 +43,7 @@ namespace Wabbajack.Lib.Downloaders IndexedDownloaders = Downloaders.ToDictionary(d => d.GetType()); } - public static async Task Infer(Uri uri) + public static async Task Infer(Uri uri) { foreach (var inf in Inferencers) { @@ -73,7 +73,7 @@ namespace Wabbajack.Lib.Downloaders /// /// /// - public static AbstractDownloadState ResolveArchive(string url) + public static AbstractDownloadState? ResolveArchive(string url) { return Downloaders.OfType().Select(d => d.GetDownloaderState(url)).FirstOrDefault(result => result != null); } @@ -111,13 +111,9 @@ namespace Wabbajack.Lib.Downloaders var patchName = $"{archive.Hash.ToHex()}_{upgrade.Hash.ToHex()}"; var patchPath = destination.Parent.Combine("_Patch_" + patchName); - var patchState = new Archive + var patchState = new Archive(new HTTPDownloader.State($"https://wabbajackcdn.b-cdn.net/updates/{patchName}")) { Name = patchName, - State = new HTTPDownloader.State - { - Url = $"https://wabbajackcdn.b-cdn.net/updates/{patchName}" - } }; var patchResult = await Download(patchState, patchPath); diff --git a/Wabbajack.Lib/Downloaders/DownloaderUtils.cs b/Wabbajack.Lib/Downloaders/DownloaderUtils.cs index fbe309d8..f798856f 100644 --- a/Wabbajack.Lib/Downloaders/DownloaderUtils.cs +++ b/Wabbajack.Lib/Downloaders/DownloaderUtils.cs @@ -4,7 +4,7 @@ namespace Wabbajack.Lib.Downloaders { public static class DownloaderUtils { - public static Uri GetDirectURL(dynamic meta) + public static Uri? GetDirectURL(dynamic? meta) { var url = meta?.General?.directURL; if (url == null) return null; diff --git a/Wabbajack.Lib/Downloaders/DropboxDownloader.cs b/Wabbajack.Lib/Downloaders/DropboxDownloader.cs index 6a2da893..0247b14e 100644 --- a/Wabbajack.Lib/Downloaders/DropboxDownloader.cs +++ b/Wabbajack.Lib/Downloaders/DropboxDownloader.cs @@ -7,13 +7,13 @@ namespace Wabbajack.Lib.Downloaders { public class DropboxDownloader : IDownloader, IUrlDownloader { - public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) + public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) { var urlstring = archiveINI?.General?.directURL; return GetDownloaderState(urlstring); } - public AbstractDownloadState GetDownloaderState(string url) + public AbstractDownloadState? GetDownloaderState(string url) { try { @@ -29,10 +29,7 @@ namespace Wabbajack.Lib.Downloaders uri.Query = query.ToString(); - return new HTTPDownloader.State() - { - Url = uri.ToString().Replace("dropbox.com:443/", "dropbox.com/") - }; + return new HTTPDownloader.State(uri.ToString().Replace("dropbox.com:443/", "dropbox.com/")); } catch (Exception) { diff --git a/Wabbajack.Lib/Downloaders/GameFileSourceDownloader.cs b/Wabbajack.Lib/Downloaders/GameFileSourceDownloader.cs index 45f1e80e..4268033e 100644 --- a/Wabbajack.Lib/Downloaders/GameFileSourceDownloader.cs +++ b/Wabbajack.Lib/Downloaders/GameFileSourceDownloader.cs @@ -9,33 +9,30 @@ namespace Wabbajack.Lib.Downloaders { public class GameFileSourceDownloader : IDownloader { - public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) + public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) { - var gameName = (string)archiveINI?.General?.gameName; - var gameFile = (string)archiveINI?.General?.gameFile; + var gameName = (string?)archiveINI?.General?.gameName; + var gameFile = (string?)archiveINI?.General?.gameFile; - if (gameFile == null || gameFile == null) + if (gameName == null || gameFile == null) return null; - var game = GameRegistry.GetByFuzzyName(gameName); - if (game == null) return null; + if (!GameRegistry.TryGetByFuzzyName(gameName, out var game)) return null; - var path = game.GameLocation(); + var path = game.TryGetGameLocation(); var filePath = path?.Combine(gameFile); - - if (!filePath?.Exists ?? false) + if (!(filePath?.Exists ?? false)) return null; var fp = filePath.Value; var hash = await fp.FileHashCachedAsync(); - return new State + return new State(game.InstalledVersion) { Game = game.Game, GameFile = (RelativePath)gameFile, - Hash = hash, - GameVersion = game.InstalledVersion + Hash = hash }; } @@ -49,10 +46,15 @@ namespace Wabbajack.Lib.Downloaders public Game Game { get; set; } public RelativePath GameFile { get; set; } public Hash Hash { get; set; } - public string GameVersion { get; set; } + public string GameVersion { get; } + + public State(string gameVersion) + { + GameVersion = gameVersion; + } [JsonIgnore] - internal AbsolutePath SourcePath => Game.MetaData().GameLocation().Value.Combine(GameFile); + internal AbsolutePath SourcePath => Game.MetaData().GameLocation().Combine(GameFile); [JsonIgnore] public override object[] PrimaryKey { get => new object[] {Game, GameVersion, GameFile}; } @@ -82,7 +84,7 @@ namespace Wabbajack.Lib.Downloaders return DownloadDispatcher.GetInstance(); } - public override string GetManifestURL(Archive a) + public override string? GetManifestURL(Archive a) { return null; } diff --git a/Wabbajack.Lib/Downloaders/GoogleDriveDownloader.cs b/Wabbajack.Lib/Downloaders/GoogleDriveDownloader.cs index 9c96e219..26602978 100644 --- a/Wabbajack.Lib/Downloaders/GoogleDriveDownloader.cs +++ b/Wabbajack.Lib/Downloaders/GoogleDriveDownloader.cs @@ -10,22 +10,19 @@ namespace Wabbajack.Lib.Downloaders { public class GoogleDriveDownloader : IDownloader, IUrlDownloader { - public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) + public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) { var url = archiveINI?.General?.directURL; return GetDownloaderState(url); } - public AbstractDownloadState GetDownloaderState(string url) + public AbstractDownloadState? GetDownloaderState(string url) { if (url != null && url.StartsWith("https://drive.google.com")) { var regex = new Regex("((?<=id=)[a-zA-Z0-9_-]*)|(?<=\\/file\\/d\\/)[a-zA-Z0-9_-]*"); var match = regex.Match(url); - return new State - { - Id = match.ToString() - }; + return new State(match.ToString()); } return null; @@ -38,10 +35,15 @@ namespace Wabbajack.Lib.Downloaders [JsonName("GoogleDriveDownloader")] public class State : AbstractDownloadState { - public string Id { get; set; } + public string Id { get; } [JsonIgnore] - public override object[] PrimaryKey { get => new object[] {Id}; } + public override object[] PrimaryKey => new object[] { Id }; + + public State(string id) + { + Id = id; + } public override bool IsWhitelisted(ServerWhitelist whitelist) { @@ -64,7 +66,7 @@ namespace Wabbajack.Lib.Downloaders var regex = new Regex("(?<=/uc\\?export=download&confirm=).*(?=;id=)"); var confirm = regex.Match(await response.Content.ReadAsStringAsync()); var url = $"https://drive.google.com/uc?export=download&confirm={confirm}&id={Id}"; - var httpState = new HTTPDownloader.State {Url = url, Client = client}; + var httpState = new HTTPDownloader.State(url) { Client = client }; return httpState; } diff --git a/Wabbajack.Lib/Downloaders/HTTPDownloader.cs b/Wabbajack.Lib/Downloaders/HTTPDownloader.cs index 9b051f38..912306a2 100644 --- a/Wabbajack.Lib/Downloaders/HTTPDownloader.cs +++ b/Wabbajack.Lib/Downloaders/HTTPDownloader.cs @@ -15,29 +15,24 @@ namespace Wabbajack.Lib.Downloaders { public class HTTPDownloader : IDownloader, IUrlDownloader { - - public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) + public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) { var url = archiveINI?.General?.directURL; return GetDownloaderState(url, archiveINI); } - public AbstractDownloadState GetDownloaderState(string uri) + public AbstractDownloadState? GetDownloaderState(string uri) { return GetDownloaderState(uri, null); } - public AbstractDownloadState GetDownloaderState(string url, dynamic archiveINI) + public AbstractDownloadState? GetDownloaderState(string url, dynamic? archiveINI) { if (url != null) { - var tmp = new State - { - Url = url - }; + var tmp = new State(url); if (archiveINI?.General?.directURLHeaders != null) { - tmp.Headers = new List(); tmp.Headers.AddRange(archiveINI?.General.directURLHeaders.Split('|')); } return tmp; @@ -53,15 +48,20 @@ namespace Wabbajack.Lib.Downloaders [JsonName("HttpDownloader")] public class State : AbstractDownloadState { - public string Url { get; set; } + public string Url { get; } - public List Headers { get; set; } + public List Headers { get; } = new List(); [JsonIgnore] - public Common.Http.Client Client { get; set; } + public Common.Http.Client? Client { get; set; } [JsonIgnore] - public override object[] PrimaryKey { get => new object[] {Url};} + public override object[] PrimaryKey => new object[] { Url }; + + public State(string url) + { + Url = url; + } public override bool IsWhitelisted(ServerWhitelist whitelist) { @@ -85,19 +85,18 @@ namespace Wabbajack.Lib.Downloaders var client = Client ?? new Common.Http.Client(); client.Headers.Add(("User-Agent", Consts.UserAgent)); - if (Headers != null) - foreach (var header in Headers) - { - var idx = header.IndexOf(':'); - var k = header.Substring(0, idx); - var v = header.Substring(idx + 1); - client.Headers.Add((k, v)); - } + foreach (var header in Headers) + { + var idx = header.IndexOf(':'); + var k = header.Substring(0, idx); + var v = header.Substring(idx + 1); + client.Headers.Add((k, v)); + } long totalRead = 0; var bufferSize = 1024 * 32; - Utils.Status($"Starting Download {a?.Name ?? Url}", Percent.Zero); + Utils.Status($"Starting Download {a.Name ?? Url}", Percent.Zero); var response = await client.GetAsync(Url); TOP: @@ -177,7 +176,7 @@ TOP: if (read == 0) break; Utils.Status($"Downloading {a.Name}", Percent.FactoryPutInRange(totalRead, contentSize)); - fs.Write(buffer, 0, read); + fs!.Write(buffer, 0, read); totalRead += read; } } @@ -203,7 +202,7 @@ TOP: public override string[] GetMetaIni() { - if (Headers != null) + if (Headers.Count > 0) return new [] {"[General]", $"directURL={Url}", $"directURLHeaders={string.Join("|", Headers)}"}; diff --git a/Wabbajack.Lib/Downloaders/IDownloader.cs b/Wabbajack.Lib/Downloaders/IDownloader.cs index ddac3407..17ced7bc 100644 --- a/Wabbajack.Lib/Downloaders/IDownloader.cs +++ b/Wabbajack.Lib/Downloaders/IDownloader.cs @@ -4,12 +4,11 @@ namespace Wabbajack.Lib.Downloaders { public interface IDownloader { - Task GetDownloaderState(dynamic archiveINI, bool quickMode = false); + Task GetDownloaderState(dynamic archiveINI, bool quickMode = false); /// /// Called before any downloads are inacted by the installer; /// Task Prepare(); } - } diff --git a/Wabbajack.Lib/Downloaders/INeedsLogin.cs b/Wabbajack.Lib/Downloaders/INeedsLogin.cs index 6f331011..8ce03afe 100644 --- a/Wabbajack.Lib/Downloaders/INeedsLogin.cs +++ b/Wabbajack.Lib/Downloaders/INeedsLogin.cs @@ -11,9 +11,9 @@ namespace Wabbajack.Lib.Downloaders ReactiveCommand ClearLogin { get; } IObservable IsLoggedIn { get; } string SiteName { get; } - IObservable MetaInfo { get; } + IObservable? MetaInfo { get; } Uri SiteURL { get; } - Uri IconUri { get; } + Uri? IconUri { get; } } public struct LoginReturnMessage diff --git a/Wabbajack.Lib/Downloaders/IUrlDownloader.cs b/Wabbajack.Lib/Downloaders/IUrlDownloader.cs index 7650e0e5..1b4384af 100644 --- a/Wabbajack.Lib/Downloaders/IUrlDownloader.cs +++ b/Wabbajack.Lib/Downloaders/IUrlDownloader.cs @@ -2,6 +2,6 @@ { public interface IUrlDownloader : IDownloader { - AbstractDownloadState GetDownloaderState(string url); + AbstractDownloadState? GetDownloaderState(string url); } } diff --git a/Wabbajack.Lib/Downloaders/MEGADownloader.cs b/Wabbajack.Lib/Downloaders/MEGADownloader.cs index e612c2c3..e318a06a 100644 --- a/Wabbajack.Lib/Downloaders/MEGADownloader.cs +++ b/Wabbajack.Lib/Downloaders/MEGADownloader.cs @@ -23,8 +23,6 @@ namespace Wabbajack.Lib.Downloaders try { authInfos = MegaApiClient.GenerateAuthInfos(username, password.ToNormalString()); - username = null; - password = null; } catch (ApiException e) { @@ -72,16 +70,16 @@ namespace Wabbajack.Lib.Downloaders IsLoggedIn.ObserveOnGuiThread()); } - public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) + public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) { var url = archiveINI?.General?.directURL; return GetDownloaderState(url); } - public AbstractDownloadState GetDownloaderState(string url) + public AbstractDownloadState? GetDownloaderState(string url) { if (url != null && url.StartsWith(Consts.MegaPrefix)) - return new State { Url = url}; + return new State(url); return null; } @@ -92,6 +90,11 @@ namespace Wabbajack.Lib.Downloaders [JsonName("MegaDownloader")] public class State : HTTPDownloader.State { + public State(string url) + : base(url) + { + } + private static MegaApiClient MegaApiClient => DownloadDispatcher.GetInstance().MegaApiClient; private void MegaLogin() diff --git a/Wabbajack.Lib/Downloaders/ManualDownloader.cs b/Wabbajack.Lib/Downloaders/ManualDownloader.cs index f08fe587..805ebc45 100644 --- a/Wabbajack.Lib/Downloaders/ManualDownloader.cs +++ b/Wabbajack.Lib/Downloaders/ManualDownloader.cs @@ -18,8 +18,8 @@ namespace Wabbajack.Lib.Downloaders class FileEvent { - public string FullPath { get; set; } - public string Name { get; set; } + public string FullPath { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; public long Size { get; set; } } @@ -57,10 +57,10 @@ namespace Wabbajack.Lib.Downloaders } } - public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) + public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) { var url = archiveINI?.General?.manualURL; - return url != null ? new State { Url = url} : null; + return url != null ? new State(url) : null; } public async Task Prepare() @@ -70,10 +70,15 @@ namespace Wabbajack.Lib.Downloaders [JsonName("ManualDownloader")] public class State : AbstractDownloadState { - public string Url { get; set; } + public string Url { get; } [JsonIgnore] - public override object[] PrimaryKey { get => new object[] {Url}; } + public override object[] PrimaryKey => new object[] { Url }; + + public State(string url) + { + Url = url; + } public override bool IsWhitelisted(ServerWhitelist whitelist) { @@ -83,7 +88,7 @@ namespace Wabbajack.Lib.Downloaders public override async Task Download(Archive a, AbsolutePath destination) { var (uri, client) = await Utils.Log(await ManuallyDownloadFile.Create(this)).Task; - var state = new HTTPDownloader.State {Url = uri.ToString(), Client = client}; + var state = new HTTPDownloader.State(uri.ToString()) { Client = client }; return await state.Download(a, destination); } @@ -104,10 +109,10 @@ namespace Wabbajack.Lib.Downloaders public override string[] GetMetaIni() { - return new [] { + return new [] + { "[General]", - $"manualURL={Url}" - + $"manualURL={Url}", }; } } diff --git a/Wabbajack.Lib/Downloaders/MediaFireDownloader.cs b/Wabbajack.Lib/Downloaders/MediaFireDownloader.cs index 03e09d90..a9678fd8 100644 --- a/Wabbajack.Lib/Downloaders/MediaFireDownloader.cs +++ b/Wabbajack.Lib/Downloaders/MediaFireDownloader.cs @@ -10,22 +10,24 @@ namespace Wabbajack.Lib.Downloaders { public class MediaFireDownloader : IUrlDownloader { - public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) + public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) { Uri url = DownloaderUtils.GetDirectURL(archiveINI); if (url == null || url.Host != "www.mediafire.com") return null; - return new State - { - Url = url.ToString() - }; + return new State(url.ToString()); } public class State : AbstractDownloadState { - public string Url { get; set; } + public string Url { get; } - public override object[] PrimaryKey { get => new object[] {Url};} + public override object[] PrimaryKey => new object[] { Url }; + + public State(string url) + { + Url = url; + } public override bool IsWhitelisted(ServerWhitelist whitelist) { @@ -35,6 +37,7 @@ namespace Wabbajack.Lib.Downloaders public override async Task Download(Archive a, AbsolutePath destination) { var result = await Resolve(); + if (result == null) return false; return await result.Download(a, destination); } @@ -43,7 +46,7 @@ namespace Wabbajack.Lib.Downloaders return await Resolve() != null; } - private async Task Resolve() + private async Task Resolve() { using (var d = await Driver.Create()) { @@ -52,10 +55,9 @@ namespace Wabbajack.Lib.Downloaders await Task.Delay(1000); var newURL = await d.GetAttr("a.input", "href"); if (newURL == null || !newURL.StartsWith("http")) return null; - return new HTTPDownloader.State() + return new HTTPDownloader.State(newURL) { Client = new Common.Http.Client(), - Url = newURL }; } } @@ -84,15 +86,12 @@ namespace Wabbajack.Lib.Downloaders { } - public AbstractDownloadState GetDownloaderState(string u) + public AbstractDownloadState? GetDownloaderState(string u) { var url = new Uri(u); if (url.Host != "www.mediafire.com") return null; - return new State - { - Url = url.ToString() - }; + return new State(url.ToString()); } } } diff --git a/Wabbajack.Lib/Downloaders/ModDBDownloader.cs b/Wabbajack.Lib/Downloaders/ModDBDownloader.cs index d48064ba..9d0022ae 100644 --- a/Wabbajack.Lib/Downloaders/ModDBDownloader.cs +++ b/Wabbajack.Lib/Downloaders/ModDBDownloader.cs @@ -11,20 +11,17 @@ namespace Wabbajack.Lib.Downloaders { public class ModDBDownloader : IDownloader, IUrlDownloader { - public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) + public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) { var url = archiveINI?.General?.directURL; return GetDownloaderState(url); } - public AbstractDownloadState GetDownloaderState(string url) + public AbstractDownloadState? GetDownloaderState(string url) { if (url != null && url.StartsWith("https://www.moddb.com/downloads/start")) { - return new State - { - Url = url - }; + return new State(url); } return null; @@ -37,10 +34,15 @@ namespace Wabbajack.Lib.Downloaders [JsonName("ModDBDownloader")] public class State : AbstractDownloadState { - public string Url { get; set; } + public string Url { get; } [JsonIgnore] - public override object[] PrimaryKey { get => new object[]{Url}; } + public override object[] PrimaryKey => new object[] { Url }; + + public State(string url) + { + Url = url; + } public override bool IsWhitelisted(ServerWhitelist whitelist) { @@ -56,7 +58,7 @@ namespace Wabbajack.Lib.Downloaders { try { - await new HTTPDownloader.State {Url = url}.Download(a, destination); + await new HTTPDownloader.State(url).Download(a, destination); return true; } catch (Exception) diff --git a/Wabbajack.Lib/Downloaders/NexusDownloader.cs b/Wabbajack.Lib/Downloaders/NexusDownloader.cs index 0aca79cf..087b46cf 100644 --- a/Wabbajack.Lib/Downloaders/NexusDownloader.cs +++ b/Wabbajack.Lib/Downloaders/NexusDownloader.cs @@ -19,8 +19,8 @@ namespace Wabbajack.Lib.Downloaders { private bool _prepared; private AsyncLock _lock = new AsyncLock(); - private UserStatus _status; - private NexusApiClient _client; + private UserStatus? _status; + private NexusApiClient? _client; public IObservable IsLoggedIn => Utils.HaveEncryptedJsonObservable("nexusapikey"); @@ -50,9 +50,9 @@ namespace Wabbajack.Lib.Downloaders canExecute: IsLoggedIn.ObserveOnGuiThread()); } - public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) + public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) { - var general = archiveINI?.General; + var general = archiveINI.General; if (general.modID != null && general.fileID != null && general.gameName != null) { @@ -135,17 +135,17 @@ namespace Wabbajack.Lib.Downloaders [JsonIgnore] public Uri URL => new Uri($"http://nexusmods.com/{Game.MetaData().NexusName}/mods/{ModID}"); - public string Name { get; set; } + public string? Name { get; set; } - public string Author { get; set; } + public string? Author { get; set; } - public string Version { get; set; } + public string? Version { get; set; } - public Uri ImageURL { get; set; } + public Uri? ImageURL { get; set; } public bool IsNSFW { get; set; } - public string Description { get; set; } + public string? Description { get; set; } [JsonProperty("GameName")] [JsonConverter(typeof(Utils.GameConverter))] @@ -184,10 +184,7 @@ namespace Wabbajack.Lib.Downloaders Utils.Log($"Downloading Nexus Archive - {a.Name} - {Game} - {ModID} - {FileID}"); - return await new HTTPDownloader.State - { - Url = url - }.Download(a, destination); + return await new HTTPDownloader.State(url).Download(a, destination); } public override async Task Verify(Archive a) diff --git a/Wabbajack.Lib/Downloaders/SteamWorkshopDownloader.cs b/Wabbajack.Lib/Downloaders/SteamWorkshopDownloader.cs index 96cfadde..cdf1ad5a 100644 --- a/Wabbajack.Lib/Downloaders/SteamWorkshopDownloader.cs +++ b/Wabbajack.Lib/Downloaders/SteamWorkshopDownloader.cs @@ -14,20 +14,21 @@ namespace Wabbajack.Lib.Downloaders { public class SteamWorkshopDownloader : IUrlDownloader { - private SteamWorkshopItem _item; - - public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) + public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) { var id = archiveINI?.General?.itemID; var steamID = archiveINI?.General?.steamID; var size = archiveINI?.General?.itemSize; - _item = new SteamWorkshopItem + if (steamID == null) + { + throw new ArgumentException("Steam workshop item had no steam ID."); + } + var item = new SteamWorkshopItem(GameRegistry.GetBySteamID(int.Parse(steamID))) { ItemID = id != null ? int.Parse(id) : 0, Size = size != null ? int.Parse(size) : 0, - Game = steamID != null ? GameRegistry.GetBySteamID(int.Parse(steamID)) : null }; - return new State {Item = _item}; + return new State(item); } public async Task Prepare() @@ -41,8 +42,14 @@ namespace Wabbajack.Lib.Downloaders public class State : AbstractDownloadState { - public SteamWorkshopItem Item { get; set; } - public override object[] PrimaryKey { get => new object[] {Item.Game, Item.ItemID}; } + public SteamWorkshopItem Item { get; } + + public override object[] PrimaryKey => new object[] { Item.Game, Item.ItemID }; + + public State(SteamWorkshopItem item) + { + Item = item; + } public override bool IsWhitelisted(ServerWhitelist whitelist) { diff --git a/Wabbajack.Lib/Downloaders/UrlDownloaders/BethesdaNetInferencer.cs b/Wabbajack.Lib/Downloaders/UrlDownloaders/BethesdaNetInferencer.cs index 1265b46e..0ef367e1 100644 --- a/Wabbajack.Lib/Downloaders/UrlDownloaders/BethesdaNetInferencer.cs +++ b/Wabbajack.Lib/Downloaders/UrlDownloaders/BethesdaNetInferencer.cs @@ -5,7 +5,7 @@ namespace Wabbajack.Lib.Downloaders.UrlDownloaders { public class BethesdaNetInferencer : IUrlInferencer { - public async Task Infer(Uri uri) + public async Task Infer(Uri uri) { return BethesdaNetDownloader.StateFromUrl(uri); } diff --git a/Wabbajack.Lib/Downloaders/UrlDownloaders/IUrlInferencer.cs b/Wabbajack.Lib/Downloaders/UrlDownloaders/IUrlInferencer.cs index a0febfa3..9aaf73d7 100644 --- a/Wabbajack.Lib/Downloaders/UrlDownloaders/IUrlInferencer.cs +++ b/Wabbajack.Lib/Downloaders/UrlDownloaders/IUrlInferencer.cs @@ -5,6 +5,6 @@ namespace Wabbajack.Lib.Downloaders.UrlDownloaders { public interface IUrlInferencer { - Task Infer(Uri uri); + Task Infer(Uri uri); } } diff --git a/Wabbajack.Lib/Downloaders/UrlDownloaders/YoutubeInferencer.cs b/Wabbajack.Lib/Downloaders/UrlDownloaders/YoutubeInferencer.cs index 66cff7eb..c3f96716 100644 --- a/Wabbajack.Lib/Downloaders/UrlDownloaders/YoutubeInferencer.cs +++ b/Wabbajack.Lib/Downloaders/UrlDownloaders/YoutubeInferencer.cs @@ -8,10 +8,9 @@ namespace Wabbajack.Lib.Downloaders.UrlDownloaders { public class YoutubeInferencer : IUrlInferencer { - - public async Task Infer(Uri uri) + public async Task Infer(Uri uri) { - var state = (YouTubeDownloader.State)YouTubeDownloader.UriToState(uri); + var state = YouTubeDownloader.UriToState(uri) as YouTubeDownloader.State; if (state == null) return null; var client = new YoutubeClient(Common.Http.ClientFactory.Client); @@ -24,13 +23,13 @@ namespace Wabbajack.Lib.Downloaders.UrlDownloaders .Select(line => { var segments = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); - if (segments.Length == 0) return (TimeSpan.Zero, null); + if (segments.Length == 0) return (TimeSpan.Zero, string.Empty); if (TryParseEx(segments.First(), out var s1)) return (s1, string.Join(" ", segments.Skip(1))); if (TryParseEx(Enumerable.Last(segments), out var s2)) return (s2, string.Join(" ", Utils.ButLast(segments))); - return (TimeSpan.Zero, null); + return (TimeSpan.Zero, string.Empty); }) .Where(t => t.Item2 != null) .ToList(); diff --git a/Wabbajack.Lib/Downloaders/YouTubeDownloader.cs b/Wabbajack.Lib/Downloaders/YouTubeDownloader.cs index 713000bd..9d35c7f7 100644 --- a/Wabbajack.Lib/Downloaders/YouTubeDownloader.cs +++ b/Wabbajack.Lib/Downloaders/YouTubeDownloader.cs @@ -21,10 +21,10 @@ namespace Wabbajack.Lib.Downloaders { public class YouTubeDownloader : IDownloader { - public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) + public async Task GetDownloaderState(dynamic archiveINI, bool quickMode) { var directURL = (Uri)DownloaderUtils.GetDirectURL(archiveINI); - var state = (State)UriToState(directURL); + var state = UriToState(directURL) as State; if (state == null) return state; var idx = 0; @@ -45,7 +45,7 @@ namespace Wabbajack.Lib.Downloaders return state; } - internal static AbstractDownloadState UriToState(Uri directURL) + internal static AbstractDownloadState? UriToState(Uri directURL) { if (directURL == null || !directURL.Host.EndsWith("youtube.com")) { @@ -53,7 +53,7 @@ namespace Wabbajack.Lib.Downloaders } var key = HttpUtility.ParseQueryString(directURL.Query)["v"]; - return key != null ? new State {Key = key} : null; + return key != null ? new State(key) : null; } public async Task Prepare() @@ -63,13 +63,18 @@ namespace Wabbajack.Lib.Downloaders [JsonName("YouTubeDownloader")] public class State : AbstractDownloadState { - public string Key { get; set; } + public string Key { get; } public List Tracks { get; set; } = new List(); [JsonIgnore] public override object[] PrimaryKey => new object[] {Key}; + public State(string key) + { + Key = key; + } + [JsonName("YouTubeTrack")] public class Track { @@ -79,8 +84,8 @@ namespace Wabbajack.Lib.Downloaders WAV } public FormatEnum Format { get; set; } - - public string Name { get; set; } + + public string Name { get; set; } = string.Empty; public TimeSpan Start { get; set; } diff --git a/Wabbajack.Lib/Exceptions/HttpException.cs b/Wabbajack.Lib/Exceptions/HttpException.cs index 41d46b03..af0a3bb0 100644 --- a/Wabbajack.Lib/Exceptions/HttpException.cs +++ b/Wabbajack.Lib/Exceptions/HttpException.cs @@ -12,6 +12,5 @@ namespace Wabbajack.Lib.Exceptions Code = code; Reason = reason; } - } } diff --git a/Wabbajack.Lib/FileUploader/AuthorAPI.cs b/Wabbajack.Lib/FileUploader/AuthorAPI.cs index 149b4d35..638dc927 100644 --- a/Wabbajack.Lib/FileUploader/AuthorAPI.cs +++ b/Wabbajack.Lib/FileUploader/AuthorAPI.cs @@ -21,9 +21,9 @@ namespace Wabbajack.Lib.FileUploader { public static IObservable HaveAuthorAPIKey => Utils.HaveEncryptedJsonObservable(Consts.AuthorAPIKeyFile); - public static string ApiKeyOverride = null; + public static string? ApiKeyOverride = null; - public static async Task GetAPIKey(string apiKey = null) + public static async Task GetAPIKey(string? apiKey = null) { if (ApiKeyOverride != null) return ApiKeyOverride; return apiKey ?? (await Consts.LocalAppDataPath.Combine(Consts.AuthorAPIKeyFile).ReadAllTextAsync()).Trim(); @@ -32,7 +32,7 @@ namespace Wabbajack.Lib.FileUploader public static Uri UploadURL => new Uri($"{Consts.WabbajackBuildServerUri}upload_file"); public static long BLOCK_SIZE = (long)1024 * 1024 * 2; public static int MAX_CONNECTIONS = 8; - public static Task UploadFile(AbsolutePath filename, Action progressFn, string apikey=null) + public static Task UploadFile(AbsolutePath filename, Action progressFn, string? apikey = null) { var tcs = new TaskCompletionSource(); Task.Run(async () => @@ -123,7 +123,7 @@ namespace Wabbajack.Lib.FileUploader return tcs.Task; } - public static async Task GetAuthorizedClient(string apiKey=null) + public static async Task GetAuthorizedClient(string? apiKey = null) { var client = new Common.Http.Client(); client.Headers.Add(("X-API-KEY", await GetAPIKey(apiKey))); diff --git a/Wabbajack.Lib/LibCefHelpers/Init.cs b/Wabbajack.Lib/LibCefHelpers/Init.cs index 573fd9db..9f3c9985 100644 --- a/Wabbajack.Lib/LibCefHelpers/Init.cs +++ b/Wabbajack.Lib/LibCefHelpers/Init.cs @@ -78,10 +78,10 @@ namespace Wabbajack.Lib.LibCefHelpers [JsonName("HttpCookie")] public class Cookie { - public string Name { get; set; } - public string Value { get; set; } - public string Domain { get; set; } - public string Path { get; set; } + public string Name { get; set; } = string.Empty; + public string Value { get; set; } = string.Empty; + public string Domain { get; set; } = string.Empty; + public string Path { get; set; } = string.Empty; } public static void Init() diff --git a/Wabbajack.Lib/MO2Compiler.cs b/Wabbajack.Lib/MO2Compiler.cs index 4ddc87ca..69b1546b 100644 --- a/Wabbajack.Lib/MO2Compiler.cs +++ b/Wabbajack.Lib/MO2Compiler.cs @@ -25,7 +25,6 @@ namespace Wabbajack.Lib { public class MO2Compiler : ACompiler { - private AbsolutePath _mo2DownloadsFolder; public AbsolutePath MO2Folder; @@ -38,7 +37,7 @@ namespace Wabbajack.Lib public override AbsolutePath GamePath { get; } - public GameMetaData CompilingGame { get; set; } + public GameMetaData CompilingGame { get; } public override AbsolutePath ModListOutputFolder => ((RelativePath)"output_folder").RelativeToEntryPoint(); @@ -48,7 +47,19 @@ namespace Wabbajack.Lib Consts.LocalAppDataPath.Combine( $"vfs_compile_cache-{Path.Combine((string)MO2Folder ?? "Unknown", "ModOrganizer.exe").StringSha256Hex()}.bin"); + public dynamic MO2Ini { get; } + + public static AbsolutePath GetTypicalDownloadsFolder(AbsolutePath mo2Folder) => mo2Folder.Combine("downloads"); + + public AbsolutePath MO2ProfileDir => MO2Folder.Combine("profiles", MO2Profile); + + public ConcurrentBag ExtraFiles { get; private set; } = new ConcurrentBag(); + public Dictionary ModInis { get; } = new Dictionary(); + + public HashSet SelectedProfiles { get; set; } = new HashSet(); + public MO2Compiler(AbsolutePath mo2Folder, string mo2Profile, AbsolutePath outputFile) + : base(steps: 20) { MO2Folder = mo2Folder; MO2Profile = mo2Profile; @@ -59,8 +70,6 @@ namespace Wabbajack.Lib ModListOutputFile = outputFile; } - public dynamic MO2Ini { get; } - public AbsolutePath MO2DownloadsFolder { get @@ -75,20 +84,10 @@ namespace Wabbajack.Lib set => _mo2DownloadsFolder = value; } - public static AbsolutePath GetTypicalDownloadsFolder(AbsolutePath mo2Folder) => mo2Folder.Combine("downloads"); - - public AbsolutePath MO2ProfileDir => MO2Folder.Combine("profiles", MO2Profile); - - internal UserStatus User { get; private set; } - public ConcurrentBag ExtraFiles { get; private set; } - public Dictionary ModInis { get; private set; } - - public HashSet SelectedProfiles { get; set; } = new HashSet(); - protected override async Task _Begin(CancellationToken cancel) { if (cancel.IsCancellationRequested) return false; - ConfigureProcessor(20, ConstructDynamicNumThreads(await RecommendQueueSize())); + Queue.SetActiveThreadsObservable(ConstructDynamicNumThreads(await RecommendQueueSize())); UpdateTracker.Reset(); UpdateTracker.NextStep("Gathering information"); Info("Looking for other profiles"); @@ -126,6 +125,11 @@ namespace Wabbajack.Lib if (lootPath.Exists) { + if (CompilingGame.MO2Name == null) + { + throw new ArgumentException("Compiling game had no MO2 name specified."); + } + var lootGameDirs = new [] { CompilingGame.MO2Name, // most of the games use the MO2 name @@ -170,9 +174,8 @@ namespace Wabbajack.Lib IndexedArchives = (await MO2DownloadsFolder.EnumerateFiles() .Where(f => f.WithExtension(Consts.MetaFileExtension).Exists) - .PMap(Queue, async f => new IndexedArchive + .PMap(Queue, async f => new IndexedArchive(VFS.Index.ByRootPath[f]) { - File = VFS.Index.ByRootPath[f], Name = (string)f.FileName, IniData = f.WithExtension(Consts.MetaFileExtension).LoadIniFile(), Meta = await f.WithExtension(Consts.MetaFileExtension).ReadAllTextAsync() @@ -216,10 +219,9 @@ namespace Wabbajack.Lib .GroupBy(f => f.Hash) .ToDictionary(f => f.Key, f => f.AsEnumerable()); - AllFiles = mo2Files.Concat(gameFiles) + AllFiles.SetTo(mo2Files.Concat(gameFiles) .Concat(lootFiles) - .DistinctBy(f => f.Path) - .ToList(); + .DistinctBy(f => f.Path)); Info($"Found {AllFiles.Count} files to build into mod list"); @@ -239,13 +241,10 @@ namespace Wabbajack.Lib Error($"Found {dups.Count} duplicates, exiting"); } - ExtraFiles = new ConcurrentBag(); - - if (cancel.IsCancellationRequested) return false; UpdateTracker.NextStep("Loading INIs"); - ModInis = MO2Folder.Combine(Consts.MO2ModFolderName) + ModInis.SetTo(MO2Folder.Combine(Consts.MO2ModFolderName) .EnumerateDirectories() .Select(f => { @@ -254,7 +253,7 @@ namespace Wabbajack.Lib return metaPath.Exists ? (mod_name: f, metaPath.LoadIniFile()) : default; }) .Where(f => f.Item1 != default) - .ToDictionary(f => f.Item1, f => f.Item2); + .Select(f => new KeyValuePair(f.Item1, f.Item2))); if (cancel.IsCancellationRequested) return false; var stack = MakeStack(); @@ -270,7 +269,7 @@ namespace Wabbajack.Lib PrintNoMatches(noMatch); if (CheckForNoMatchExit(noMatch)) return false; - InstallDirectives = results.Where(i => !(i is IgnoredDirectly)).ToList(); + InstallDirectives.SetTo(results.Where(i => !(i is IgnoredDirectly))); Info("Getting Nexus api_key, please click authorize if a browser window appears"); @@ -337,7 +336,7 @@ namespace Wabbajack.Lib { return a; } - })).Where(a => a != null).ToHashSet(); + })).NotNull().ToHashSet(); if (remove.Count == 0) return; @@ -389,7 +388,6 @@ namespace Wabbajack.Lib }); } - private async Task IncludeArchiveMetadata() { Utils.Log($"Including {SelectedArchives.Count} .meta files for downloads"); @@ -411,13 +409,12 @@ namespace Wabbajack.Lib /// private void ResetMembers() { - AllFiles = null; - InstallDirectives = null; - SelectedArchives = null; - ExtraFiles = null; + AllFiles = new List(); + InstallDirectives = new List(); + SelectedArchives = new List(); + ExtraFiles = new ConcurrentBag(); } - /// /// Fills in the Patch fields in files that require them /// @@ -450,7 +447,7 @@ namespace Wabbajack.Lib private async Task BuildArchivePatches(Hash archiveSha, IEnumerable group, Dictionary absolutePaths) { - using var files = await VFS.StageWith(@group.Select(g => VFS.Index.FileForArchiveHashPath(g.ArchiveHashPath))); + await using var files = await VFS.StageWith(@group.Select(g => VFS.Index.FileForArchiveHashPath(g.ArchiveHashPath))); var byPath = files.GroupBy(f => string.Join("|", f.FilesInFullPath.Skip(1).Select(i => i.Name))) .ToDictionary(f => f.Key, f => f.First()); // Now Create the patches @@ -487,8 +484,7 @@ namespace Wabbajack.Lib return returnStream; } - Error($"Couldn't load data for {to}"); - return null; + throw new ArgumentException($"Couldn't load data for {to}"); } public override IEnumerable GetStack() @@ -567,12 +563,5 @@ namespace Wabbajack.Lib new DropAll(this) }; } - - public class IndexedFileMatch - { - public IndexedArchive Archive; - public IndexedArchiveEntry Entry; - public DateTime LastModified; - } } } diff --git a/Wabbajack.Lib/MO2Installer.cs b/Wabbajack.Lib/MO2Installer.cs index a1fa887a..f0647c37 100644 --- a/Wabbajack.Lib/MO2Installer.cs +++ b/Wabbajack.Lib/MO2Installer.cs @@ -32,14 +32,18 @@ namespace Wabbajack.Lib public AbsolutePath? GameFolder { get; set; } + public GameMetaData Game { get; } + public MO2Installer(AbsolutePath archive, ModList modList, AbsolutePath outputFolder, AbsolutePath downloadFolder, SystemParameters parameters) : base( archive: archive, modList: modList, outputFolder: outputFolder, downloadFolder: downloadFolder, - parameters: parameters) + parameters: parameters, + steps: 20) { + Game = ModList.GameType.MetaData(); } protected override async Task _Begin(CancellationToken cancel) @@ -48,28 +52,27 @@ namespace Wabbajack.Lib var metric = Metrics.Send(Metrics.BeginInstall, ModList.Name); Utils.Log("Configuring Processor"); - ConfigureProcessor(20, ConstructDynamicNumThreads(await RecommendQueueSize())); - var game = ModList.GameType.MetaData(); + Queue.SetActiveThreadsObservable(ConstructDynamicNumThreads(await RecommendQueueSize())); if (GameFolder == null) - GameFolder = game.GameLocation(); + GameFolder = Game.TryGetGameLocation(); if (GameFolder == null) { - var otherGame = game.CommonlyConfusedWith.Where(g => g.MetaData().IsInstalled).Select(g => g.MetaData()).FirstOrDefault(); + var otherGame = Game.CommonlyConfusedWith.Where(g => g.MetaData().IsInstalled).Select(g => g.MetaData()).FirstOrDefault(); if (otherGame != null) { await Utils.Log(new CriticalFailureIntervention( - $"In order to do a proper install Wabbajack needs to know where your {game.HumanFriendlyGameName} folder resides. However this game doesn't seem to be installed, we did however find a installed " + + $"In order to do a proper install Wabbajack needs to know where your {Game.HumanFriendlyGameName} folder resides. However this game doesn't seem to be installed, we did however find a installed " + $"copy of {otherGame.HumanFriendlyGameName}, did you install the wrong game?", - $"Could not locate {game.HumanFriendlyGameName}")) + $"Could not locate {Game.HumanFriendlyGameName}")) .Task; } else { await Utils.Log(new CriticalFailureIntervention( - $"In order to do a proper install Wabbajack needs to know where your {game.HumanFriendlyGameName} folder resides. However this game doesn't seem to be installed", - $"Could not locate {game.HumanFriendlyGameName}")) + $"In order to do a proper install Wabbajack needs to know where your {Game.HumanFriendlyGameName} folder resides. However this game doesn't seem to be installed", + $"Could not locate {Game.HumanFriendlyGameName}")) .Task; } @@ -169,7 +172,6 @@ namespace Wabbajack.Lib private void CreateOutputMods() { - OutputFolder.Combine("profiles") .EnumerateFiles(true) .Where(f => f.FileName == Consts.SettingsIni) @@ -235,7 +237,7 @@ namespace Wabbajack.Lib foreach (var esm in ModList.Directives.OfType().ToList()) { var filename = esm.To.FileName; - var gameFile = GameFolder.Value.Combine((RelativePath)"Data", filename); + var gameFile = GameFolder!.Value.Combine((RelativePath)"Data", filename); Utils.Log($"Validating {filename}"); var hash = gameFile.FileHash(); if (hash != esm.SourceESMHash) @@ -310,7 +312,7 @@ namespace Wabbajack.Lib private async Task GenerateCleanedESM(CleanedESM directive) { var filename = directive.To.FileName; - var gameFile = GameFolder.Value.Combine((RelativePath)"Data", filename); + var gameFile = GameFolder!.Value.Combine((RelativePath)"Data", filename); Info($"Generating cleaned ESM for {filename}"); if (!gameFile.Exists) throw new InvalidDataException($"Missing {filename} at {gameFile}"); Status($"Hashing game version of {filename}"); @@ -329,6 +331,10 @@ namespace Wabbajack.Lib private void SetScreenSizeInPrefs() { + if (SystemParameters == null) + { + throw new ArgumentNullException("System Parameters was null. Cannot set screen size prefs"); + } var config = new IniParserConfiguration {AllowDuplicateKeys = true, AllowDuplicateSections = true}; foreach (var file in OutputFolder.Combine("profiles").EnumerateFiles() .Where(f => ((string)f.FileName).EndsWith("refs.ini"))) @@ -375,8 +381,8 @@ namespace Wabbajack.Lib { var data = Encoding.UTF8.GetString(await LoadBytesFromPath(directive.SourceDataID)); - data = data.Replace(Consts.GAME_PATH_MAGIC_BACK, (string)GameFolder); - data = data.Replace(Consts.GAME_PATH_MAGIC_DOUBLE_BACK, ((string)GameFolder).Replace("\\", "\\\\")); + data = data.Replace(Consts.GAME_PATH_MAGIC_BACK, (string)GameFolder!); + data = data.Replace(Consts.GAME_PATH_MAGIC_DOUBLE_BACK, ((string)GameFolder!).Replace("\\", "\\\\")); data = data.Replace(Consts.GAME_PATH_MAGIC_FORWARD, ((string)GameFolder).Replace("\\", "/")); data = data.Replace(Consts.MO2_PATH_MAGIC_BACK, (string)OutputFolder); diff --git a/Wabbajack.Lib/Manifest.cs b/Wabbajack.Lib/Manifest.cs index 4058e922..1aa4d846 100644 --- a/Wabbajack.Lib/Manifest.cs +++ b/Wabbajack.Lib/Manifest.cs @@ -41,12 +41,11 @@ namespace Wabbajack.Lib InstallSize = modlist.InstallSize; // meta is being omitted due to it being useless and not very space friendly - Archives = modlist.Archives.Select(a => new Archive + Archives = modlist.Archives.Select(a => new Archive(a.State) { Hash = a.Hash, Name = a.Name, Size = a.Size, - State = a.State }).ToList(); } } diff --git a/Wabbajack.Lib/ModListRegistry/ModListMetadata.cs b/Wabbajack.Lib/ModListRegistry/ModListMetadata.cs index a74b52d3..df19992b 100644 --- a/Wabbajack.Lib/ModListRegistry/ModListMetadata.cs +++ b/Wabbajack.Lib/ModListRegistry/ModListMetadata.cs @@ -13,13 +13,13 @@ namespace Wabbajack.Lib.ModListRegistry public class ModlistMetadata { [JsonProperty("title")] - public string Title { get; set; } + public string Title { get; set; } = string.Empty; [JsonProperty("description")] - public string Description { get; set; } + public string Description { get; set; } = string.Empty; [JsonProperty("author")] - public string Author { get; set; } + public string Author { get; set; } = string.Empty; [JsonProperty("game")] public Game Game { get; set; } @@ -33,7 +33,7 @@ namespace Wabbajack.Lib.ModListRegistry public LinksObject Links { get; set; } = new LinksObject(); [JsonProperty("download_metadata")] - public DownloadMetadata DownloadMetadata { get; set; } + public DownloadMetadata? DownloadMetadata { get; set; } [JsonIgnore] public ModListSummary ValidationSummary { get; set; } = new ModListSummary(); @@ -42,22 +42,18 @@ namespace Wabbajack.Lib.ModListRegistry public class LinksObject { [JsonProperty("image")] - public string ImageUri { get; set; } + public string ImageUri { get; set; } = string.Empty; [JsonProperty("readme")] - public string Readme { get; set; } + public string Readme { get; set; } = string.Empty; [JsonProperty("download")] - public string Download { get; set; } - + public string Download { get; set; } = string.Empty; + [JsonProperty("machineURL")] - public string MachineURL { get; set; } + public string MachineURL { get; set; } = string.Empty; } - - - - public static async Task> LoadFromGithub() { var client = new Common.Http.Client(); @@ -103,18 +99,17 @@ namespace Wabbajack.Lib.ModListRegistry public long SizeOfArchives { get; set; } public long NumberOfInstalledFiles { get; set; } public long SizeOfInstalledFiles { get; set; } - } [JsonName("ModListSummary")] public class ModListSummary { [JsonProperty("name")] - public string Name { get; set; } - + public string Name { get; set; } = string.Empty; + [JsonProperty("machineURL")] - public string MachineURL { get; set; } - + public string MachineURL { get; set; } = string.Empty; + [JsonProperty("checked")] public DateTime Checked { get; set; } [JsonProperty("failed")] diff --git a/Wabbajack.Lib/NexusApi/Dtos.cs b/Wabbajack.Lib/NexusApi/Dtos.cs index ec19b0a0..02fd2f6d 100644 --- a/Wabbajack.Lib/NexusApi/Dtos.cs +++ b/Wabbajack.Lib/NexusApi/Dtos.cs @@ -4,68 +4,68 @@ namespace Wabbajack.Lib.NexusApi { public class UserStatus { - public string email; + public string email = string.Empty; public bool is_premium; public bool is_supporter; - public string key; - public string name; - public string profile_url; - public string user_id; + public string key = string.Empty; + public string name = string.Empty; + public string profile_url = string.Empty; + public string user_id = string.Empty; } public class NexusFileInfo { public long category_id { get; set; } - public string category_name { get; set; } - public string changelog_html { get; set; } - public string description { get; set; } - public string external_virus_scan_url { get; set; } + public string category_name { get; set; } = string.Empty; + public string changelog_html { get; set; } = string.Empty; + public string description { get; set; } = string.Empty; + public string external_virus_scan_url { get; set; } = string.Empty; public long file_id { get; set; } - public string file_name { get; set; } + public string file_name { get; set; } = string.Empty; public bool is_primary { get; set; } - public string mod_version { get; set; } - public string name { get; set; } + public string mod_version { get; set; } = string.Empty; + public string name { get; set; } = string.Empty; public long size { get; set; } public long size_kb { get; set; } public DateTime uploaded_time { get; set; } public long uploaded_timestamp { get; set; } - public string version { get; set; } + public string version { get; set; } = string.Empty; } public class ModInfo { - public string name { get; set; } - public string summary { get; set; } - public string description { get; set; } - public Uri picture_url { get; set; } - public string mod_id { get; set; } + public string name { get; set; } = string.Empty; + public string summary { get; set; } = string.Empty; + public string description { get; set; } = string.Empty; + public Uri picture_url { get; set; } = new Uri(string.Empty); + public string mod_id { get; set; } = string.Empty; public long game_id { get; set; } public bool allow_rating { get; set; } - public string domain_name { get; set; } + public string domain_name { get; set; } = string.Empty; public long category_id { get; set; } - public string version { get; set; } + public string version { get; set; } = string.Empty; public long endorsement_count { get; set; } public long created_timestamp { get; set; } - public DateTime created_time { get; set; } + public DateTime created_time { get; set; } public long updated_timestamp { get; set; } public DateTime updated_time { get; set; } - public string author { get; set; } - public string uploaded_by { get; set; } - public Uri uploaded_users_profile_url { get; set; } + public string author { get; set; } = string.Empty; + public string uploaded_by { get; set; } = string.Empty; + public Uri uploaded_users_profile_url { get; set; } = new Uri(string.Empty); public bool contains_adult_content { get; set; } - public string status { get; set; } + public string status { get; set; } = string.Empty; public bool available { get; set; } = true; } public class MD5Response { - public ModInfo mod; - public NexusFileInfo file_details; + public ModInfo? mod; + public NexusFileInfo? file_details; } public class EndorsementResponse { - public string message; - public string status; + public string message = string.Empty; + public string status = string.Empty; } } diff --git a/Wabbajack.Lib/NexusApi/NexusApi.cs b/Wabbajack.Lib/NexusApi/NexusApi.cs index 5e020b27..3f33d093 100644 --- a/Wabbajack.Lib/NexusApi/NexusApi.cs +++ b/Wabbajack.Lib/NexusApi/NexusApi.cs @@ -27,12 +27,11 @@ namespace Wabbajack.Lib.NexusApi #region Authentication - public string ApiKey { get; } + public string? ApiKey { get; } public bool IsAuthenticated => ApiKey != null; - private Task _userStatus; - + private Task? _userStatus; public Task UserStatus { get @@ -214,7 +213,7 @@ namespace Wabbajack.Lib.NexusApi #endregion - private NexusApiClient(string apiKey = null) + private NexusApiClient(string? apiKey = null) { ApiKey = apiKey; @@ -230,9 +229,9 @@ namespace Wabbajack.Lib.NexusApi Directory.CreateDirectory(Consts.NexusCacheDirectory); } - public static async Task Get(string apiKey = null) + public static async Task Get(string? apiKey = null) { - apiKey = apiKey ?? await GetApiKey(); + apiKey ??= await GetApiKey(); return new NexusApiClient(apiKey); } @@ -316,9 +315,10 @@ namespace Wabbajack.Lib.NexusApi throw; } } + public class GetModFilesResponse { - public List files { get; set; } + public List files { get; set; } = new List(); } public async Task GetModFiles(Game game, long modid, bool useCache = true) @@ -350,18 +350,18 @@ namespace Wabbajack.Lib.NexusApi private class DownloadLink { - public string URI { get; set; } + public string URI { get; set; } = string.Empty; } - public static MethodInfo CacheMethod { get; set; } - - private static string _localCacheDir; + private static string? _localCacheDir; public static string LocalCacheDir { get { if (_localCacheDir == null) _localCacheDir = Environment.GetEnvironmentVariable("NEXUSCACHEDIR"); + if (_localCacheDir == null) + throw new ArgumentNullException($"Enviornment variable could not be located: NEXUSCACHEDIR"); return _localCacheDir; } set => _localCacheDir = value; diff --git a/Wabbajack.Lib/NexusApi/NexusApiUtils.cs b/Wabbajack.Lib/NexusApi/NexusApiUtils.cs index f29959e4..c75ecf78 100644 --- a/Wabbajack.Lib/NexusApi/NexusApiUtils.cs +++ b/Wabbajack.Lib/NexusApi/NexusApiUtils.cs @@ -19,15 +19,10 @@ namespace Wabbajack.Lib.NexusApi public static string FixupSummary(string argSummary) { - if (argSummary != null) - { - return argSummary.Replace("'", "'") - .Replace("
", "\n\n") - .Replace("
", "\n\n") - .Replace("!", "!"); - } - - return argSummary; + return argSummary.Replace("'", "'") + .Replace("
", "\n\n") + .Replace("
", "\n\n") + .Replace("!", "!"); } } } diff --git a/Wabbajack.Lib/NexusApi/NexusUpdatesFeeds.cs b/Wabbajack.Lib/NexusApi/NexusUpdatesFeeds.cs index 164d2250..f2f4c98c 100644 --- a/Wabbajack.Lib/NexusApi/NexusUpdatesFeeds.cs +++ b/Wabbajack.Lib/NexusApi/NexusUpdatesFeeds.cs @@ -26,8 +26,7 @@ namespace Wabbajack.Lib.NexusApi { var parts = link.Uri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries); - var foundGame = GameRegistry.GetByFuzzyName(parts[0]); - if (foundGame == null) + if (!GameRegistry.TryGetByFuzzyName(parts[0], out var foundGame)) { game = Game.Oblivion; modId = 0; @@ -65,7 +64,8 @@ namespace Wabbajack.Lib.NexusApi } return null; - }).Where(v => v != null); + }) + .NotNull(); } diff --git a/Wabbajack.Lib/NexusApi/RequestNexusAuthorization.cs b/Wabbajack.Lib/NexusApi/RequestNexusAuthorization.cs index bf17e3a3..a84c707e 100644 --- a/Wabbajack.Lib/NexusApi/RequestNexusAuthorization.cs +++ b/Wabbajack.Lib/NexusApi/RequestNexusAuthorization.cs @@ -10,7 +10,7 @@ namespace Wabbajack.Lib.NexusApi public class RequestNexusAuthorization : AUserIntervention { public override string ShortDescription => "Getting User's Nexus API Key"; - public override string ExtendedDescription { get; } + public override string ExtendedDescription { get; } = string.Empty; private readonly TaskCompletionSource _source = new TaskCompletionSource(); public Task Task => _source.Task; diff --git a/Wabbajack.Lib/StatusMessages/ConfirmUpdateOfExistingInstall.cs b/Wabbajack.Lib/StatusMessages/ConfirmUpdateOfExistingInstall.cs index d3e7db4f..6e65243d 100644 --- a/Wabbajack.Lib/StatusMessages/ConfirmUpdateOfExistingInstall.cs +++ b/Wabbajack.Lib/StatusMessages/ConfirmUpdateOfExistingInstall.cs @@ -10,7 +10,7 @@ namespace Wabbajack.Lib public class ConfirmUpdateOfExistingInstall : ConfirmationIntervention { public AbsolutePath OutputFolder { get; set; } - public string ModListName { get; set; } + public string ModListName { get; set; } = string.Empty; public override string ShortDescription { get; } = "Do you want to overwrite existing files?"; diff --git a/Wabbajack.Lib/StatusMessages/ManuallyDownloadFile.cs b/Wabbajack.Lib/StatusMessages/ManuallyDownloadFile.cs index a782e089..ee02101d 100644 --- a/Wabbajack.Lib/StatusMessages/ManuallyDownloadFile.cs +++ b/Wabbajack.Lib/StatusMessages/ManuallyDownloadFile.cs @@ -9,9 +9,9 @@ namespace Wabbajack.Lib public class ManuallyDownloadFile : AUserIntervention { public ManualDownloader.State State { get; } - public override string ShortDescription { get; } - public override string ExtendedDescription { get; } - + public override string ShortDescription { get; } = string.Empty; + public override string ExtendedDescription { get; } = string.Empty; + private TaskCompletionSource<(Uri, Common.Http.Client)> _tcs = new TaskCompletionSource<(Uri, Common.Http.Client)>(); public Task<(Uri, Common.Http.Client)> Task => _tcs.Task; diff --git a/Wabbajack.Lib/StatusMessages/ManuallyDownloadNexusFile.cs b/Wabbajack.Lib/StatusMessages/ManuallyDownloadNexusFile.cs index e403ab73..8637f1cb 100644 --- a/Wabbajack.Lib/StatusMessages/ManuallyDownloadNexusFile.cs +++ b/Wabbajack.Lib/StatusMessages/ManuallyDownloadNexusFile.cs @@ -9,9 +9,9 @@ namespace Wabbajack.Lib public class ManuallyDownloadNexusFile : AUserIntervention { public NexusDownloader.State State { get; } - public override string ShortDescription { get; } - public override string ExtendedDescription { get; } - + public override string ShortDescription { get; } = string.Empty; + public override string ExtendedDescription { get; } = string.Empty; + private TaskCompletionSource _tcs = new TaskCompletionSource(); public Task Task => _tcs.Task; diff --git a/Wabbajack.Lib/SystemParameters.cs b/Wabbajack.Lib/SystemParameters.cs index 1964b8c1..2f288894 100644 --- a/Wabbajack.Lib/SystemParameters.cs +++ b/Wabbajack.Lib/SystemParameters.cs @@ -14,9 +14,9 @@ namespace Wabbajack.Lib public int ScreenWidth { get; set; } public long VideoMemorySize { get; set; } public long SystemMemorySize { get; set; } - - public Version WindowsVersion { get; set; } - + + public Version WindowsVersion { get; set; } = Environment.OSVersion.Version; + /// /// Value used in LE ENBs for VideoMemorySizeMb /// diff --git a/Wabbajack.Lib/Validation/DTOs.cs b/Wabbajack.Lib/Validation/DTOs.cs index 25a0741e..00be5104 100644 --- a/Wabbajack.Lib/Validation/DTOs.cs +++ b/Wabbajack.Lib/Validation/DTOs.cs @@ -10,33 +10,32 @@ namespace Wabbajack.Lib.Validation public bool? CanUseInOtherGames { get; set; } } - public class Author - { - public Permissions Permissions { get; set; } - public Dictionary Games; - } + //public class Author + //{ + // public Permissions Permissions { get; set; } + // public Dictionary Games; + //} - public class Game - { - public Permissions Permissions; - public Dictionary Mods; - } + //public class Game + //{ + // public Permissions Permissions; + // public Dictionary Mods; + //} - public class Mod - { - public Permissions Permissions; - public Dictionary Files; - } - - public class File - { - public Permissions Permissions; - } + //public class Mod + //{ + // public Permissions Permissions; + // public Dictionary Files; + //} + //public class File + //{ + // public Permissions Permissions; + //} public class ServerWhitelist { - public List GoogleIDs; - public List AllowedPrefixes; + public List GoogleIDs = new List(); + public List AllowedPrefixes = new List(); } } diff --git a/Wabbajack.Lib/ViewModel.cs b/Wabbajack.Lib/ViewModel.cs index 6b2b06fc..719a7ccc 100644 --- a/Wabbajack.Lib/ViewModel.cs +++ b/Wabbajack.Lib/ViewModel.cs @@ -24,7 +24,7 @@ namespace Wabbajack.Lib protected void RaiseAndSetIfChanged( ref T item, T newItem, - [CallerMemberName] string propertyName = null) + [CallerMemberName] string? propertyName = null) { if (EqualityComparer.Default.Equals(item, newItem)) return; item = newItem; diff --git a/Wabbajack.Lib/VortexInstaller.cs b/Wabbajack.Lib/VortexInstaller.cs index 72ee01c4..7876097d 100644 --- a/Wabbajack.Lib/VortexInstaller.cs +++ b/Wabbajack.Lib/VortexInstaller.cs @@ -11,6 +11,7 @@ using Directory = Alphaleonis.Win32.Filesystem.Directory; using DirectoryInfo = Alphaleonis.Win32.Filesystem.DirectoryInfo; using File = Alphaleonis.Win32.Filesystem.File; using Path = Alphaleonis.Win32.Filesystem.Path; +#nullable disable namespace Wabbajack.Lib { @@ -26,7 +27,8 @@ namespace Wabbajack.Lib modList: modList, outputFolder: outputFolder, downloadFolder: downloadFolder, - parameters: parameters) + parameters: parameters, + steps: 10) { #if DEBUG // TODO: only for testing @@ -51,7 +53,7 @@ namespace Wabbajack.Lib } if (cancel.IsCancellationRequested) return false; - ConfigureProcessor(10, ConstructDynamicNumThreads(await RecommendQueueSize())); + Queue.SetActiveThreadsObservable(ConstructDynamicNumThreads(await RecommendQueueSize())); DownloadFolder.CreateDirectory(); if (cancel.IsCancellationRequested) return false; @@ -122,7 +124,7 @@ namespace Wabbajack.Lib var manualFilesDir = OutputFolder.Combine(Consts.ManualGameFilesDir); - var gameFolder = GameInfo.GameLocation(); + var gameFolder = GameInfo.TryGetGameLocation(); Info($"Copying files from {manualFilesDir} " + $"to the game folder at {gameFolder}"); diff --git a/Wabbajack.Lib/Wabbajack.Lib.csproj b/Wabbajack.Lib/Wabbajack.Lib.csproj index 7e8793e3..cf84fea6 100644 --- a/Wabbajack.Lib/Wabbajack.Lib.csproj +++ b/Wabbajack.Lib/Wabbajack.Lib.csproj @@ -1,9 +1,10 @@  - netcoreapp3.1 - x64 - win10-x64 + netcoreapp3.1 + x64 + win10-x64 + enable diff --git a/Wabbajack.Lib/WebAutomation/CefSharpWrapper.cs b/Wabbajack.Lib/WebAutomation/CefSharpWrapper.cs index 513d3008..48e73d50 100644 --- a/Wabbajack.Lib/WebAutomation/CefSharpWrapper.cs +++ b/Wabbajack.Lib/WebAutomation/CefSharpWrapper.cs @@ -13,8 +13,8 @@ namespace Wabbajack.Lib.WebAutomation { public class CefSharpWrapper : IWebDriver { - private IWebBrowser _browser; - public Action DownloadHandler { get; set; } + private readonly IWebBrowser _browser; + public Action? DownloadHandler { get; set; } public CefSharpWrapper(IWebBrowser browser) { _browser = browser; @@ -24,7 +24,7 @@ namespace Wabbajack.Lib.WebAutomation { var tcs = new TaskCompletionSource(); - EventHandler handler = null; + EventHandler? handler = null; handler = (sender, e) => { if (!e.IsLoading) @@ -68,7 +68,7 @@ namespace Wabbajack.Lib.WebAutomation public class PopupBlocker : ILifeSpanHandler { - private CefSharpWrapper _wrapper; + private readonly CefSharpWrapper _wrapper; public PopupBlocker(CefSharpWrapper cefSharpWrapper) { @@ -77,7 +77,7 @@ namespace Wabbajack.Lib.WebAutomation public bool OnBeforePopup(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, - IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser) + IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser? newBrowser) { // Block popups newBrowser = null; @@ -86,7 +86,6 @@ namespace Wabbajack.Lib.WebAutomation public void OnAfterCreated(IWebBrowser chromiumWebBrowser, IBrowser browser) { - } public bool DoClose(IWebBrowser chromiumWebBrowser, IBrowser browser) @@ -96,7 +95,6 @@ namespace Wabbajack.Lib.WebAutomation public void OnBeforeClose(IWebBrowser chromiumWebBrowser, IBrowser browser) { - } } @@ -112,7 +110,7 @@ namespace Wabbajack.Lib.WebAutomation public void OnBeforeDownload(IWebBrowser chromiumWebBrowser, IBrowser browser, DownloadItem downloadItem, IBeforeDownloadCallback callback) { - _wrapper.DownloadHandler(new Uri(downloadItem.Url)); + _wrapper.DownloadHandler?.Invoke(new Uri(downloadItem.Url)); } public void OnDownloadUpdated(IWebBrowser chromiumWebBrowser, IBrowser browser, DownloadItem downloadItem, diff --git a/Wabbajack.Lib/WebAutomation/IWebDriver.cs b/Wabbajack.Lib/WebAutomation/IWebDriver.cs index 0672aef5..529a04cb 100644 --- a/Wabbajack.Lib/WebAutomation/IWebDriver.cs +++ b/Wabbajack.Lib/WebAutomation/IWebDriver.cs @@ -13,6 +13,6 @@ namespace Wabbajack.Lib.WebAutomation Task NavigateTo(Uri uri); Task EvaluateJavaScript(string text); Task GetCookies(string domainPrefix); - public Action DownloadHandler { get; set; } + public Action? DownloadHandler { get; set; } } } diff --git a/Wabbajack.Lib/WebAutomation/WebAutomation.cs b/Wabbajack.Lib/WebAutomation/WebAutomation.cs index 09eba17a..d906c968 100644 --- a/Wabbajack.Lib/WebAutomation/WebAutomation.cs +++ b/Wabbajack.Lib/WebAutomation/WebAutomation.cs @@ -24,13 +24,13 @@ namespace Wabbajack.Lib.WebAutomation return driver; } - public async Task NavigateTo(Uri uri) + public async Task NavigateTo(Uri uri) { await _driver.NavigateTo(uri); return await GetLocation(); } - public async ValueTask GetLocation() + public async ValueTask GetLocation() { try { diff --git a/Wabbajack.Lib/zEditIntegration.cs b/Wabbajack.Lib/zEditIntegration.cs index a4e67b0e..ba2d1845 100644 --- a/Wabbajack.Lib/zEditIntegration.cs +++ b/Wabbajack.Lib/zEditIntegration.cs @@ -14,34 +14,17 @@ namespace Wabbajack.Lib { public class zEditIntegration { - private static MO2Compiler _mo2Compiler; - - public static AbsolutePath FindzEditPath(ACompiler compiler) - { - _mo2Compiler = (MO2Compiler) compiler; - var executables = _mo2Compiler.MO2Ini.customExecutables; - if (executables.size == null) return default; - - foreach (var idx in Enumerable.Range(1, int.Parse(executables.size))) - { - var path = (string)executables[$"{idx}\\binary"]; - if (path == null) continue; - - if (path.EndsWith("zEdit.exe")) - return (AbsolutePath)path; - } - - return default; - } - public class IncludeZEditPatches : ACompilationStep { - private readonly Dictionary _mergesIndexed; + private readonly Dictionary _mergesIndexed = new Dictionary(); private bool _disabled = true; - public IncludeZEditPatches(ACompiler compiler) : base(compiler) + private MO2Compiler _mo2Compiler; + + public IncludeZEditPatches(MO2Compiler compiler) : base(compiler) { + _mo2Compiler = compiler; var zEditPath = FindzEditPath(compiler); var havezEdit = zEditPath != default; @@ -126,7 +109,24 @@ namespace Wabbajack.Lib _disabled = false; } - public override async ValueTask Run(RawSourceFile source) + public static AbsolutePath FindzEditPath(MO2Compiler compiler) + { + var executables = compiler.MO2Ini.customExecutables; + if (executables.size == null) return default; + + foreach (var idx in Enumerable.Range(1, int.Parse(executables.size))) + { + var path = (string)executables[$"{idx}\\binary"]; + if (path == null) continue; + + if (path.EndsWith("zEdit.exe")) + return (AbsolutePath)path; + } + + return default; + } + + public override async ValueTask Run(RawSourceFile source) { if (_disabled) return null; if (!_mergesIndexed.TryGetValue(source.AbsolutePath, out var merge)) @@ -150,7 +150,7 @@ namespace Wabbajack.Lib return inline; } var result = source.EvolveTo(); - result.Sources = merge.plugins.Select(f => + result.Sources.SetTo(merge.plugins.Select(f => { var origPath = (AbsolutePath)Path.Combine(f.dataFolder, f.filename); var paths = new[] @@ -171,10 +171,11 @@ namespace Wabbajack.Lib try { hash = _compiler.VFS.Index.ByRootPath[absPath].Hash; - } catch (KeyNotFoundException e) + } + catch (KeyNotFoundException e) { - Utils.ErrorThrow(e, $"Could not find the key {absPath} in the VFS Index dictionary!"); - return null; + Utils.Error(e, $"Could not find the key {absPath} in the VFS Index dictionary!"); + throw; } return new SourcePatch @@ -182,7 +183,7 @@ namespace Wabbajack.Lib RelativePath = absPath.RelativeTo(_mo2Compiler.MO2Folder), Hash = hash }; - }).ToList(); + })); var srcData = result.Sources.Select(f => _mo2Compiler.MO2Folder.Combine(f.RelativePath).ReadAllBytes()) .ConcatArrays(); @@ -209,14 +210,14 @@ namespace Wabbajack.Lib { public ICompilationStep CreateStep(ACompiler compiler) { - return new IncludeZEditPatches(compiler); + return new IncludeZEditPatches((MO2Compiler)compiler); } } } public class zEditSettings { - public string modManager; + public string modManager = string.Empty; public AbsolutePath managerPath; public AbsolutePath modsPath; public AbsolutePath mergePath; @@ -224,16 +225,16 @@ namespace Wabbajack.Lib public class zEditMerge { - public string name; - public string filename; - public List plugins; + public string name = string.Empty; + public string filename = string.Empty; + public List plugins = new List(); } public class zEditMergePlugin { - public string filename; - public string dataFolder; + public string? filename; + public string? dataFolder; } public static void VerifyMerges(MO2Compiler compiler) diff --git a/Wabbajack.Test/ACompilerTest.cs b/Wabbajack.Test/ACompilerTest.cs index c980b234..a48d50a4 100644 --- a/Wabbajack.Test/ACompilerTest.cs +++ b/Wabbajack.Test/ACompilerTest.cs @@ -28,7 +28,7 @@ namespace Wabbajack.Test public override void Dispose() { - utils.Dispose(); + utils.DisposeAsync().AsTask().Wait(); _unsub.Dispose(); base.Dispose(); } diff --git a/Wabbajack.Test/ContentRightsManagementTests.cs b/Wabbajack.Test/ContentRightsManagementTests.cs index 4c9b1eb6..7da956cc 100644 --- a/Wabbajack.Test/ContentRightsManagementTests.cs +++ b/Wabbajack.Test/ContentRightsManagementTests.cs @@ -48,15 +48,15 @@ namespace Wabbajack.Test GameType = Game.Skyrim, Archives = new List { - new Archive - { - State = new NexusDownloader.State + new Archive( + new NexusDownloader.State { Game = Game.Skyrim, Author = "bill", ModID = 42, FileID = 33, - }, + }) + { Hash = Hash.FromLong(42) } }, @@ -71,9 +71,8 @@ namespace Wabbajack.Test }; // Error due to file downloaded from 3rd party modlist.GameType = Game.Skyrim; - modlist.Archives[0] = new Archive() + modlist.Archives[0] = new Archive(new HTTPDownloader.State("https://somebadplace.com")) { - State = new HTTPDownloader.State() { Url = "https://somebadplace.com" }, Hash = Hash.FromLong(42) }; var errors = await validate.Validate(modlist); @@ -81,9 +80,8 @@ namespace Wabbajack.Test // Ok due to file downloaded from whitelisted 3rd party modlist.GameType = Game.Skyrim; - modlist.Archives[0] = new Archive + modlist.Archives[0] = new Archive(new HTTPDownloader.State("https://somegoodplace.com/baz.7z")) { - State = new HTTPDownloader.State { Url = "https://somegoodplace.com/baz.7z" }, Hash = Hash.FromLong(42) }; errors = await validate.Validate(modlist); @@ -92,9 +90,8 @@ namespace Wabbajack.Test // Error due to file downloaded from bad 3rd party modlist.GameType = Game.Skyrim; - modlist.Archives[0] = new Archive + modlist.Archives[0] = new Archive(new GoogleDriveDownloader.State("bleg")) { - State = new GoogleDriveDownloader.State { Id = "bleg"}, Hash = Hash.FromLong(42) }; errors = await validate.Validate(modlist); @@ -102,9 +99,8 @@ namespace Wabbajack.Test // Ok due to file downloaded from good google site modlist.GameType = Game.Skyrim; - modlist.Archives[0] = new Archive + modlist.Archives[0] = new Archive(new GoogleDriveDownloader.State("googleDEADBEEF")) { - State = new GoogleDriveDownloader.State { Id = "googleDEADBEEF" }, Hash = Hash.FromLong(42) }; errors = await validate.Validate(modlist); diff --git a/Wabbajack.Test/DownloaderTests.cs b/Wabbajack.Test/DownloaderTests.cs index ed1614d8..2375f922 100644 --- a/Wabbajack.Test/DownloaderTests.cs +++ b/Wabbajack.Test/DownloaderTests.cs @@ -67,13 +67,13 @@ namespace Wabbajack.Test var converted = RoundTripState(state); - Assert.True(await converted.Verify(new Archive{Size = 20})); + Assert.True(await converted.Verify(new Archive(state: null!){Size = 20})); using var filename = new TempFile(); Assert.True(converted.IsWhitelisted(new ServerWhitelist {AllowedPrefixes = new List{"https://mega.nz/#!CsMSFaaJ!-uziC4mbJPRy2e4pPk8Gjb3oDT_38Be9fzZ6Ld4NL-k" } })); Assert.False(converted.IsWhitelisted(new ServerWhitelist { AllowedPrefixes = new List{ "blerg" }})); - await converted.Download(new Archive {Name = "MEGA Test.txt"}, filename.Path); + await converted.Download(new Archive(state: null!) {Name = "MEGA Test.txt"}, filename.Path); Assert.Equal(Hash.FromBase64("eSIyd+KOG3s="), await filename.Path.FileHashAsync()); @@ -97,13 +97,13 @@ namespace Wabbajack.Test ((HTTPDownloader.State)url_state).Url); var converted = RoundTripState(state); - Assert.True(await converted.Verify(new Archive{Size = 20})); + Assert.True(await converted.Verify(new Archive(state: null!){Size = 20})); using var filename = new TempFile(); Assert.True(converted.IsWhitelisted(new ServerWhitelist { AllowedPrefixes = new List { "https://www.dropbox.com/s/5hov3m2pboppoc2/WABBAJACK_TEST_FILE.txt?" } })); Assert.False(converted.IsWhitelisted(new ServerWhitelist { AllowedPrefixes = new List { "blerg" } })); - await converted.Download(new Archive { Name = "MEGA Test.txt" }, filename.Path); + await converted.Download(new Archive(state: null!) { Name = "MEGA Test.txt" }, filename.Path); Assert.Equal(Hash.FromBase64("eSIyd+KOG3s="), await filename.Path.FileHashAsync()); @@ -127,13 +127,13 @@ namespace Wabbajack.Test ((GoogleDriveDownloader.State)url_state).Id); var converted = RoundTripState(state); - Assert.True(await converted.Verify(new Archive{Size = 20})); + Assert.True(await converted.Verify(new Archive(state: null!) { Size = 20})); using var filename = new TempFile(); Assert.True(converted.IsWhitelisted(new ServerWhitelist { GoogleIDs = new List { "1grLRTrpHxlg7VPxATTFNfq2OkU_Plvh_" } })); Assert.False(converted.IsWhitelisted(new ServerWhitelist { GoogleIDs = new List()})); - await converted.Download(new Archive { Name = "MEGA Test.txt" }, filename.Path); + await converted.Download(new Archive(state: null!) { Name = "MEGA Test.txt" }, filename.Path); Assert.Equal(Hash.FromBase64("eSIyd+KOG3s="), await filename.Path.FileHashAsync()); @@ -156,13 +156,13 @@ namespace Wabbajack.Test ((HTTPDownloader.State)url_state).Url); var converted = RoundTripState(state); - Assert.True(await converted.Verify(new Archive{Size = 20})); + Assert.True(await converted.Verify(new Archive(state: null!) { Size = 20})); using var filename = new TempFile(); Assert.True(converted.IsWhitelisted(new ServerWhitelist { AllowedPrefixes = new List { "http://build.wabbajack.org/" } })); Assert.False(converted.IsWhitelisted(new ServerWhitelist { AllowedPrefixes = new List() })); - await converted.Download(new Archive { Name = "MEGA Test.txt" }, filename.Path); + await converted.Download(new Archive(state: null!) { Name = "MEGA Test.txt" }, filename.Path); Assert.Equal(Hash.FromBase64("eSIyd+KOG3s="), await filename.Path.FileHashAsync()); @@ -180,7 +180,7 @@ namespace Wabbajack.Test Assert.NotNull(state); var converted = RoundTripState(state); - Assert.True(await converted.Verify(new Archive{Size = 20})); + Assert.True(await converted.Verify(new Archive(state: null!) { Size = 20})); using var filename = new TempFile(); Assert.True(converted.IsWhitelisted(new ServerWhitelist { AllowedPrefixes = new List { "http://build.wabbajack.org/" } })); @@ -226,37 +226,27 @@ namespace Wabbajack.Test [Fact] public async Task NexusDownload() { - var old_val = NexusApiClient.CacheMethod; - try - { - NexusApiClient.CacheMethod = null; - var ini = @"[General] + var ini = @"[General] gameName=SkyrimSE modID = 12604 fileID=35407"; - var state = (AbstractDownloadState)await DownloadDispatcher.ResolveArchive(ini.LoadIniString()); + var state = (AbstractDownloadState)await DownloadDispatcher.ResolveArchive(ini.LoadIniString()); - Assert.NotNull(state); + Assert.NotNull(state); - var converted = RoundTripState(state); - Assert.True(await converted.Verify(new Archive{Size = 20})); - // Exercise the cache code - Assert.True(await converted.Verify(new Archive{Size = 20})); - using var filename = new TempFile(); + var converted = RoundTripState(state); + Assert.True(await converted.Verify(new Archive(state: null!) { Size = 20 })); + // Exercise the cache code + Assert.True(await converted.Verify(new Archive(state: null!) { Size = 20 })); + using var filename = new TempFile(); - Assert.True(converted.IsWhitelisted(new ServerWhitelist { AllowedPrefixes = new List () })); + Assert.True(converted.IsWhitelisted(new ServerWhitelist { AllowedPrefixes = new List() })); - await converted.Download(new Archive { Name = "SkyUI.7z" }, filename.Path); + await converted.Download(new Archive(state: null!) { Name = "SkyUI.7z" }, filename.Path); - Assert.Equal(Hash.FromBase64("dF2yafV2Oks="),await filename.Path.FileHashAsync()); - - } - finally - { - NexusApiClient.CacheMethod = old_val; - } + Assert.Equal(Hash.FromBase64("dF2yafV2Oks="), await filename.Path.FileHashAsync()); } [Fact] @@ -276,12 +266,12 @@ namespace Wabbajack.Test ((ModDBDownloader.State)url_state).Url); var converted = RoundTripState(state); - Assert.True(await converted.Verify(new Archive{Size = 20})); + Assert.True(await converted.Verify(new Archive(state: null!) { Size = 20})); using var filename = new TempFile(); Assert.True(converted.IsWhitelisted(new ServerWhitelist { AllowedPrefixes = new List() })); - await converted.Download(new Archive { Name = "moddbtest.7z" }, filename.Path); + await converted.Download(new Archive(state: null!) { Name = "moddbtest.7z" }, filename.Path); Assert.Equal(Hash.FromBase64("2lZt+1h6wxM="), await filename.Path.FileHashAsync()); } @@ -302,12 +292,12 @@ namespace Wabbajack.Test ((HTTPDownloader.State)url_state).Url); */ var converted = RoundTripState(state); - Assert.True(await converted.Verify(new Archive{Size = 20})); + Assert.True(await converted.Verify(new Archive(state: null!) { Size = 20})); using var filename = new TempFile(); Assert.True(converted.IsWhitelisted(new ServerWhitelist { AllowedPrefixes = new List() })); - await converted.Download(new Archive { Name = "LoversLab Test.txt" }, filename.Path); + await converted.Download(new Archive(state: null!) { Name = "LoversLab Test.txt" }, filename.Path); Assert.Equal(Hash.FromBase64("eSIyd+KOG3s="), await filename.Path.FileHashAsync()); @@ -330,12 +320,12 @@ namespace Wabbajack.Test ((HTTPDownloader.State)url_state).Url); */ var converted = RoundTripState(state); - Assert.True(await converted.Verify(new Archive{Size = 20})); + Assert.True(await converted.Verify(new Archive(state: null!) { Size = 20})); using var filename = new TempFile(); Assert.True(converted.IsWhitelisted(new ServerWhitelist { AllowedPrefixes = new List() })); - await converted.Download(new Archive { Name = "Vector Plexus Test.zip" }, filename.Path); + await converted.Download(new Archive(state: null!) { Name = "Vector Plexus Test.zip" }, filename.Path); Assert.Equal(Hash.FromBase64("eSIyd+KOG3s="), await filename.Path.FileHashAsync()); @@ -355,12 +345,12 @@ namespace Wabbajack.Test Assert.NotNull(state); var converted = RoundTripState(state); - Assert.True(await converted.Verify(new Archive{Size = 20})); + Assert.True(await converted.Verify(new Archive(state: null!) { Size = 20})); using var filename = new TempFile(); Assert.True(converted.IsWhitelisted(new ServerWhitelist { AllowedPrefixes = new List() })); - await converted.Download(new Archive { Name = "TESAlliance Test.zip" }, filename.Path); + await converted.Download(new Archive(state: null!) { Name = "TESAlliance Test.zip" }, filename.Path); Assert.Equal(Hash.FromBase64("eSIyd+KOG3s="), await filename.Path.FileHashAsync()); @@ -407,15 +397,15 @@ namespace Wabbajack.Test Assert.NotNull(state); var converted = RoundTripState(state); - Assert.True(await converted.Verify(new Archive{Size = 20})); + Assert.True(await converted.Verify(new Archive(state: null!) { Size = 20})); using var filename = new TempFile(); Assert.True(converted.IsWhitelisted(new ServerWhitelist { AllowedPrefixes = new List() })); - await converted.Download(new Archive { Name = "Update.esm" }, filename.Path); + await converted.Download(new Archive(state: null!) { Name = "Update.esm" }, filename.Path); Assert.Equal(Hash.FromBase64("/DLG/LjdGXI="), await Utils.FileHashAsync(filename.Path)); - Assert.Equal(await filename.Path.ReadAllBytesAsync(), await Game.SkyrimSpecialEdition.MetaData().GameLocation()?.Combine("Data/Update.esm").ReadAllBytesAsync()); + Assert.Equal(await filename.Path.ReadAllBytesAsync(), await Game.SkyrimSpecialEdition.MetaData().GameLocation().Combine("Data/Update.esm").ReadAllBytesAsync()); Consts.TestMode = true; } @@ -434,11 +424,11 @@ namespace Wabbajack.Test Assert.NotNull(state); var converted = state.ViaJSON(); - Assert.True(await converted.Verify(new Archive {Name = "mod.ckm"})); + Assert.True(await converted.Verify(new Archive(state: null!) { Name = "mod.ckm"})); Assert.True(converted.IsWhitelisted(new ServerWhitelist { AllowedPrefixes = new List() })); - await converted.Download(new Archive { Name = "mod.zip" }, filename.Path); + await converted.Download(new Archive(state: null!) { Name = "mod.zip" }, filename.Path); await using var fs = filename.Path.OpenRead(); using var archive = new ZipArchive(fs); @@ -463,12 +453,12 @@ namespace Wabbajack.Test var converted = RoundTripState(state); - Assert.True(await converted.Verify(new Archive {Name = "yt_test.zip"})); + Assert.True(await converted.Verify(new Archive(state: null!) { Name = "yt_test.zip"})); Assert.True(converted.IsWhitelisted(new ServerWhitelist { AllowedPrefixes = new List() })); using var tempFile = new TempFile(); - await converted.Download(new Archive {Name = "yt_test.zip"}, tempFile.Path); + await converted.Download(new Archive(state: null!) { Name = "yt_test.zip"}, tempFile.Path); Assert.Equal(Hash.FromBase64("kD36zbA2X9Q="), await tempFile.Path.FileHashAsync()); } @@ -497,12 +487,12 @@ namespace Wabbajack.Test var archivesa = new List() { - new Archive {Hash = statea.Hash, Name = "Download.esm", State = statea} + new Archive(statea) {Hash = statea.Hash, Name = "Download.esm" } }; var archivesb = new List() { - new Archive {Hash = stateb.Hash, Name = "Download.esm", State = stateb} + new Archive(stateb) {Hash = stateb.Hash, Name = "Download.esm" } }; var folder = ((RelativePath)"DownloadTests").RelativeToEntryPoint(); @@ -532,34 +522,32 @@ namespace Wabbajack.Test return state.ToJson().FromJsonString(); } - [Fact] public async Task TestUpgrading() { await using var folder = new TempFolder(); var dest = folder.Dir.Combine("Cori.7z"); - var archive = new Archive - { - Name = "Cori.7z", - Hash = Hash.FromBase64("gCRVrvzDNH0="), - State = new NexusDownloader.State + var archive = new Archive( + new NexusDownloader.State { Game = Game.SkyrimSpecialEdition, ModID = 24808, FileID = 123501 - } + }) + { + Name = "Cori.7z", + Hash = Hash.FromBase64("gCRVrvzDNH0="), }; Assert.True(await DownloadDispatcher.DownloadWithPossibleUpgrade(archive, dest)); Assert.Equal(Hash.FromBase64("gCRVrvzDNH0="), await dest.FileHashCachedAsync()); } - - class TestInstaller : AInstaller { - public TestInstaller(AbsolutePath archive, ModList modList, AbsolutePath outputFolder, AbsolutePath downloadFolder, SystemParameters parameters) : base(archive, modList, outputFolder, downloadFolder, parameters) + public TestInstaller(AbsolutePath archive, ModList modList, AbsolutePath outputFolder, AbsolutePath downloadFolder, SystemParameters parameters) + : base(archive, modList, outputFolder, downloadFolder, parameters, steps: 1) { - ConfigureProcessor(1, new Subject().StartWith(1)); + Queue.SetActiveThreadsObservable(Observable.Return(1)); } protected override Task _Begin(CancellationToken cancel) @@ -569,10 +557,5 @@ namespace Wabbajack.Test public override ModManager ModManager { get => ModManager.MO2; } } - - } - - - } diff --git a/Wabbajack.Test/EndToEndTests.cs b/Wabbajack.Test/EndToEndTests.cs index 70caa696..6f3ad560 100644 --- a/Wabbajack.Test/EndToEndTests.cs +++ b/Wabbajack.Test/EndToEndTests.cs @@ -39,6 +39,7 @@ namespace Wabbajack.Test { Queue.Dispose(); _unsub.Dispose(); + utils.DisposeAsync().AsTask().Wait(); base.Dispose(); } @@ -97,7 +98,7 @@ namespace Wabbajack.Test if (!src.Exists) { var state = DownloadDispatcher.ResolveArchive(url); - await state.Download(new Archive { Name = "Unknown"}, src); + await state.Download(new Archive(state: null!) { Name = "Unknown"}, src); } utils.DownloadsFolder.CreateDirectory(); diff --git a/Wabbajack.Test/ModlistMetadataTests.cs b/Wabbajack.Test/ModlistMetadataTests.cs index f05f8ef2..6facd767 100644 --- a/Wabbajack.Test/ModlistMetadataTests.cs +++ b/Wabbajack.Test/ModlistMetadataTests.cs @@ -26,7 +26,7 @@ namespace Wabbajack.Test { var logoState = DownloadDispatcher.ResolveArchive(modlist.ImageUri); Assert.NotNull(logoState); - Assert.True(await logoState.Verify(new Archive{Size = 0}), $"{modlist.ImageUri} is not valid"); + Assert.True(await logoState.Verify(new Archive(state: null){Size = 0}), $"{modlist.ImageUri} is not valid"); } } diff --git a/Wabbajack.Test/RestartingDownloadsTests.cs b/Wabbajack.Test/RestartingDownloadsTests.cs index c93e3a6c..d8602d76 100644 --- a/Wabbajack.Test/RestartingDownloadsTests.cs +++ b/Wabbajack.Test/RestartingDownloadsTests.cs @@ -72,8 +72,8 @@ namespace Wabbajack.Test { using var testFile = new TempFile(); using var server = new CrappyRandomServer(); - var state = new HTTPDownloader.State {Url = $"http://localhost:{server.Port}/foo"}; - + var state = new HTTPDownloader.State($"http://localhost:{server.Port}/foo"); + await state.Download(testFile.Path); Assert.Equal(server.Data, await testFile.Path.ReadAllBytesAsync()); @@ -87,8 +87,4 @@ namespace Wabbajack.Test _unsubIntevention?.Dispose(); } } - - - - } diff --git a/Wabbajack.Test/TestUtils.cs b/Wabbajack.Test/TestUtils.cs index 5efdb7a1..0081c560 100644 --- a/Wabbajack.Test/TestUtils.cs +++ b/Wabbajack.Test/TestUtils.cs @@ -14,7 +14,7 @@ using Path = Alphaleonis.Win32.Filesystem.Path; namespace Wabbajack.Test { - public class TestUtils : IDisposable + public class TestUtils : IAsyncDisposable { private static Random _rng = new Random(); public TestUtils() @@ -119,13 +119,14 @@ namespace Wabbajack.Test return arr; } - public void Dispose() + public async ValueTask DisposeAsync() { - var exts = new [] {".md", ".exe"}; - WorkingDirectory.Combine(ID).DeleteDirectory(); + var exts = new[] { ".md", ".exe" }; + await WorkingDirectory.Combine(ID).DeleteDirectory(); Profiles.Do(p => { - foreach (var ext in exts) { + foreach (var ext in exts) + { var path = Path.Combine(Directory.GetCurrentDirectory(), p + ext); if (File.Exists(path)) File.Delete(path); @@ -246,7 +247,5 @@ namespace Wabbajack.Test GenerateRandomFileData(fullPath, i); return fullPath; } - - } } diff --git a/Wabbajack.VirtualFileSystem.Test/VirtualFileSystemTests.cs b/Wabbajack.VirtualFileSystem.Test/VirtualFileSystemTests.cs index eca1b235..5059fbf8 100644 --- a/Wabbajack.VirtualFileSystem.Test/VirtualFileSystemTests.cs +++ b/Wabbajack.VirtualFileSystem.Test/VirtualFileSystemTests.cs @@ -9,7 +9,7 @@ using Xunit.Abstractions; namespace Wabbajack.VirtualFileSystem.Test { - public class VFSTests + public class VFSTests : IAsyncLifetime { private static readonly AbsolutePath VFS_TEST_DIR = "vfs_test_dir".ToPath().RelativeToEntryPoint(); private static readonly AbsolutePath TEST_ZIP = "test.zip".RelativeTo(VFS_TEST_DIR); @@ -18,18 +18,26 @@ namespace Wabbajack.VirtualFileSystem.Test private Context context; private readonly ITestOutputHelper _helper; - private WorkQueue Queue { get; } + private WorkQueue Queue { get; } = new WorkQueue(); public VFSTests(ITestOutputHelper helper) { _helper = helper; Utils.LogMessages.Subscribe(f => _helper.WriteLine(f.ShortDescription)); - VFS_TEST_DIR.DeleteDirectory(); - VFS_TEST_DIR.CreateDirectory(); - Queue = new WorkQueue(); context = new Context(Queue); } + public async Task InitializeAsync() + { + await VFS_TEST_DIR.DeleteDirectory(); + VFS_TEST_DIR.CreateDirectory(); + } + + public async Task DisposeAsync() + { + await VFS_TEST_DIR.DeleteDirectory(); + } + [Fact] public async Task FilesAreIndexed() { @@ -51,12 +59,11 @@ namespace Wabbajack.VirtualFileSystem.Test await context.IntegrateFromFile( "vfs_cache.bin".RelativeTo(VFS_TEST_DIR)); } - [Fact] public async Task ArchiveContentsAreIndexed() { await AddFile(ARCHIVE_TEST_TXT, "This is a test"); - ZipUpFolder(ARCHIVE_TEST_TXT.Parent, TEST_ZIP); + await ZipUpFolder(ARCHIVE_TEST_TXT.Parent, TEST_ZIP); await AddTestRoot(); var absPath = "test.zip".RelativeTo(VFS_TEST_DIR); @@ -78,7 +85,7 @@ namespace Wabbajack.VirtualFileSystem.Test public async Task DuplicateFileHashes() { await AddFile(ARCHIVE_TEST_TXT, "This is a test"); - ZipUpFolder(ARCHIVE_TEST_TXT.Parent, TEST_ZIP); + await ZipUpFolder(ARCHIVE_TEST_TXT.Parent, TEST_ZIP); await AddFile(TEST_TXT, "This is a test"); await AddTestRoot(); @@ -127,7 +134,7 @@ namespace Wabbajack.VirtualFileSystem.Test public async Task CanStageSimpleArchives() { await AddFile(ARCHIVE_TEST_TXT, "This is a test"); - ZipUpFolder(ARCHIVE_TEST_TXT.Parent, TEST_ZIP); + await ZipUpFolder(ARCHIVE_TEST_TXT.Parent, TEST_ZIP); await AddTestRoot(); var res = new FullPath(TEST_ZIP, new[] {(RelativePath)"test.txt"}); @@ -136,19 +143,19 @@ namespace Wabbajack.VirtualFileSystem.Test var cleanup = await context.Stage(new List {file}); Assert.Equal("This is a test", await file.StagedPath.ReadAllTextAsync()); - cleanup(); + await cleanup(); } [Fact] public async Task CanStageNestedArchives() { await AddFile(ARCHIVE_TEST_TXT, "This is a test"); - ZipUpFolder(ARCHIVE_TEST_TXT.Parent, TEST_ZIP); + await ZipUpFolder(ARCHIVE_TEST_TXT.Parent, TEST_ZIP); var inner_dir = @"archive\other\dir".RelativeTo(VFS_TEST_DIR); inner_dir.CreateDirectory(); TEST_ZIP.MoveTo( @"archive\other\dir\nested.zip".RelativeTo(VFS_TEST_DIR)); - ZipUpFolder(ARCHIVE_TEST_TXT.Parent, TEST_ZIP); + await ZipUpFolder(ARCHIVE_TEST_TXT.Parent, TEST_ZIP); await AddTestRoot(); @@ -159,7 +166,7 @@ namespace Wabbajack.VirtualFileSystem.Test foreach (var file in files) Assert.Equal("This is a test", await file.StagedPath.ReadAllTextAsync()); - cleanup(); + await cleanup(); } private static async Task AddFile(AbsolutePath filename, string text) @@ -168,10 +175,10 @@ namespace Wabbajack.VirtualFileSystem.Test await filename.WriteAllTextAsync(text); } - private static void ZipUpFolder(AbsolutePath folder, AbsolutePath output) + private static async Task ZipUpFolder(AbsolutePath folder, AbsolutePath output) { ZipFile.CreateFromDirectory((string)folder, (string)output); - folder.DeleteDirectory(); + await folder.DeleteDirectory(); } } } diff --git a/Wabbajack.VirtualFileSystem/Context.cs b/Wabbajack.VirtualFileSystem/Context.cs index 2d42769b..5105d83d 100644 --- a/Wabbajack.VirtualFileSystem/Context.cs +++ b/Wabbajack.VirtualFileSystem/Context.cs @@ -195,7 +195,7 @@ namespace Wabbajack.VirtualFileSystem } } - public async Task Stage(IEnumerable files) + public async Task> Stage(IEnumerable files) { var grouped = files.SelectMany(f => f.FilesInFullPath) .Distinct() @@ -215,18 +215,18 @@ namespace Wabbajack.VirtualFileSystem file.StagedPath = file.RelativeName.RelativeTo(tmpPath); } - return () => + return async () => { - paths.Do(p => + foreach (var p in paths) { - p.DeleteDirectory(); - }); + await p.DeleteDirectory(); + } }; } - public async Task> StageWith(IEnumerable files) + public async Task> StageWith(IEnumerable files) { - return new DisposableList(await Stage(files), files); + return new AsyncDisposableList(await Stage(files), files); } @@ -275,7 +275,7 @@ namespace Wabbajack.VirtualFileSystem _knownFiles = new List(); } - + #endregion } @@ -294,6 +294,21 @@ namespace Wabbajack.VirtualFileSystem } } + public class AsyncDisposableList : List, IAsyncDisposable + { + private Func _unstage; + + public AsyncDisposableList(Func unstage, IEnumerable files) : base(files) + { + _unstage = unstage; + } + + public async ValueTask DisposeAsync() + { + await _unstage(); + } + } + public class IndexRoot { public static IndexRoot Empty = new IndexRoot(); diff --git a/Wabbajack/Converters/BoolToVisibilityConverter.cs b/Wabbajack/Converters/BoolToVisibilityConverter.cs index 8a132dd8..8e577fdd 100644 --- a/Wabbajack/Converters/BoolToVisibilityConverter.cs +++ b/Wabbajack/Converters/BoolToVisibilityConverter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.Windows; using System.Windows.Data; diff --git a/Wabbajack/Util/SystemParametersConstructor.cs b/Wabbajack/Util/SystemParametersConstructor.cs index 28abeafb..cad457b9 100644 --- a/Wabbajack/Util/SystemParametersConstructor.cs +++ b/Wabbajack/Util/SystemParametersConstructor.cs @@ -56,7 +56,6 @@ namespace Wabbajack.Util ScreenHeight = height, VideoMemorySize = video_memory, SystemMemorySize = (long)memory.ullTotalPhys, - WindowsVersion = Environment.OSVersion.Version }; } } diff --git a/Wabbajack/View Models/Gallery/ModListMetadataVM.cs b/Wabbajack/View Models/Gallery/ModListMetadataVM.cs index 1decb1b4..4eb1a17f 100644 --- a/Wabbajack/View Models/Gallery/ModListMetadataVM.cs +++ b/Wabbajack/View Models/Gallery/ModListMetadataVM.cs @@ -155,7 +155,7 @@ namespace Wabbajack try { var downloader = DownloadDispatcher.ResolveArchive(Metadata.Links.Download); - var result = await downloader.Download(new Archive { Name = Metadata.Title, Size = Metadata.DownloadMetadata?.Size ?? 0 }, Location); + var result = await downloader.Download(new Archive(state: null!) { Name = Metadata.Title, Size = Metadata.DownloadMetadata?.Size ?? 0 }, Location); // Want to rehash to current file, even if failed? Location.FileHashCached(); tcs.SetResult(result);