Merge Master

This commit is contained in:
Timothy Baldridge 2020-04-15 06:05:05 -06:00
commit 470992cc4a
115 changed files with 893 additions and 833 deletions

View File

@ -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
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

View File

@ -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}));

View File

@ -190,19 +190,14 @@ namespace Wabbajack.BuildServer.Test
ModListData = new ModList
{
Archives = new List<Archive>
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);

View File

@ -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);
}
}
}

View File

@ -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
}
}
});

View File

@ -22,7 +22,7 @@ namespace Wabbajack.BuildServer.Controllers
public async Task<long> 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;
}

View File

@ -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)
{

View File

@ -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<object> PrimaryKey => new object[0];

View File

@ -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()
}
}
}
})

View File

@ -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
}
}
}
});

View File

@ -541,9 +541,8 @@ namespace Wabbajack.BuildServer.Model.Models
var result = await conn.QueryFirstOrDefaultAsync<string>(@"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<AbstractDownloadState>())
{
State = result.FromJsonString<AbstractDownloadState>(),
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<AbstractDownloadState>())
{
State = result.Item1.FromJsonString<AbstractDownloadState>(),
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<AbstractDownloadState>())
{
State = result.State.FromJsonString<AbstractDownloadState>(),
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<AbstractDownloadState>())
{
Size = r.Size,
Hash = r.Hash,
State = r.State.FromJsonString<AbstractDownloadState>()
}).ToList();}
public async Task UpdateNonNexusModlistArchivesStatus(IEnumerable<(Archive Archive, bool IsValid)> results)

View File

@ -318,7 +318,7 @@ namespace Wabbajack.CLI.Verbs
private static async Task<string> 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)
{

View File

@ -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<ExitCode> Run()
{

View File

@ -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());

View File

@ -21,6 +21,9 @@ namespace Wabbajack
return ret;
}
/// <summary>
/// Adds the given values to the dictionary. If a key already exists, it will throw an exception
/// </summary>
public static void Add<K, V>(this IDictionary<K, V> dict, IEnumerable<KeyValuePair<K, V>> vals)
where K : notnull
{
@ -30,6 +33,9 @@ namespace Wabbajack
}
}
/// <summary>
/// Adds the given values to the dictionary. If a key already exists, it will be replaced
/// </summary>
public static void Set<K, V>(this IDictionary<K, V> dict, IEnumerable<KeyValuePair<K, V>> vals)
where K : notnull
{
@ -38,5 +44,15 @@ namespace Wabbajack
dict[val.Key] = val.Value;
}
}
/// <summary>
/// Clears the dictionary and adds the given values
/// </summary>
public static void SetTo<K, V>(this IDictionary<K, V> dict, IEnumerable<KeyValuePair<K, V>> vals)
where K : notnull
{
dict.Clear();
dict.Set(vals);
}
}
}

View File

@ -33,5 +33,32 @@ namespace Wabbajack
yield return next;
foreach (var itm in coll) yield return itm;
}
/// <summary>
/// Converts and filters a nullable enumerable to a non-nullable enumerable
/// </summary>
public static IEnumerable<T> NotNull<T>(this IEnumerable<T?> e)
where T : class
{
// Filter out nulls
return e.Where(e => e != null)
// Cast to non nullable type
.Select(e => e!);
}
/// <summary>
/// Selects items that are castable to the desired type
/// </summary>
/// <typeparam name="T">Type of the original enumerable to cast from</typeparam>
/// <typeparam name="R">Type to attempt casting to</typeparam>
/// <param name="e">Enumerable to process</param>
/// <returns>Enumerable with only objects that were castable</returns>
public static IEnumerable<R> WhereCastable<T, R>(this IEnumerable<T> e)
where T : class
where R : T
{
return e.Where(e => e is R)
.Select(e => (R)e);
}
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Wabbajack.Common
{
public static class ListExt
{
public static void SetTo<T>(this List<T> list, IEnumerable<T> rhs)
{
list.Clear();
list.AddRange(rhs);
}
}
}

View File

@ -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
}
/// <summary>
/// Tries to parse game data from an arbitrary string. Tries first via parsing as a game Enum, then by Nexus name,
/// <param nambe="someName"></param>
/// <returns></returns>
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.
/// <param nambe="someName">Name to query</param>
/// <returns>GameMetaData found</returns>
/// <exception cref="ArgumentNullException">If string could not be translated to a game</exception>
public static GameMetaData GetByFuzzyName(string someName)
{
return TryGetByFuzzyName(someName) ?? throw new ArgumentNullException($"{someName} could not be translated to a game");
}
/// <summary>
/// Tries to parse game data from an arbitrary string. Tries first via parsing as a game Enum, then by Nexus name.
/// <param nambe="someName">Name to query</param>
/// <returns>GameMetaData if found, otherwise null</returns>
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<Game, GameMetaData> Games = new Dictionary<Game, GameMetaData>
{
{
@ -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<int>{1027920},
RequiredFiles = new List<string>
{
"TESV.exe"
},
MainExecutable = "TESV.exe"
}
}
};

View File

@ -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<Cookie> Cookies = new List<Cookie>();
public async Task<HttpResponseMessage> GetAsync(string url, HttpCompletionOption responseHeadersRead = HttpCompletionOption.ResponseHeadersRead)
{

View File

@ -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<T>(this T obj, string filename)
@ -62,16 +60,12 @@ namespace Wabbajack.Common
return JsonConvert.SerializeObject(obj, JsonSettings);
}
public static T FromJson<T>(this AbsolutePath filename,
TypeNameHandling handling = TypeNameHandling.All,
TypeNameAssemblyFormatHandling format = TypeNameAssemblyFormatHandling.Full)
public static T FromJson<T>(this AbsolutePath filename)
{
return JsonConvert.DeserializeObject<T>(filename.ReadAllText(), JsonSettings)!;
}
public static T FromJsonString<T>(this string data,
TypeNameHandling handling = TypeNameHandling.Objects,
TypeNameAssemblyFormatHandling format = TypeNameAssemblyFormatHandling.Full)
public static T FromJsonString<T>(this string data)
{
return JsonConvert.DeserializeObject<T>(data, JsonSettings)!;
}
@ -86,8 +80,6 @@ namespace Wabbajack.Common
throw new JsonException("Type deserialized into null");
return result;
}
private class HashJsonConverter : JsonConverter<Hash>
{
@ -118,7 +110,7 @@ namespace Wabbajack.Common
}
}
private class AbolutePathConverter : JsonConverter<AbsolutePath>
private class AbsolutePathConverter : JsonConverter<AbsolutePath>
{
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<string, Type> _nameToType = new Dictionary<string, Type>();
private static Dictionary<Type, string> _typeToName = new Dictionary<Type, string>();
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;
}

View File

@ -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}");

View File

@ -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;
}

View File

@ -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<IObservable<int>> _activeNumThreadsObservable = new Subject<IObservable<int>>();
private readonly Subject<IObservable<int>?> _activeNumThreadsObservable = new Subject<IObservable<int>?>();
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
/// </summary>
/// <param name="numThreads">Driving observable that determines how many threads should be actively pulling jobs from the queue</param>
public WorkQueue(IObservable<int> numThreads)
public WorkQueue(IObservable<int>? 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
/// </summary>
/// <param name="numThreads">Driving observable that determines how many threads should be actively pulling jobs from the queue</param>
public void SetActiveThreadsObservable(IObservable<int> numThreads)
public void SetActiveThreadsObservable(IObservable<int>? numThreads)
{
_activeNumThreadsObservable.OnNext(numThreads);
}

View File

@ -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<Percent> _percentCompleted { get; } = new Subject<Percent>();
@ -42,7 +42,6 @@ namespace Wabbajack.Lib
private Subject<bool> _isRunning { get; } = new Subject<bool>();
public IObservable<bool> IsRunning => _isRunning;
private int _configured;
private int _started;
private readonly CancellationTokenSource _cancel = new CancellationTokenSource();
@ -53,21 +52,16 @@ namespace Wabbajack.Lib
public BehaviorSubject<byte> MaxCores = new BehaviorSubject<byte>(byte.MaxValue);
public BehaviorSubject<Percent> TargetUsagePercent = new BehaviorSubject<Percent>(Percent.One);
protected void ConfigureProcessor(int steps, IObservable<int> 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 };
}
/// <summary>

View File

@ -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<Archive> SelectedArchives = new List<Archive>();
public List<Directive> InstallDirectives = new List<Directive>();
public List<RawSourceFile> AllFiles = new List<RawSourceFile>();
public List<Archive> SelectedArchives { get; protected set; } = new List<Archive>();
public List<Directive> InstallDirectives { get; protected set; } = new List<Directive>();
public List<RawSourceFile> AllFiles { get; protected set; } = new List<RawSourceFile>();
public ModList ModList = new ModList();
public List<IndexedArchive> IndexedArchives = new List<IndexedArchive>();
public Dictionary<Hash, IEnumerable<VirtualFile>> IndexedFiles = new Dictionary<Hash, IEnumerable<VirtualFile>>();
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<Archive> ResolveArchive(Hash hash, IDictionary<Hash, IndexedArchive> 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<Archive> 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");

View File

@ -27,11 +27,12 @@ namespace Wabbajack.Lib
public AbsolutePath ModListArchive { get; private set; }
public ModList ModList { get; private set; }
public Dictionary<Hash, AbsolutePath> HashedArchives { get; set; }
public Dictionary<Hash, AbsolutePath> HashedArchives { get; } = new Dictionary<Hash, AbsolutePath>();
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<PatchedFromArchive>()
@ -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<Hash, AbsolutePath>(e.Item1, e.Item2)));
}
/// <summary>
@ -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");

View File

@ -14,7 +14,7 @@ namespace Wabbajack.Lib
return client;
}
public static async Task<Archive> GetModUpgrade(Hash hash)
public static async Task<Archive?> GetModUpgrade(Hash hash)
{
using var response = await GetClient()
.GetAsync($"{Consts.WabbajackBuildServerUri}alternative/{hash.ToHex()}");
@ -33,7 +33,7 @@ namespace Wabbajack.Lib
/// </summary>
/// <param name="hash"></param>
/// <returns></returns>
public static async Task<string> GetModIni(Hash hash)
public static async Task<string?> GetModIni(Hash hash)
{
var client = new Common.Http.Client();
try

View File

@ -11,7 +11,7 @@ namespace Wabbajack.Lib.CompilationSteps
_compiler = compiler;
}
public abstract ValueTask<Directive> Run(RawSourceFile source);
public abstract ValueTask<Directive?> Run(RawSourceFile source);
public abstract IState GetState();
}
}

View File

@ -51,7 +51,7 @@ namespace Wabbajack.Lib.CompilationSteps
return new State();
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> 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()
};
}

View File

@ -11,7 +11,7 @@ namespace Wabbajack.Lib.CompilationSteps
{
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> Run(RawSourceFile source)
{
if (!_compiler.IndexedFiles.TryGetValue(source.Hash, out var found)) return null;
var result = source.EvolveTo<FromArchive>();

View File

@ -10,7 +10,7 @@ namespace Wabbajack.Lib.CompilationSteps
{
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> Run(RawSourceFile source)
{
var result = source.EvolveTo<NoMatch>();
result.Reason = "No Match in Stack";

View File

@ -4,7 +4,7 @@ namespace Wabbajack.Lib.CompilationSteps
{
public interface ICompilationStep
{
ValueTask<Directive> Run(RawSourceFile source);
ValueTask<Directive?> Run(RawSourceFile source);
IState GetState();
}

View File

@ -25,7 +25,7 @@ namespace Wabbajack.Lib.CompilationSteps
.ToList();
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> Run(RawSourceFile source)
{
if (!source.AbsolutePath.InFolder(_mo2Compiler.MO2ModsFolder)) return null;
if (_allEnabledMods.Any(mod => source.AbsolutePath.InFolder(mod)))

View File

@ -14,7 +14,7 @@ namespace Wabbajack.Lib.CompilationSteps
_reason = $"Ignored because path ends with {postfix}";
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> Run(RawSourceFile source)
{
if (!((string)source.Path).EndsWith(_postfix)) return null;
var result = source.EvolveTo<IgnoredDirectly>();
@ -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);

View File

@ -13,7 +13,7 @@ namespace Wabbajack.Lib.CompilationSteps
_startDir = Consts.GameFolderFilesDir + "\\";
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> Run(RawSourceFile source)
{
if (!((string)source.Path).StartsWith(_startDir)) return null;
var i = source.EvolveTo<IgnoredDirectly>();

View File

@ -15,7 +15,7 @@ namespace Wabbajack.Lib.CompilationSteps
_gameFolder = compiler.GamePath;
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> Run(RawSourceFile source)
{
if (_gameFolderFilesExists)
{

View File

@ -14,7 +14,7 @@ namespace Wabbajack.Lib.CompilationSteps
_reason = $"Ignored because path contains {_pattern}";
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> Run(RawSourceFile source)
{
if (!((string)source.Path).Contains(_pattern)) return null;
var result = source.EvolveTo<IgnoredDirectly>();
@ -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);

View File

@ -17,7 +17,7 @@ namespace Wabbajack.Lib.CompilationSteps
_regex = new Regex(pattern);
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> Run(RawSourceFile source)
{
if (!_regex.IsMatch((string)source.Path)) return null;
var result = source.EvolveTo<IgnoredDirectly>();
@ -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);

View File

@ -14,7 +14,7 @@ namespace Wabbajack.Lib.CompilationSteps
_reason = string.Format("Ignored because path starts with {0}", _prefix);
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> 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);

View File

@ -19,7 +19,7 @@ namespace Wabbajack.Lib.CompilationSteps
};
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> Run(RawSourceFile source)
{
if (!_cruftFiles.Any(f => source.Path.StartsWith(f))) return null;
var result = source.EvolveTo<IgnoredDirectly>();

View File

@ -10,7 +10,7 @@ namespace Wabbajack.Lib.CompilationSteps
{
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> Run(RawSourceFile source)
{
var inline = source.EvolveTo<InlineFile>();
inline.SourceDataID = await _compiler.IncludeFile(await source.AbsolutePath.ReadAllBytesAsync());

View File

@ -11,7 +11,7 @@ namespace Wabbajack.Lib.CompilationSteps
{
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> Run(RawSourceFile source)
{
if (!Consts.ConfigFileExtensions.Contains(source.Path.Extension)) return null;
var result = source.EvolveTo<InlineFile>();

View File

@ -11,7 +11,7 @@ namespace Wabbajack.Lib.CompilationSteps
{
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> Run(RawSourceFile source)
{
if (source.AbsolutePath.Extension != Consts.ESP &&
source.AbsolutePath.Extension != Consts.ESM) return null;

View File

@ -14,7 +14,7 @@ namespace Wabbajack.Lib.CompilationSteps
_prefix = Consts.LOOTFolderFilesDir + "\\";
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> Run(RawSourceFile source)
{
if (!source.Path.StartsWith(_prefix)) return null;
var result = source.EvolveTo<InlineFile>();

View File

@ -11,7 +11,7 @@ namespace Wabbajack.Lib.CompilationSteps
{
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> Run(RawSourceFile source)
{
if (!source.Path.StartsWith("mods\\") || source.Path.FileName != Consts.MetaIni) return null;
var e = source.EvolveTo<InlineFile>();

View File

@ -23,7 +23,7 @@ namespace Wabbajack.Lib.CompilationSteps
.ToList();
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> Run(RawSourceFile source)
{
if (!source.AbsolutePath.InFolder(_modProfilesFolder)) return null;
if (_profiles.Any(profile => source.AbsolutePath.InFolder(profile))) return null;

View File

@ -12,11 +12,11 @@ namespace Wabbajack.Lib.CompilationSteps
public class IncludePatches : ACompilationStep
{
private readonly Dictionary<RelativePath, IGrouping<RelativePath, VirtualFile>> _indexed;
private VirtualFile _bsa;
private VirtualFile? _bsa;
private Dictionary<RelativePath, VirtualFile> _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<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> 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)

View File

@ -14,7 +14,7 @@ namespace Wabbajack.Lib.CompilationSteps
{
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> Run(RawSourceFile source)
{
var files = new HashSet<AbsolutePath>
{

View File

@ -16,7 +16,7 @@ namespace Wabbajack.Lib.CompilationSteps
_regex = new Regex(pattern);
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> 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);

View File

@ -18,7 +18,7 @@ namespace Wabbajack.Lib.CompilationSteps
_game = steamGame;
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> 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;

View File

@ -15,7 +15,7 @@ namespace Wabbajack.Lib.CompilationSteps
_mo2Compiler = (MO2Compiler) compiler;
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> 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<Directive> RemapFile(RawSourceFile source)
private async Task<Directive?> RemapFile(RawSourceFile source)
{
var data = await source.AbsolutePath.ReadAllTextAsync();
var originalData = data;

View File

@ -26,7 +26,7 @@ namespace Wabbajack.Lib.CompilationSteps
}).Select(kv => $"mods\\{kv.Key}\\");
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> 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);

View File

@ -19,7 +19,7 @@ namespace Wabbajack.Lib.CompilationSteps
_correctProfiles = _mo2Compiler.SelectedProfiles.Select(p => _mo2Compiler.MO2ProfileDir.Parent.Combine(p)).ToList();
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> Run(RawSourceFile source)
{
if (!_correctProfiles.Any(p => source.AbsolutePath.InFolder(p)))
return null;

View File

@ -12,7 +12,7 @@ namespace Wabbajack.Lib.CompilationSteps
{
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> Run(RawSourceFile source)
{
// * TODO I don't know what this does
/*

View File

@ -16,7 +16,7 @@ namespace Wabbajack.Lib.CompilationSteps
_mo2Compiler = (MO2Compiler) compiler;
}
public override async ValueTask<Directive> Run(RawSourceFile source)
public override async ValueTask<Directive?> Run(RawSourceFile source)
{
var filename = source.Path.FileName;
var gameFile = _mo2Compiler.GamePath.Combine((RelativePath)"Data", filename);

View File

@ -43,22 +43,22 @@ namespace Wabbajack.Lib
/// <summary>
/// Archives required by this modlist
/// </summary>
public List<Archive> Archives;
public List<Archive> Archives = new List<Archive>();
/// <summary>
/// Author of the ModList
/// </summary>
public string Author;
public string Author = string.Empty;
/// <summary>
/// Description of the ModList
/// </summary>
public string Description;
public string Description = string.Empty;
/// <summary>
/// Install directives
/// </summary>
public List<Directive> Directives;
public List<Directive> Directives = new List<Directive>();
/// <summary>
/// The game variant to which this game applies
@ -78,12 +78,12 @@ namespace Wabbajack.Lib
/// <summary>
/// Name of the ModList
/// </summary>
public string Name;
public string Name = string.Empty;
/// <summary>
/// readme path or website
/// </summary>
public string Readme;
public string Readme = string.Empty;
/// <summary>
/// Whether readme is a website
@ -93,12 +93,12 @@ namespace Wabbajack.Lib
/// <summary>
/// The build version of Wabbajack used when compiling the Modlist
/// </summary>
public Version WabbajackVersion;
public Version? WabbajackVersion;
/// <summary>
/// Website of the ModList
/// </summary>
public Uri Website;
public Uri? Website;
/// <summary>
/// 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<FileStateObject> FileStates { get; set; }
public ArchiveStateObject State { get; }
public List<FileStateObject> FileStates { get; set; } = new List<FileStateObject>();
public CreateBSA(ArchiveStateObject state, IEnumerable<FileStateObject>? 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<SourcePatch> Sources { get; set; }
public List<SourcePatch> Sources { get; set; } = new List<SourcePatch>();
}
[JsonName("Archive")]
@ -245,49 +254,33 @@ namespace Wabbajack.Lib
/// <summary>
/// Meta INI for the downloaded archive
/// </summary>
public string Meta { get; set; }
public string? Meta { get; set; }
/// <summary>
/// Human friendly name of this archive
/// </summary>
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; }
/// <summary>
/// A archive entry
/// </summary>
public class IndexedEntry
{
/// <summary>
/// MurMur3 hash of this file
/// </summary>
public string Hash;
/// <summary>
/// Path in the archive to this file
/// </summary>
public string Path;
/// <summary>
/// Size of the file (uncompressed)
/// </summary>
public long Size;
}
public class IndexedArchiveEntry : IndexedEntry
{
public string[] HashPath;
public IndexedArchive(VirtualFile file)
{
File = file;
}
}
}

View File

@ -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<bool> LoadMetaData();
}
public abstract class AbstractDownloadState
{
public static List<Type> KnownSubTypes = new List<Type>
{
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
}
}
/// <summary>
/// Returns true if this file is allowed to be downloaded via whitelist
/// </summary>
@ -84,7 +82,7 @@ namespace Wabbajack.Lib.Downloaders
public async Task<bool> 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);
}
/// <summary>
@ -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();
}
}

View File

@ -18,10 +18,12 @@ namespace Wabbajack.Lib.Downloaders
where TState : AbstractIPS4Downloader<TDownloader, TState>.State<TDownloader>, 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<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode)
public async Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode)
{
Uri url = DownloaderUtils.GetDirectURL(archiveINI);
@ -70,23 +72,23 @@ namespace Wabbajack.Lib.Downloaders
};
}
public class State<TStateDownloader> : AbstractDownloadState, IMetaState where TStateDownloader : IDownloader
public class State<TStateDownloader> : 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<bool> 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<Stream> ResolveDownloadStream()
private async Task<Stream?> 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")
{
}
}
}

View File

@ -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<Unit, Unit> TriggerLogin { get; }
public ReactiveCommand<Unit, Unit> ClearLogin { get; }
public IObservable<bool> IsLoggedIn => Utils.HaveEncryptedJsonObservable(_encryptedKeyName);
public abstract string SiteName { get; }
public virtual IObservable<string>? MetaInfo { get; }
public abstract Uri SiteURL { get; }
public virtual Uri? IconUri { get; }
/// <summary>
/// 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<Unit, Unit> TriggerLogin { get; }
public ReactiveCommand<Unit, Unit> ClearLogin { get; }
public IObservable<bool> IsLoggedIn => Utils.HaveEncryptedJsonObservable(_encryptedKeyName);
public abstract string SiteName { get; }
public virtual IObservable<string> 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<Helpers.Cookie[]> _source = new TaskCompletionSource<Helpers.Cookie[]>();
public Task<Helpers.Cookie[]> Task => _source.Task;
@ -138,6 +140,4 @@ namespace Wabbajack.Lib.Downloaders
}
}
}
}

View File

@ -30,6 +30,15 @@ namespace Wabbajack.Lib.Downloaders
public class BethesdaNetDownloader : IUrlDownloader, INeedsLogin
{
public const string DataName = "bethesda-net-data";
public ReactiveCommand<Unit, Unit> TriggerLogin { get; }
public ReactiveCommand<Unit, Unit> ClearLogin { get; }
public IObservable<bool> IsLoggedIn => Utils.HaveEncryptedJsonObservable(DataName);
public string SiteName => "Bethesda.NET";
public IObservable<string> 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<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode)
public async Task<AbstractDownloadState?> 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<BethesdaNetData> Login(Game game)
public static async Task<BethesdaNetData?> 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<Unit, Unit> TriggerLogin { get; }
public ReactiveCommand<Unit, Unit> ClearLogin { get; }
public IObservable<bool> IsLoggedIn => Utils.HaveEncryptedJsonObservable(DataName);
public string SiteName => "Bethesda.NET";
public IObservable<string> 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<bool> 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<CDPTree>();
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> depot_list { get; set; }
public List<Depot> depot_list { get; set; } = null!;
public class Depot
{
public List<CDPFile> file_list { get; set; }
public List<CDPFile> file_list { get; set; } = null!;
public class CDPFile
{
public int chunk_count { get; set; }
public List<Chunk> chunk_list { get; set; }
public List<Chunk> 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<BethesdaNetData> _source = new TaskCompletionSource<BethesdaNetData>();
public Task<BethesdaNetData> 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<string, string> headers = new Dictionary<string, string>();
}
}

View File

@ -43,7 +43,7 @@ namespace Wabbajack.Lib.Downloaders
IndexedDownloaders = Downloaders.ToDictionary(d => d.GetType());
}
public static async Task<AbstractDownloadState> Infer(Uri uri)
public static async Task<AbstractDownloadState?> Infer(Uri uri)
{
foreach (var inf in Inferencers)
{
@ -73,7 +73,7 @@ namespace Wabbajack.Lib.Downloaders
/// </summary>
/// <param name="ini"></param>
/// <returns></returns>
public static AbstractDownloadState ResolveArchive(string url)
public static AbstractDownloadState? ResolveArchive(string url)
{
return Downloaders.OfType<IUrlDownloader>().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);

View File

@ -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;

View File

@ -7,13 +7,13 @@ namespace Wabbajack.Lib.Downloaders
{
public class DropboxDownloader : IDownloader, IUrlDownloader
{
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode)
public async Task<AbstractDownloadState?> 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)
{

View File

@ -9,33 +9,30 @@ namespace Wabbajack.Lib.Downloaders
{
public class GameFileSourceDownloader : IDownloader
{
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode)
public async Task<AbstractDownloadState?> 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<GameFileSourceDownloader>();
}
public override string GetManifestURL(Archive a)
public override string? GetManifestURL(Archive a)
{
return null;
}

View File

@ -10,22 +10,19 @@ namespace Wabbajack.Lib.Downloaders
{
public class GoogleDriveDownloader : IDownloader, IUrlDownloader
{
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode)
public async Task<AbstractDownloadState?> 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&amp;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;
}

View File

@ -15,29 +15,24 @@ namespace Wabbajack.Lib.Downloaders
{
public class HTTPDownloader : IDownloader, IUrlDownloader
{
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode)
public async Task<AbstractDownloadState?> 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<string>();
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<string> Headers { get; set; }
public List<string> Headers { get; } = new List<string>();
[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)}"};

View File

@ -4,12 +4,11 @@ namespace Wabbajack.Lib.Downloaders
{
public interface IDownloader
{
Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode = false);
Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode = false);
/// <summary>
/// Called before any downloads are inacted by the installer;
/// </summary>
Task Prepare();
}
}

View File

@ -11,9 +11,9 @@ namespace Wabbajack.Lib.Downloaders
ReactiveCommand<Unit, Unit> ClearLogin { get; }
IObservable<bool> IsLoggedIn { get; }
string SiteName { get; }
IObservable<string> MetaInfo { get; }
IObservable<string>? MetaInfo { get; }
Uri SiteURL { get; }
Uri IconUri { get; }
Uri? IconUri { get; }
}
public struct LoginReturnMessage

View File

@ -2,6 +2,6 @@
{
public interface IUrlDownloader : IDownloader
{
AbstractDownloadState GetDownloaderState(string url);
AbstractDownloadState? GetDownloaderState(string url);
}
}

View File

@ -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<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode)
public async Task<AbstractDownloadState?> 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<MegaDownloader>().MegaApiClient;
private void MegaLogin()

View File

@ -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<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode)
public async Task<AbstractDownloadState?> 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<bool> 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}",
};
}
}

View File

@ -10,22 +10,24 @@ namespace Wabbajack.Lib.Downloaders
{
public class MediaFireDownloader : IUrlDownloader
{
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode)
public async Task<AbstractDownloadState?> 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<bool> 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<HTTPDownloader.State> Resolve()
private async Task<HTTPDownloader.State?> 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());
}
}
}

View File

@ -11,20 +11,17 @@ namespace Wabbajack.Lib.Downloaders
{
public class ModDBDownloader : IDownloader, IUrlDownloader
{
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode)
public async Task<AbstractDownloadState?> 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)

View File

@ -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<bool> IsLoggedIn => Utils.HaveEncryptedJsonObservable("nexusapikey");
@ -50,9 +50,9 @@ namespace Wabbajack.Lib.Downloaders
canExecute: IsLoggedIn.ObserveOnGuiThread());
}
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode)
public async Task<AbstractDownloadState?> 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<bool> Verify(Archive a)

View File

@ -14,20 +14,21 @@ namespace Wabbajack.Lib.Downloaders
{
public class SteamWorkshopDownloader : IUrlDownloader
{
private SteamWorkshopItem _item;
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode)
public async Task<AbstractDownloadState?> 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)
{

View File

@ -5,7 +5,7 @@ namespace Wabbajack.Lib.Downloaders.UrlDownloaders
{
public class BethesdaNetInferencer : IUrlInferencer
{
public async Task<AbstractDownloadState> Infer(Uri uri)
public async Task<AbstractDownloadState?> Infer(Uri uri)
{
return BethesdaNetDownloader.StateFromUrl(uri);
}

View File

@ -5,6 +5,6 @@ namespace Wabbajack.Lib.Downloaders.UrlDownloaders
{
public interface IUrlInferencer
{
Task<AbstractDownloadState> Infer(Uri uri);
Task<AbstractDownloadState?> Infer(Uri uri);
}
}

View File

@ -8,10 +8,9 @@ namespace Wabbajack.Lib.Downloaders.UrlDownloaders
{
public class YoutubeInferencer : IUrlInferencer
{
public async Task<AbstractDownloadState> Infer(Uri uri)
public async Task<AbstractDownloadState?> 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();

View File

@ -21,10 +21,10 @@ namespace Wabbajack.Lib.Downloaders
{
public class YouTubeDownloader : IDownloader
{
public async Task<AbstractDownloadState> GetDownloaderState(dynamic archiveINI, bool quickMode)
public async Task<AbstractDownloadState?> 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<Track> Tracks { get; set; } = new List<Track>();
[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; }

View File

@ -12,6 +12,5 @@ namespace Wabbajack.Lib.Exceptions
Code = code;
Reason = reason;
}
}
}

View File

@ -21,9 +21,9 @@ namespace Wabbajack.Lib.FileUploader
{
public static IObservable<bool> HaveAuthorAPIKey => Utils.HaveEncryptedJsonObservable(Consts.AuthorAPIKeyFile);
public static string ApiKeyOverride = null;
public static string? ApiKeyOverride = null;
public static async Task<string> GetAPIKey(string apiKey = null)
public static async Task<string> 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<string> UploadFile(AbsolutePath filename, Action<double> progressFn, string apikey=null)
public static Task<string> UploadFile(AbsolutePath filename, Action<double> progressFn, string? apikey = null)
{
var tcs = new TaskCompletionSource<string>();
Task.Run(async () =>
@ -123,7 +123,7 @@ namespace Wabbajack.Lib.FileUploader
return tcs.Task;
}
public static async Task<Common.Http.Client> GetAuthorizedClient(string apiKey=null)
public static async Task<Common.Http.Client> GetAuthorizedClient(string? apiKey = null)
{
var client = new Common.Http.Client();
client.Headers.Add(("X-API-KEY", await GetAPIKey(apiKey)));

View File

@ -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()

View File

@ -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<Directive> ExtraFiles { get; private set; } = new ConcurrentBag<Directive>();
public Dictionary<AbsolutePath, dynamic> ModInis { get; } = new Dictionary<AbsolutePath, dynamic>();
public HashSet<string> SelectedProfiles { get; set; } = new HashSet<string>();
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<Directive> ExtraFiles { get; private set; }
public Dictionary<AbsolutePath, dynamic> ModInis { get; private set; }
public HashSet<string> SelectedProfiles { get; set; } = new HashSet<string>();
protected override async Task<bool> _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<Directive>();
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<AbsolutePath, dynamic>(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
/// </summary>
private void ResetMembers()
{
AllFiles = null;
InstallDirectives = null;
SelectedArchives = null;
ExtraFiles = null;
AllFiles = new List<RawSourceFile>();
InstallDirectives = new List<Directive>();
SelectedArchives = new List<Archive>();
ExtraFiles = new ConcurrentBag<Directive>();
}
/// <summary>
/// Fills in the Patch fields in files that require them
/// </summary>
@ -450,7 +447,7 @@ namespace Wabbajack.Lib
private async Task BuildArchivePatches(Hash archiveSha, IEnumerable<PatchedFromArchive> group,
Dictionary<RelativePath, AbsolutePath> 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<ICompilationStep> GetStack()
@ -567,12 +563,5 @@ namespace Wabbajack.Lib
new DropAll(this)
};
}
public class IndexedFileMatch
{
public IndexedArchive Archive;
public IndexedArchiveEntry Entry;
public DateTime LastModified;
}
}
}

View File

@ -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<bool> _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<CleanedESM>().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);

View File

@ -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();
}
}

View File

@ -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<List<ModlistMetadata>> 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")]

View File

@ -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;
}
}

View File

@ -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> _userStatus;
private Task<UserStatus>? _userStatus;
public Task<UserStatus> 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<NexusApiClient> Get(string apiKey = null)
public static async Task<NexusApiClient> 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<NexusFileInfo> files { get; set; }
public List<NexusFileInfo> files { get; set; } = new List<NexusFileInfo>();
}
public async Task<GetModFilesResponse> 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;

View File

@ -19,15 +19,10 @@ namespace Wabbajack.Lib.NexusApi
public static string FixupSummary(string argSummary)
{
if (argSummary != null)
{
return argSummary.Replace("&#39;", "'")
.Replace("<br/>", "\n\n")
.Replace("<br />", "\n\n")
.Replace("&#33;", "!");
}
return argSummary;
return argSummary.Replace("&#39;", "'")
.Replace("<br/>", "\n\n")
.Replace("<br />", "\n\n")
.Replace("&#33;", "!");
}
}
}

View File

@ -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();
}

View File

@ -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<string> _source = new TaskCompletionSource<string>();
public Task<string> Task => _source.Task;

View File

@ -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?";

View File

@ -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;

View File

@ -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<Uri> _tcs = new TaskCompletionSource<Uri>();
public Task<Uri> Task => _tcs.Task;

View File

@ -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;
/// <summary>
/// Value used in LE ENBs for VideoMemorySizeMb
/// </summary>

View File

@ -10,33 +10,32 @@ namespace Wabbajack.Lib.Validation
public bool? CanUseInOtherGames { get; set; }
}
public class Author
{
public Permissions Permissions { get; set; }
public Dictionary<string, Game> Games;
}
//public class Author
//{
// public Permissions Permissions { get; set; }
// public Dictionary<string, Game> Games;
//}
public class Game
{
public Permissions Permissions;
public Dictionary<string, Mod> Mods;
}
//public class Game
//{
// public Permissions Permissions;
// public Dictionary<string, Mod> Mods;
//}
public class Mod
{
public Permissions Permissions;
public Dictionary<string, File> Files;
}
public class File
{
public Permissions Permissions;
}
//public class Mod
//{
// public Permissions Permissions;
// public Dictionary<string, File> Files;
//}
//public class File
//{
// public Permissions Permissions;
//}
public class ServerWhitelist
{
public List<string> GoogleIDs;
public List<string> AllowedPrefixes;
public List<string> GoogleIDs = new List<string>();
public List<string> AllowedPrefixes = new List<string>();
}
}

View File

@ -24,7 +24,7 @@ namespace Wabbajack.Lib
protected void RaiseAndSetIfChanged<T>(
ref T item,
T newItem,
[CallerMemberName] string propertyName = null)
[CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(item, newItem)) return;
item = newItem;

View File

@ -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}");

View File

@ -1,9 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<Platforms>x64</Platforms>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
<TargetFramework>netcoreapp3.1</TargetFramework>
<Platforms>x64</Platforms>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CefSharp.Common">

View File

@ -13,8 +13,8 @@ namespace Wabbajack.Lib.WebAutomation
{
public class CefSharpWrapper : IWebDriver
{
private IWebBrowser _browser;
public Action<Uri> DownloadHandler { get; set; }
private readonly IWebBrowser _browser;
public Action<Uri>? DownloadHandler { get; set; }
public CefSharpWrapper(IWebBrowser browser)
{
_browser = browser;
@ -24,7 +24,7 @@ namespace Wabbajack.Lib.WebAutomation
{
var tcs = new TaskCompletionSource<bool>();
EventHandler<LoadingStateChangedEventArgs> handler = null;
EventHandler<LoadingStateChangedEventArgs>? 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,

Some files were not shown because too many files have changed in this diff Show More