Merge branch 'master' into issue-606

This commit is contained in:
Timothy Baldridge 2020-03-04 22:29:27 -07:00 committed by GitHub
commit ae5ac5fa13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 194 additions and 120 deletions

View File

@ -1,10 +1,12 @@
### Changelog
#### Version - 1.1.0.0 -
#### Version - Next
* Binary Patching stores temporary and patch data on disk instead of memory (reducing memory usage)
* Fix a memory leak with diffing progress reporting
* Fix a bug with bad data in inferred game INI files.
* Building BSAs now leverage Virtual Memory resulting in a 32x reduction in memory usage during installation
* Fix a bug with bad data in inferred game INI files.
* Added download support for YouTube
* Slideshow can now display mods from non-Nexus sites
* Building BSAs now leverage Virtual Memory resulting in a 32x reduction in memory usage during installation (#609)
#### Verison - 1.0.0.0 - 2/29/2020
* 1.0, first non-beta release

View File

@ -18,7 +18,7 @@
<ItemGroup>
<PackageReference Include="BunnyCDN.Net.Storage" Version="1.0.2" />
<PackageReference Include="Dapper" Version="2.0.30" />
<PackageReference Include="FluentFTP" Version="31.3.0" />
<PackageReference Include="FluentFTP" Version="31.3.2" />
<PackageReference Include="graphiql" Version="1.2.0" />
<PackageReference Include="GraphQL" Version="3.0.0-preview-1352" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Core" Version="2.2.0" />

View File

@ -106,5 +106,8 @@ namespace Wabbajack.Common
public static string LogsFolder = "logs";
public static int MaxOldLogs = 50;
public static string SettingsFile => Path.Combine(LocalAppDataPath, "settings.json");
public static byte SettingsVersion => 1;
}
}

View File

@ -224,7 +224,7 @@ namespace Wabbajack.Common
return sha.Hash.ToBase64();
}
public static string StringSHA256Hex(this string s)
{
var sha = new SHA256Managed();

View File

@ -84,6 +84,27 @@ namespace Wabbajack.Lib
return id;
}
public async Task<bool> GatherMetaData()
{
Utils.Log($"Getting meta data for {SelectedArchives.Count} archives");
await SelectedArchives.PMap(Queue, async a =>
{
if (a.State is IMetaState metaState)
{
var b = await metaState.LoadMetaData();
Utils.Log(b
? $"Getting meta data for {a.Name} was successful!"
: $"Getting meta data for {a.Name} failed!");
}
else
{
Utils.Log($"Archive {a.Name} is not an AbstractMetaState!");
}
});
return true;
}
public void ExportModList()
{
Utils.Log($"Exporting ModList to {ModListOutputFile}");

View File

@ -30,7 +30,7 @@ namespace Wabbajack.Lib
typeof(PropertyFile), typeof(SteamMeta), typeof(SteamWorkshopDownloader), typeof(SteamWorkshopDownloader.State),
typeof(LoversLabDownloader.State), typeof(GameFileSourceDownloader.State), typeof(VectorPlexusDownloader.State),
typeof(DeadlyStreamDownloader.State), typeof(AFKModsDownloader.State), typeof(TESAllianceDownloader.State),
typeof(TES3ArchiveState), typeof(TES3FileState), typeof(BethesdaNetDownloader.State), typeof(YouTubeDownloader)
typeof(TES3ArchiveState), typeof(TES3FileState), typeof(BethesdaNetDownloader.State), typeof(YouTubeDownloader), typeof(IMetaState)
},
};
Config.VersionTolerance.Mode = VersionToleranceMode.Standard;

View File

@ -7,10 +7,23 @@ using Wabbajack.Lib.Validation;
namespace Wabbajack.Lib.Downloaders
{
public interface IMetaState
{
string URL { get; }
string Name { get; set; }
string Author { get; set; }
string Version { get; set; }
string ImageURL { get; set; }
bool IsNSFW { get; set; }
string Description { get; set; }
Task<bool> LoadMetaData();
}
public abstract class AbstractDownloadState
{
public static List<Type> KnownSubTypes = new List<Type>()
public static List<Type> KnownSubTypes = new List<Type>
{
typeof(HTTPDownloader.State),
typeof(GameFileSourceDownloader.State),

View File

@ -5,6 +5,7 @@ using System.Net;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using HtmlAgilityPack;
using Newtonsoft.Json;
using Wabbajack.Common;
using Wabbajack.Lib.Validation;
@ -15,8 +16,8 @@ namespace Wabbajack.Lib.Downloaders
// IPS4 is the site used by LoversLab, VectorPlexus, etc. the general mechanics of each site are the
// same, so we can fairly easily abstract the state.
// Pass in the state type via TState
public abstract class AbstractIPS4Downloader<TDownloader, TState> : AbstractNeedsLoginDownloader, IDownloader
where TState : AbstractIPS4Downloader<TDownloader, TState>.State<TDownloader>, new()
public abstract class AbstractIPS4Downloader<TDownloader, TState> : AbstractNeedsLoginDownloader, IDownloader
where TState : AbstractIPS4Downloader<TDownloader, TState>.State<TDownloader>, new()
where TDownloader : IDownloader
{
public override string SiteName { get; }
@ -61,7 +62,7 @@ namespace Wabbajack.Lib.Downloaders
}
public class State<TDownloader> : AbstractDownloadState where TDownloader : IDownloader
public class State<TDownloader> : AbstractDownloadState, IMetaState where TDownloader : IDownloader
{
public string FileID { get; set; }
public string FileName { get; set; }
@ -69,10 +70,12 @@ namespace Wabbajack.Lib.Downloaders
private static bool IsHTTPS => Downloader.SiteURL.AbsolutePath.StartsWith("https://");
private static string URLPrefix => IsHTTPS ? "https://" : "http://";
private static string Site => string.IsNullOrWhiteSpace(Downloader.SiteURL.Query)
public static string Site => string.IsNullOrWhiteSpace(Downloader.SiteURL.Query)
? $"{URLPrefix}{Downloader.SiteURL.Host}"
: Downloader.SiteURL.ToString();
public static AbstractNeedsLoginDownloader Downloader => (AbstractNeedsLoginDownloader)(object)DownloadDispatcher.GetInstance<TDownloader>();
public override object[] PrimaryKey
{
get
@ -100,13 +103,13 @@ namespace Wabbajack.Lib.Downloaders
private async Task<Stream> ResolveDownloadStream()
{
var downloader = (AbstractNeedsLoginDownloader)(object)DownloadDispatcher.GetInstance<TDownloader>();
//var downloader = (AbstractNeedsLoginDownloader)(object)DownloadDispatcher.GetInstance<TDownloader>();
TOP:
var csrfurl = FileID == null
? $"{Site}/files/file/{FileName}/?do=download"
: $"{Site}/files/file/{FileName}/?do=download&r={FileID}";
var html = await downloader.AuthedClient.GetStringAsync(csrfurl);
var html = await Downloader.AuthedClient.GetStringAsync(csrfurl);
var pattern = new Regex("(?<=csrfKey=).*(?=[&\"\'])|(?<=csrfKey: \").*(?=[&\"\'])");
var matches = pattern.Matches(html).Cast<Match>();
@ -122,10 +125,10 @@ namespace Wabbajack.Lib.Downloaders
: $"{Site}/files/file/{FileName}/{sep}do=download&r={FileID}&confirm=1&t=1&csrfKey={csrfKey}";
var streamResult = await downloader.AuthedClient.GetAsync(url);
var streamResult = await Downloader.AuthedClient.GetAsync(url);
if (streamResult.StatusCode != HttpStatusCode.OK)
{
Utils.ErrorThrow(new InvalidOperationException(), $"{downloader.SiteName} servers reported an error for file: {FileID}");
Utils.ErrorThrow(new InvalidOperationException(), $"{Downloader.SiteName} servers reported an error for file: {FileID}");
}
var contentType = streamResult.Content.Headers.ContentType;
@ -138,7 +141,7 @@ namespace Wabbajack.Lib.Downloaders
var secs = times.Download - times.CurrentTime;
for (int x = 0; x < secs; x++)
{
Utils.Status($"Waiting for {secs} at the request of {downloader.SiteName}", Percent.FactoryPutInRange(x, secs));
Utils.Status($"Waiting for {secs} at the request of {Downloader.SiteName}", Percent.FactoryPutInRange(x, secs));
await Task.Delay(1000);
}
streamResult.Dispose();
@ -200,7 +203,19 @@ namespace Wabbajack.Lib.Downloaders
};
}
private static AbstractNeedsLoginDownloader Downloader => (AbstractNeedsLoginDownloader)(object)DownloadDispatcher.GetInstance<TDownloader>();
// from IMetaState
public string URL => $"{Site}/files/file/{FileName}";
public string Name { get; set; }
public string Author { get; set; }
public string Version { get; set; }
public string ImageURL { get; set; }
public virtual bool IsNSFW { get; set; }
public string Description { get; set; }
public virtual async Task<bool> LoadMetaData()
{
return false;
}
}
protected AbstractIPS4Downloader(Uri loginUri, string encryptedKeyName, string cookieDomain) :

View File

@ -1,24 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reactive;
using System.Reactive.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Windows.Input;
using CefSharp;
using ReactiveUI;
using HtmlAgilityPack;
using Wabbajack.Common;
using Wabbajack.Lib.LibCefHelpers;
using Wabbajack.Lib.NexusApi;
using Wabbajack.Lib.Validation;
using Wabbajack.Lib.WebAutomation;
using File = Alphaleonis.Win32.Filesystem.File;
namespace Wabbajack.Lib.Downloaders
{
@ -29,7 +14,6 @@ namespace Wabbajack.Lib.Downloaders
public override Uri SiteURL => new Uri("https://www.loverslab.com");
public override Uri IconUri => new Uri("https://www.loverslab.com/favicon.ico");
#endregion
public LoversLabDownloader() : base(new Uri("https://www.loverslab.com/login"),
"loverslabcookies", "loverslab.com")
{
@ -46,8 +30,31 @@ namespace Wabbajack.Lib.Downloaders
Utils.Error(ex);
}
}
public class State : State<LoversLabDownloader>
{
public override bool IsNSFW => true;
public override async Task<bool> LoadMetaData()
{
var html = await Downloader.AuthedClient.GetStringAsync(URL);
var doc = new HtmlDocument();
doc.LoadHtml(html);
var node = doc.DocumentNode;
Name = node.SelectNodes("//h1[@class='ipsType_pageTitle ipsContained_container']/span")?.First().InnerHtml;
Author = node
.SelectNodes(
"//div[@class='ipsBox_alt']/div[@class='ipsPhotoPanel ipsPhotoPanel_tiny ipsClearfix ipsSpacer_bottom']/div/p[@class='ipsType_reset ipsType_large ipsType_blendLinks']/a")
?.First().InnerHtml;
Version = node.SelectNodes("//section/h2[@class='ipsType_sectionHead']/span[@data-role='versionTitle']")
?
.First().InnerHtml;
ImageURL = node
.SelectNodes(
"//div[@class='ipsBox ipsSpacer_top ipsSpacer_double']/section/div[@class='ipsPad ipsAreaBackground']/div[@class='ipsCarousel ipsClearfix']/div[@class='ipsCarousel_inner']/ul[@class='cDownloadsCarousel ipsClearfix']/li[@class='ipsCarousel_item ipsAreaBackground_reset ipsPad_half']/span[@class='ipsThumb ipsThumb_medium ipsThumb_bg ipsCursor_pointer']")
?.First().GetAttributeValue("data-fullurl", "none");
return true;
}
}
}
}

View File

@ -6,11 +6,13 @@ using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using Ceras;
using ReactiveUI;
using Wabbajack.Common;
using Wabbajack.Common.StatusFeed.Errors;
using Wabbajack.Lib.NexusApi;
using Wabbajack.Lib.Validation;
using Game = Wabbajack.Common.Game;
namespace Wabbajack.Lib.Downloaders
{
@ -69,21 +71,18 @@ namespace Wabbajack.Lib.Downloaders
Utils.Error($"Error getting mod info for Nexus mod with {general.modID}");
throw;
}
return new State
{
GameName = general.gameName,
FileID = general.fileID,
ModID = general.modID,
Name = NexusApiUtils.FixupSummary(info.name),
Author = NexusApiUtils.FixupSummary(info.author),
Version = general.version ?? "0.0.0.0",
Author = info.author,
UploadedBy = info.uploaded_by,
UploaderProfile = info.uploaded_users_profile_url,
ModName = info.name,
SlideShowPic = info.picture_url,
NexusURL = NexusApiUtils.GetModURL(game, info.mod_id),
Summary = info.summary,
Adult = info.contains_adult_content
ImageURL = info.picture_url,
IsNSFW = info.contains_adult_content,
Description = NexusApiUtils.FixupSummary(info.summary),
GameName = general.gameName,
ModID = general.modID,
FileID = general.fileID
};
}
@ -123,20 +122,30 @@ namespace Wabbajack.Lib.Downloaders
}
}
public class State : AbstractDownloadState
public class State : AbstractDownloadState, IMetaState
{
public string URL => $"http://nexusmods.com/{NexusApiUtils.ConvertGameName(GameName)}/mods/{ModID}";
public string Name { get; set; }
public string Author { get; set; }
public string FileID { get; set; }
public string Version { get; set; }
public string ImageURL { get; set; }
public bool IsNSFW { get; set; }
public string Description { get; set; }
public async Task<bool> LoadMetaData()
{
return true;
}
public string GameName { get; set; }
public string ModID { get; set; }
public string UploadedBy { get; set; }
public string UploaderProfile { get; set; }
public string Version { get; set; }
public string SlideShowPic { get; set; }
public string ModName { get; set; }
public string NexusURL { get; set; }
public string Summary { get; set; }
public bool Adult { get; set; }
public string FileID { get; set; }
public override object[] PrimaryKey { get => new object[]{GameName, ModID, FileID};}
@ -192,7 +201,7 @@ namespace Wabbajack.Lib.Downloaders
}
catch (Exception ex)
{
Utils.Log($"{ModName} - {GameName} - {ModID} - {FileID} - Error getting Nexus download URL - {ex}");
Utils.Log($"{Name} - {GameName} - {ModID} - {FileID} - Error getting Nexus download URL - {ex}");
return false;
}

View File

@ -87,7 +87,7 @@ namespace Wabbajack.Lib
protected override async Task<bool> _Begin(CancellationToken cancel)
{
if (cancel.IsCancellationRequested) return false;
ConfigureProcessor(19, ConstructDynamicNumThreads(await RecommendQueueSize()));
ConfigureProcessor(20, ConstructDynamicNumThreads(await RecommendQueueSize()));
UpdateTracker.Reset();
UpdateTracker.NextStep("Gathering information");
Info("Looking for other profiles");
@ -280,6 +280,9 @@ namespace Wabbajack.Lib
UpdateTracker.NextStep("Building Patches");
await BuildPatches();
UpdateTracker.NextStep("Gathering Metadata");
await GatherMetaData();
ModList = new ModList
{
GameType = CompilingGame.Game,

View File

@ -284,7 +284,7 @@ namespace Wabbajack.Lib.NexusApi
try
{
Utils.Log($"Requesting manual download for {archive.ModName}");
Utils.Log($"Requesting manual download for {archive.Name}");
return (await Utils.Log(await ManuallyDownloadNexusFile.Create(archive)).Task).ToString();
}
catch (TaskCanceledException ex)

View File

@ -113,7 +113,7 @@ namespace Wabbajack.Lib.Validation
if (nexus_mod_permissions.TryGetValue(p.ArchiveHashPath[0], out var archive))
{
var ext = Path.GetExtension(p.ArchiveHashPath.Last());
var url = (archive.archive.State as NexusDownloader.State).NexusURL;
var url = (archive.archive.State as NexusDownloader.State).URL;
if (Consts.AssetFileExtensions.Contains(ext) && !(archive.permissions.CanModifyAssets ?? true))
{
ValidationErrors.Push($"{p.To} from {url} is set to disallow asset modification");
@ -131,7 +131,7 @@ namespace Wabbajack.Lib.Validation
{
if (nexus_mod_permissions.TryGetValue(p.ArchiveHashPath[0], out var archive))
{
var url = (archive.archive.State as NexusDownloader.State).NexusURL;
var url = (archive.archive.State as NexusDownloader.State).URL;
if (!(archive.permissions.CanExtractBSAs ?? true) &&
p.ArchiveHashPath.Skip(1).ButLast().Any(a => Consts.SupportedBSAs.Contains(Path.GetExtension(a).ToLower())))
{

View File

@ -16,7 +16,7 @@
<Version>4.1.7</Version>
</PackageReference>
<PackageReference Include="Fody">
<Version>6.1.0</Version>
<Version>6.1.1</Version>
</PackageReference>
<PackageReference Include="Genbox.AlphaFS">
<Version>2.2.2.1</Version>
@ -25,7 +25,7 @@
<Version>2.0.0-alpha.3</Version>
</PackageReference>
<PackageReference Include="HtmlAgilityPack">
<Version>1.11.20</Version>
<Version>1.11.21</Version>
</PackageReference>
<PackageReference Include="MegaApiClient">
<Version>1.7.1</Version>

View File

@ -1,10 +1,8 @@
using Newtonsoft.Json;
using ReactiveUI.Fody.Helpers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
using Wabbajack.Common;
using Wabbajack.Lib;
@ -14,7 +12,7 @@ namespace Wabbajack
[JsonObject(MemberSerialization.OptOut)]
public class MainSettings
{
private static string _filename = "settings.json";
public byte Version { get; set; }
public double PosX { get; set; }
public double PosY { get; set; }
@ -30,13 +28,26 @@ namespace Wabbajack
public static bool TryLoadTypicalSettings(out MainSettings settings)
{
if (!File.Exists(_filename))
if (!File.Exists(Consts.SettingsFile))
{
settings = default;
return false;
}
settings = JsonConvert.DeserializeObject<MainSettings>(File.ReadAllText(_filename));
return true;
// Version check
settings = JsonConvert.DeserializeObject<MainSettings>(File.ReadAllText(Consts.SettingsFile));
if (settings.Version == Consts.SettingsVersion)
return true;
var backup = Consts.SettingsFile + "-backup.json";
if(File.Exists(backup))
File.Delete(backup);
File.Copy(Consts.SettingsFile, backup);
File.Delete(Consts.SettingsFile);
settings = default;
return false;
}
public static void SaveSettings(MainSettings settings)
@ -48,7 +59,7 @@ namespace Wabbajack
//settings._saveSignal.OnCompleted();
//await settings._saveSignal;
File.WriteAllText(_filename, JsonConvert.SerializeObject(settings, Formatting.Indented));
File.WriteAllText(Consts.SettingsFile, JsonConvert.SerializeObject(settings, Formatting.Indented));
}
}
@ -76,14 +87,14 @@ namespace Wabbajack
[JsonObject(MemberSerialization.OptOut)]
public class PerformanceSettings : ViewModel
{
private bool _Manual = false;
public bool Manual { get => _Manual; set => this.RaiseAndSetIfChanged(ref _Manual, value); }
private bool _manual;
public bool Manual { get => _manual; set => RaiseAndSetIfChanged(ref _manual, value); }
private byte _MaxCores = byte.MaxValue;
public byte MaxCores { get => _MaxCores; set => this.RaiseAndSetIfChanged(ref _MaxCores, value); }
private byte _maxCores = byte.MaxValue;
public byte MaxCores { get => _maxCores; set => RaiseAndSetIfChanged(ref _maxCores, value); }
private Percent _TargetUsage = Percent.One;
public Percent TargetUsage { get => _TargetUsage; set => this.RaiseAndSetIfChanged(ref _TargetUsage, value); }
private Percent _targetUsage = Percent.One;
public Percent TargetUsage { get => _targetUsage; set => RaiseAndSetIfChanged(ref _targetUsage, value); }
public void AttachToBatchProcessor(ABatchProcessor processor)
{

View File

@ -274,7 +274,7 @@ namespace Wabbajack
_titleText = Observable.CombineLatest(
this.WhenAny(x => x.ModList)
.Select(modList => modList?.Name ?? string.Empty),
this.WhenAny(x => x.Slideshow.TargetMod.ModName)
this.WhenAny(x => x.Slideshow.TargetMod.State.Name)
.StartWith(default(string)),
this.WhenAny(x => x.Installing),
resultSelector: (modList, mod, installing) => installing ? mod : modList)
@ -282,7 +282,7 @@ namespace Wabbajack
_authorText = Observable.CombineLatest(
this.WhenAny(x => x.ModList)
.Select(modList => modList?.Author ?? string.Empty),
this.WhenAny(x => x.Slideshow.TargetMod.ModAuthor)
this.WhenAny(x => x.Slideshow.TargetMod.State.Author)
.StartWith(default(string)),
this.WhenAny(x => x.Installing),
resultSelector: (modList, mod, installing) => installing ? mod : modList)
@ -290,7 +290,7 @@ namespace Wabbajack
_description = Observable.CombineLatest(
this.WhenAny(x => x.ModList)
.Select(modList => modList?.Description ?? string.Empty),
this.WhenAny(x => x.Slideshow.TargetMod.ModDescription)
this.WhenAny(x => x.Slideshow.TargetMod.State.Description)
.StartWith(default(string)),
this.WhenAny(x => x.Installing),
resultSelector: (modList, mod, installing) => installing ? mod : modList)

View File

@ -38,6 +38,7 @@ namespace Wabbajack
catch (Exception ex)
{
Error = ex;
Utils.Error(ex, "Exception while loading the modlist!");
}
ImageObservable = Observable.Return(Unit.Default)

View File

@ -1,49 +1,29 @@
using ReactiveUI;
using System;
using System.IO;
using System.Net.Http;
using System.Reactive.Linq;
using System.Windows.Media.Imaging;
using Wabbajack.Common;
using Wabbajack.Lib;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.NexusApi;
namespace Wabbajack
{
public class ModVM : ViewModel
{
public string ModName { get; }
public string ModID { get; }
public string ModDescription { get; }
public string ModAuthor { get; }
public bool IsNSFW { get; }
public string ModURL { get; }
public string ImageURL { get; }
public IMetaState State { get; }
// Image isn't exposed as a direct property, but as an observable.
// This acts as a caching mechanism, as interested parties will trigger it to be created,
// and the cached image will automatically be released when the last interested party is gone.
public IObservable<BitmapImage> ImageObservable { get; }
public ModVM(NexusDownloader.State m)
public ModVM(IMetaState state)
{
ModName = NexusApiUtils.FixupSummary(m.ModName);
ModID = m.ModID;
ModDescription = NexusApiUtils.FixupSummary(m.Summary);
ModAuthor = NexusApiUtils.FixupSummary(m.Author);
IsNSFW = m.Adult;
ModURL = m.NexusURL;
ImageURL = m.SlideShowPic;
ImageObservable = Observable.Return(ImageURL)
State = state;
ImageObservable = Observable.Return(State.ImageURL)
.ObserveOn(RxApp.TaskpoolScheduler)
.DownloadBitmapImage((ex) => Utils.Log($"Skipping slide for mod {ModName} ({ModID})"))
.DownloadBitmapImage((ex) => Utils.Log($"Skipping slide for mod {State.Name}"))
.Replay(1)
.RefCount(TimeSpan.FromMilliseconds(5000));
}

View File

@ -7,6 +7,7 @@ using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Text.RegularExpressions;
using System.Windows.Media.Imaging;
using Wabbajack.Common;
using Wabbajack.Lib;
@ -33,7 +34,7 @@ namespace Wabbajack
public ModVM TargetMod => _targetMod.Value;
public ReactiveCommand<Unit, Unit> SlideShowNextItemCommand { get; } = ReactiveCommand.Create(() => { });
public ReactiveCommand<Unit, Unit> VisitNexusSiteCommand { get; }
public ReactiveCommand<Unit, Unit> VisitURLCommand { get; }
public const int PreloadAmount = 4;
@ -82,23 +83,24 @@ namespace Wabbajack
{
if (modList?.SourceModList?.Archives == null)
{
return Observable.Empty<NexusDownloader.State>()
.ToObservableChangeSet(x => x.ModID);
return Observable.Empty<IMetaState>()
.ToObservableChangeSet(x => x.URL);
}
return modList.SourceModList.Archives
.Select(m => m.State)
.OfType<NexusDownloader.State>()
.OfType<IMetaState>()
.DistinctBy(x => x.URL)
// Shuffle it
.Shuffle(_random)
.AsObservableChangeSet(x => x.ModID);
.AsObservableChangeSet(x => x.URL);
})
// Switch to the new list after every ModList change
.Switch()
.Transform(nexus => new ModVM(nexus))
.Transform(mod => new ModVM(mod))
.DisposeMany()
// Filter out any NSFW slides if we don't want them
.AutoRefreshOnObservable(slide => this.WhenAny(x => x.ShowNSFW))
.Filter(slide => !slide.IsNSFW || ShowNSFW)
.Filter(slide => !slide.State.IsNSFW || ShowNSFW)
.RefCount();
// Find target mod to display by combining dynamic list with currently desired index
@ -120,14 +122,18 @@ namespace Wabbajack
.Switch()
.ToGuiProperty(this, nameof(Image));
VisitNexusSiteCommand = ReactiveCommand.Create(
VisitURLCommand = ReactiveCommand.Create(
execute: () =>
{
Utils.OpenWebsite(TargetMod.ModURL);
Utils.OpenWebsite(TargetMod.State.URL);
return Unit.Default;
},
canExecute: this.WhenAny(x => x.TargetMod.ModURL)
.Select(x => x?.StartsWith("https://") ?? false)
canExecute: this.WhenAny(x => x.TargetMod.State.URL)
.Select(x =>
{
var regex = new Regex("^(http|https):\\/\\/");
return x != null && regex.Match(x).Success;
})
.ObserveOnGuiThread());
// Preload upcoming images

View File

@ -157,7 +157,7 @@ namespace Wabbajack
};
await vm.Driver.WaitForInitialized();
IWebDriver browser = new CefSharpWrapper(vm.Browser);
vm.Instructions = $"Please Download {state.ModName} - {state.ModID} - {state.FileID}";
vm.Instructions = $"Please Download {state.Name} - {state.ModID} - {state.FileID}";
browser.DownloadHandler = uri =>
{
manuallyDownloadNexusFile.Resume(uri);

View File

@ -73,7 +73,7 @@ namespace Wabbajack
})
.BindToStrict(this, x => x.PlayPauseButton.ToolTip)
.DisposeWith(dispose);
this.WhenAny(x => x.ViewModel.Slideshow.VisitNexusSiteCommand)
this.WhenAny(x => x.ViewModel.Slideshow.VisitURLCommand)
.BindToStrict(this, x => x.OpenWebsite.Command)
.DisposeWith(dispose);
this.BindStrict(this.ViewModel, x => x.Slideshow.ShowNSFW, x => x.ShowNSFWButton.IsChecked,

View File

@ -59,7 +59,10 @@ namespace Wabbajack
};
if (CLIArguments.NoSettings || !MainSettings.TryLoadTypicalSettings(out var settings))
{
_settings = new MainSettings();
_settings = new MainSettings
{
Version = Consts.SettingsVersion
};
RunWhenLoaded(DefaultSettings);
}
else

View File

@ -55,7 +55,7 @@
<PackageReference Include="CefSharp.Wpf" Version="79.1.350" />
<PackageReference Include="DynamicData" Version="6.14.8" />
<PackageReference Include="Extended.Wpf.Toolkit" Version="3.8.1" />
<PackageReference Include="Fody" Version="6.1.0">
<PackageReference Include="Fody" Version="6.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>