MessagePack, and basic sanity test passes

This commit is contained in:
Timothy Baldridge 2020-03-22 15:55:31 -06:00
parent 3b895f4dbb
commit d6123a7fb2
21 changed files with 267 additions and 208 deletions

View File

@ -37,6 +37,12 @@ namespace Wabbajack.Common.Http
return await SendStringAsync(request);
}
public async Task<string> GetStringAsync(Uri url)
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
return await SendStringAsync(request);
}
public async Task<string> DeleteStringAsync(string url)
{
var request = new HttpRequestMessage(HttpMethod.Delete, url);

View File

@ -0,0 +1,91 @@
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using MessagePack;
using MessagePack.Formatters;
using MessagePack.Resolvers;
namespace Wabbajack.Common
{
public partial class Utils
{
private static MessagePackSerializerOptions _messagePackOptions;
private static IFormatterResolver _resolver;
private static void MessagePackInit()
{
_resolver = CompositeResolver.Create(
new List<IMessagePackFormatter>{new HashFormatter()},
new List<IFormatterResolver> {StandardResolver.Instance}
);
_messagePackOptions = MessagePackSerializerOptions.Standard
.WithResolver(_resolver)
.WithCompression(MessagePackCompression.Lz4BlockArray);
}
/// <summary>
/// Writes a object to this stream using MessagePack
/// </summary>
/// <param name="stream"></param>
/// <param name="obj"></param>
/// <typeparam name="T"></typeparam>
public static async Task WriteAsMessagePackAsync<T>(this Stream stream, T obj)
{
await MessagePackSerializer.SerializeAsync(stream, obj, _messagePackOptions);
}
/// <summary>
/// Writes a object to this stream using MessagePack
/// </summary>
/// <param name="stream"></param>
/// <param name="obj"></param>
/// <typeparam name="T"></typeparam>
public static void WriteAsMessagePack<T>(this Stream stream, T obj)
{
MessagePackSerializer.Serialize(stream, obj, _messagePackOptions);
}
/// <summary>
/// Reads a object from this stream using MessagePack
/// </summary>
/// <param name="stream"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static async Task<T> ReadAsMessagePackAsync<T>(this Stream stream)
{
return await MessagePackSerializer.DeserializeAsync<T>(stream, _messagePackOptions);
}
/// <summary>
/// Reads a object from this stream using MessagePack
/// </summary>
/// <param name="stream"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T ReadAsMessagePack<T>(this Stream stream)
{
return MessagePackSerializer.Deserialize<T>(stream, _messagePackOptions);
}
}
#region Formatters
public class HashFormatter : IMessagePackFormatter<Hash>
{
public void Serialize(ref MessagePackWriter writer, Hash value, MessagePackSerializerOptions options)
{
writer.WriteUInt64((ulong)value);
}
public Hash Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
return new Hash(reader.ReadUInt64());
}
}
#endregion
}

View File

@ -6,7 +6,6 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Reflection;
@ -16,7 +15,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Alphaleonis.Win32.Filesystem;
using Ceras;
using ICSharpCode.SharpZipLib.BZip2;
using IniParser;
using Newtonsoft.Json;
@ -52,6 +50,8 @@ namespace Wabbajack.Common
static Utils()
{
MessagePackInit();
if (!Directory.Exists(Consts.LocalAppDataPath))
Directory.CreateDirectory(Consts.LocalAppDataPath);
@ -343,43 +343,6 @@ namespace Wabbajack.Common
return new DynamicIniData(new FileIniDataParser().ReadData(new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(file)))));
}
public static void ToCERAS<T>(this T obj, string filename, SerializerConfig config)
{
byte[] final;
final = ToCERAS(obj, config);
File.WriteAllBytes(filename, final);
}
public static byte[] ToCERAS<T>(this T obj, SerializerConfig config)
{
byte[] final;
var ceras = new CerasSerializer(config);
byte[] buffer = null;
ceras.Serialize(obj, ref buffer);
using (var m1 = new MemoryStream(buffer))
using (var m2 = new MemoryStream())
{
BZip2.Compress(m1, m2, false, 9);
m2.Seek(0, SeekOrigin.Begin);
final = m2.ToArray();
}
return final;
}
public static T FromCERAS<T>(this Stream data, SerializerConfig config)
{
var ceras = new CerasSerializer(config);
byte[] bytes = data.ReadAll();
using (var m1 = new MemoryStream(bytes))
using (var m2 = new MemoryStream())
{
BZip2.Decompress(m1, m2, false);
m2.Seek(0, SeekOrigin.Begin);
return ceras.Deserialize<T>(m2.ToArray());
}
}
public static void ToJSON<T>(this T obj, string filename)
{
if (File.Exists(filename))
@ -388,18 +351,6 @@ namespace Wabbajack.Common
JsonConvert.SerializeObject(obj, Formatting.Indented,
new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto}));
}
/*
public static void ToBSON<T>(this T obj, string filename)
{
using (var fo = File.Open(filename, System.IO.FileMode.Create))
using (var br = new BsonDataWriter(fo))
{
fo.SetLength(0);
var serializer = JsonSerializer.Create(new JsonSerializerSettings
{TypeNameHandling = TypeNameHandling.Auto});
serializer.Serialize(br, obj);
}
}*/
public static ulong ToMilliseconds(this DateTime date)
{
@ -1179,7 +1130,7 @@ namespace Wabbajack.Common
});
}
public static void OpenWebsite(string url)
public static void OpenWebsite(Uri url)
{
Process.Start(new ProcessStartInfo("cmd.exe", $"/c start {url}")
{

View File

@ -26,7 +26,6 @@
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Ceras" Version="4.1.7" />
<PackageReference Include="Genbox.AlphaFS" Version="2.2.2.1" />
<PackageReference Include="ini-parser-netstandard" Version="2.5.2" />
<PackageReference Include="MessagePack" Version="2.1.90" />

View File

@ -9,6 +9,7 @@ using Wabbajack.Common;
using Wabbajack.Lib.CompilationSteps;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.ModListRegistry;
using Wabbajack.Lib.Validation;
using Wabbajack.VirtualFileSystem;
using Directory = Alphaleonis.Win32.Filesystem.Directory;
using File = Alphaleonis.Win32.Filesystem.File;
@ -20,7 +21,7 @@ namespace Wabbajack.Lib
{
public string ModListName, ModListAuthor, ModListDescription, ModListImage, ModListWebsite, ModListReadme;
public bool ReadmeIsWebsite;
public string WabbajackVersion;
protected Version WabbajackVersion;
public abstract string VFSCacheName { get; }
//protected string VFSCacheName => Path.Combine(Consts.LocalAppDataPath, $"vfs_compile_cache.bin");
@ -122,7 +123,8 @@ namespace Wabbajack.Lib
ModList.ReadmeIsWebsite = ReadmeIsWebsite;
ModList.ToCERAS(Path.Combine(ModListOutputFolder, "modlist"), CerasConfig.Config);
using (var of = File.Create(Path.Combine(ModListOutputFolder, "modlist")))
of.WriteAsMessagePack(ModList);
if (File.Exists(ModListOutputFile))
File.Delete(ModListOutputFile);

View File

@ -82,7 +82,7 @@ namespace Wabbajack.Lib
return e.FromJSON<ModList>();
}
using (var e = entry.Open())
return e.FromCERAS<ModList>(CerasConfig.Config);
return e.ReadAsMessagePack<ModList>();
}
}

View File

@ -1,39 +0,0 @@
using Ceras;
using Compression.BSA;
using Wabbajack.Common;
using Wabbajack.Lib.Downloaders;
using Wabbajack.VirtualFileSystem;
namespace Wabbajack.Lib
{
public class CerasConfig
{
public static readonly SerializerConfig Config;
static CerasConfig()
{
Config = new SerializerConfig
{
KnownTypes =
{
typeof(ModList), typeof(Game), typeof(Directive), typeof(IgnoredDirectly),
typeof(NoMatch), typeof(InlineFile), typeof(PropertyType), typeof(CleanedESM),
typeof(RemappedInlineFile), typeof(FromArchive), typeof(CreateBSA), typeof(PatchedFromArchive),
typeof(SourcePatch), typeof(MergedPatch), typeof(Archive), typeof(IndexedArchive), typeof(IndexedEntry),
typeof(IndexedArchiveEntry), typeof(BSAIndexedEntry), typeof(VirtualFile),
typeof(ArchiveStateObject), typeof(FileStateObject), typeof(IDownloader),
typeof(IUrlDownloader), typeof(AbstractDownloadState), typeof(ManualDownloader.State),
typeof(DropboxDownloader), typeof(GoogleDriveDownloader.State), typeof(HTTPDownloader.State),
typeof(MegaDownloader.State), typeof(ModDBDownloader.State), typeof(NexusDownloader.State),
typeof(BSAStateObject), typeof(BSAFileStateObject), typeof(BA2StateObject), typeof(BA2DX10EntryState),
typeof(BA2FileEntryState), typeof(MediaFireDownloader.State), typeof(ArchiveMeta),
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(IMetaState)
},
};
Config.VersionTolerance.Mode = VersionToleranceMode.Standard;
}
}
}

View File

@ -2,8 +2,8 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Ceras;
using Compression.BSA;
using MessagePack;
using Wabbajack.Common;
using Wabbajack.Lib.Downloaders;
using Wabbajack.VirtualFileSystem;
@ -38,99 +38,122 @@ namespace Wabbajack.Lib
}
}
[MessagePackObject]
public class ModList
{
/// <summary>
/// Archives required by this modlist
/// </summary>
[Key(0)]
public List<Archive> Archives;
/// <summary>
/// The Mod Manager used to create the modlist
/// </summary>
public ModManager ModManager;
/// <summary>
/// The game variant to which this game applies
/// </summary>
public Game GameType;
/// <summary>
/// The build version of Wabbajack used when compiling the Modlist
/// </summary>
public string WabbajackVersion;
/// <summary>
/// Install directives
/// </summary>
public List<Directive> Directives;
/// <summary>
/// Name of the ModList
/// </summary>
public string Name;
/// <summary>
/// Author of the ModList
/// </summary>
[Key(1)]
public string Author;
/// <summary>
/// Description of the ModList
/// </summary>
[Key(2)]
public string Description;
/// <summary>
/// Install directives
/// </summary>
[Key(3)]
public List<Directive> Directives;
/// <summary>
/// The game variant to which this game applies
/// </summary>
[Key(4)]
public Game GameType;
/// <summary>
/// Hash of the banner-image
/// </summary>
[Key(5)]
public string Image;
/// <summary>
/// Website of the ModList
/// The Mod Manager used to create the modlist
/// </summary>
public string Website;
[Key(6)]
public ModManager ModManager;
/// <summary>
/// Name of the ModList
/// </summary>
[Key(7)]
public string Name;
/// <summary>
/// readme path or website
/// </summary>
[Key(8)]
public string Readme;
/// <summary>
/// The size of all the archives once they're downloaded
/// </summary>
public long DownloadSize => Archives.Sum(a => a.Size);
/// <summary>
/// The size of all the files once they are installed (excluding downloaded archives)
/// </summary>
public long InstallSize => Directives.Sum(s => s.Size);
/// <summary>
/// Estimate of the amount of space required in the VFS staging folders during installation
/// </summary>
public long ScratchSpaceSize => Archives.OrderByDescending(a => a.Size)
.Take(Environment.ProcessorCount)
.Sum(a => a.Size) * 2;
/// <summary>
/// Whether readme is a website
/// </summary>
[Key(9)]
public bool ReadmeIsWebsite;
/// <summary>
/// The build version of Wabbajack used when compiling the Modlist
/// </summary>
[Key(10)]
public Version WabbajackVersion;
/// <summary>
/// Website of the ModList
/// </summary>
[Key(11)]
public Uri Website;
/// <summary>
/// The size of all the archives once they're downloaded
/// </summary>
[IgnoreMember]
public long DownloadSize => Archives.Sum(a => a.Size);
/// <summary>
/// The size of all the files once they are installed (excluding downloaded archives)
/// </summary>
[IgnoreMember]
public long InstallSize => Directives.Sum(s => s.Size);
public ModList Clone()
{
return new MemoryStream(this.ToCERAS(CerasConfig.Config)).FromCERAS<ModList>(CerasConfig.Config);
using var ms = new MemoryStream();
ms.WriteAsMessagePack(this);
ms.Position = 0;
return ms.ReadAsMessagePack<ModList>();
}
}
public class Directive
[MessagePackObject]
[Union(0, typeof(ArchiveMeta))]
[Union(1, typeof(CreateBSA))]
[Union(2, typeof(FromArchive))]
[Union(3, typeof(MergedPatch))]
[Union(4, typeof(InlineFile))]
[Union(5, typeof(PatchedFromArchive))]
[Union(6, typeof(RemappedInlineFile))]
public abstract class Directive
{
[Key(0)]
public Hash Hash { get; set; }
[Key(1)]
public long Size { get; set; }
/// <summary>
/// location the file will be copied to, relative to the install path.
/// </summary>
public string To;
public long Size;
public Hash Hash;
[Key(2)]
public string To { get; set; }
}
public class IgnoredDirectly : Directive
@ -142,17 +165,21 @@ namespace Wabbajack.Lib
{
}
[MessagePackObject]
public class InlineFile : Directive
{
/// <summary>
/// Data that will be written as-is to the destination location;
/// </summary>
[Key(3)]
public string SourceDataID;
}
[MessagePackObject]
public class ArchiveMeta : Directive
{
public string SourceDataID;
[Key(3)]
public string SourceDataID { get; set; }
}
public enum PropertyType { Banner, Readme }
@ -173,82 +200,100 @@ namespace Wabbajack.Lib
/// <summary>
/// A file that has the game and MO2 folders remapped on installation
/// </summary>
[MessagePackObject]
public class RemappedInlineFile : InlineFile
{
}
[MessagePackObject]
public class SteamMeta : ArchiveMeta
{
public int ItemID;
/// <summary>
/// Size is in bytes
/// </summary>
public int Size;
[Key(4)]
public int ItemID { get; set; }
}
[MemberConfig(TargetMember.All)]
[MessagePackObject]
public class FromArchive : Directive
{
private string _fullPath;
public string[] ArchiveHashPath;
[Key(3)]
public string[] ArchiveHashPath { get; set; }
[Exclude]
public VirtualFile FromFile;
[IgnoreMember]
public VirtualFile FromFile { get; set; }
[Exclude]
[IgnoreMember]
public string FullPath => _fullPath ??= string.Join("|", ArchiveHashPath);
}
[MessagePackObject]
public class CreateBSA : Directive
{
public string TempID;
public uint Type;
[Key(3)]
public string TempID { get; set; }
[Key(4)]
public ArchiveStateObject State { get; set; }
[Key(5)]
public List<FileStateObject> FileStates { get; set; }
}
[MessagePackObject]
public class PatchedFromArchive : FromArchive
{
[Key(4)]
public Hash FromHash { get; set; }
/// <summary>
/// The file to apply to the source file to patch it
/// </summary>
public string PatchID;
[Exclude]
public Hash FromHash;
[Key(5)]
public string PatchID { get; set; }
}
[MessagePackObject]
public class SourcePatch
{
public string RelativePath;
public Hash Hash;
[Key(0)]
public Hash Hash { get; set; }
[Key(1)]
public string RelativePath { get; set; }
}
[MessagePackObject]
public class MergedPatch : Directive
{
public List<SourcePatch> Sources;
public string PatchID;
[Key(3)]
public string PatchID { get; set; }
[Key(4)]
public List<SourcePatch> Sources { get; set; }
}
[MessagePackObject]
public class Archive
{
/// <summary>
/// MurMur3 Hash of the archive
/// </summary>
[Key(0)]
public Hash Hash { get; set; }
/// <summary>
/// Meta INI for the downloaded archive
/// Meta INI for the downloaded archive
/// </summary>
[Key(1)]
public string Meta { get; set; }
/// <summary>
/// Human friendly name of this archive
/// </summary>
[Key(2)]
public string Name { get; set; }
[Key(3)]
public long Size { get; set; }
[Key(4)]
public AbstractDownloadState State { get; set; }
}
@ -285,15 +330,4 @@ namespace Wabbajack.Lib
{
public string[] HashPath;
}
/// <summary>
/// Data found inside a BSA file in an archive
/// </summary>
public class BSAIndexedEntry : IndexedEntry
{
/// <summary>
/// MurMur3 hash of the BSA this file comes from
/// </summary>
public string BSAHash;
}
}

View File

@ -3,13 +3,14 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Alphaleonis.Win32.Filesystem;
using MessagePack;
using Wabbajack.Lib.Validation;
namespace Wabbajack.Lib.Downloaders
{
public interface IMetaState
{
string URL { get; }
Uri URL { get; }
string Name { get; set; }
string Author { get; set; }
string Version { get; set; }
@ -20,6 +21,22 @@ namespace Wabbajack.Lib.Downloaders
Task<bool> LoadMetaData();
}
[MessagePackObject]
[Union(0, typeof(HTTPDownloader.State))]
[Union(1, typeof(GameFileSourceDownloader.State))]
[Union(2, typeof(GoogleDriveDownloader.State))]
[Union(3, typeof(LoversLabDownloader.State))]
[Union(4, typeof(ManualDownloader.State))]
[Union(5, typeof(MediaFireDownloader.State))]
[Union(6, typeof(MegaDownloader.State))]
[Union(7, typeof(ModDBDownloader.State))]
[Union(8, typeof(NexusDownloader.State))]
[Union(9, typeof(SteamWorkshopDownloader.State))]
[Union(10, typeof(VectorPlexusDownloader.State))]
[Union(11, typeof(AFKModsDownloader.State))]
[Union(12, typeof(TESAllianceDownloader.State))]
[Union(13, typeof(BethesdaNetDownloader.State))]
[Union(14, typeof(YouTubeDownloader.State))]
public abstract class AbstractDownloadState
{
@ -51,8 +68,10 @@ namespace Wabbajack.Lib.Downloaders
TypeToName = NameToType.ToDictionary(k => k.Value, k => k.Key);
}
[IgnoreMember]
public abstract object[] PrimaryKey { get; }
[IgnoreMember]
public string PrimaryKeyString
{
get

View File

@ -204,7 +204,7 @@ namespace Wabbajack.Lib.Downloaders
}
// from IMetaState
public string URL => $"{Site}/files/file/{FileName}";
public Uri URL => new Uri($"{Site}/files/file/{FileName}");
public string Name { get; set; }
public string Author { get; set; }
public string Version { get; set; }

View File

@ -2,13 +2,9 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection.Emit;
using System.Threading.Tasks;
using Ceras;
using SharpCompress.Common;
using Wabbajack.Common;
using Wabbajack.Lib.Exceptions;
using Wabbajack.Lib.Validation;
@ -53,14 +49,12 @@ namespace Wabbajack.Lib.Downloaders
{
}
[MemberConfig(TargetMember.All)]
public class State : AbstractDownloadState
{
public string Url { get; set; }
public List<string> Headers { get; set; }
[Exclude]
public Common.Http.Client Client { get; set; }
public override object[] PrimaryKey { get => new object[] {Url};}

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
using MessagePack;
using Wabbajack.Common;
using Wabbajack.Common.IO;
using Wabbajack.Lib.Validation;
@ -69,10 +70,14 @@ namespace Wabbajack.Lib.Downloaders
public async Task Prepare()
{
}
[MessagePackObject]
public class State : AbstractDownloadState
{
[Key(0)]
public string Url { get; set; }
[IgnoreMember]
public override object[] PrimaryKey { get => new object[] {Url}; }
public override bool IsWhitelisted(ServerWhitelist whitelist)

View File

@ -1,19 +1,14 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using Ceras;
using MongoDB.Bson.Serialization.Attributes;
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
{
@ -126,7 +121,7 @@ namespace Wabbajack.Lib.Downloaders
[BsonIgnoreExtraElements]
public class State : AbstractDownloadState, IMetaState
{
public string URL => $"http://nexusmods.com/{NexusApiUtils.ConvertGameName(GameName)}/mods/{ModID}";
public Uri URL => new Uri($"http://nexusmods.com/{NexusApiUtils.ConvertGameName(GameName)}/mods/{ModID}");
public string Name { get; set; }

View File

@ -313,7 +313,7 @@ namespace Wabbajack.Lib
Description = ModListDescription ?? "",
Readme = ModListReadme ?? "",
Image = ModListImage ?? "",
Website = ModListWebsite ?? ""
Website = ModListWebsite != null ? new Uri(ModListWebsite) : null
};
UpdateTracker.NextStep("Running Validation");
@ -472,7 +472,8 @@ namespace Wabbajack.Lib
Status($"Patching {entry.To}");
var srcFile = byPath[string.Join("|", entry.ArchiveHashPath.Skip(1))];
await using var srcStream = srcFile.OpenRead();
await using var outputStream = IncludeFile(out entry.PatchID);
await using var outputStream = IncludeFile(out var id);
entry.PatchID = id;
await using var destStream = LoadDataForTo(entry.To, absolutePaths);
await Utils.CreatePatch(srcStream, srcFile.Hash, destStream, entry.Hash, outputStream);
Info($"Patch size {outputStream.Length} for {entry.To}");

View File

@ -237,7 +237,7 @@ namespace Wabbajack.Lib
Description = ModListDescription ?? "",
Readme = ModListReadme ?? "",
Image = ModListImage ?? "",
Website = ModListWebsite ?? "",
Website = ModListWebsite != null ? new Uri(ModListWebsite) : null,
Archives = SelectedArchives.ToList(),
ModManager = ModManager.Vortex,
Directives = InstallDirectives,

View File

@ -12,9 +12,6 @@
<PackageReference Include="CefSharp.OffScreen">
<Version>79.1.350</Version>
</PackageReference>
<PackageReference Include="Ceras">
<Version>4.1.7</Version>
</PackageReference>
<PackageReference Include="Fody">
<Version>6.1.1</Version>
</PackageReference>
@ -30,6 +27,9 @@
<PackageReference Include="MegaApiClient">
<Version>1.7.1</Version>
</PackageReference>
<PackageReference Include="MessagePackAnalyzer">
<Version>2.1.90</Version>
</PackageReference>
<PackageReference Include="Microsoft.CSharp">
<Version>4.7.0</Version>
</PackageReference>

View File

@ -54,7 +54,7 @@ namespace Wabbajack
Metadata = metadata;
Location = Path.Combine(Consts.ModListDownloadFolder, Metadata.Links.MachineURL + Consts.ModListExtension);
IsBroken = metadata.ValidationSummary.HasFailures;
OpenWebsiteCommand = ReactiveCommand.Create(() => Utils.OpenWebsite($"https://www.wabbajack.org/modlist/{Metadata.Links.MachineURL}"));
OpenWebsiteCommand = ReactiveCommand.Create(() => Utils.OpenWebsite(new Uri($"https://www.wabbajack.org/modlist/{Metadata.Links.MachineURL}")));
ExecuteCommand = ReactiveCommand.CreateFromObservable<Unit, Unit>(
canExecute: this.WhenAny(x => x.IsBroken).Select(x => !x),
execute: (unit) =>

View File

@ -324,7 +324,7 @@ namespace Wabbajack
return Unit.Default;
},
canExecute: this.WhenAny(x => x.ModList.Website)
.Select(x => x?.StartsWith("https://") ?? false)
.Select(x => x != null)
.ObserveOnGuiThread());
_progressTitle = this.WhenAnyValue(

View File

@ -20,7 +20,7 @@ namespace Wabbajack
public string Readme => SourceModList?.Readme;
public string Author => SourceModList?.Author;
public string Description => SourceModList?.Description;
public string Website => SourceModList?.Website;
public Uri Website => SourceModList?.Website;
public ModManager ModManager => SourceModList?.ModManager ?? ModManager.MO2;
// Image isn't exposed as a direct property, but as an observable.
@ -96,7 +96,7 @@ namespace Wabbajack
if (string.IsNullOrEmpty(Readme)) return;
if (SourceModList.ReadmeIsWebsite)
{
Utils.OpenWebsite(Readme);
Utils.OpenWebsite(new Uri(Readme));
}
else
{

View File

@ -132,7 +132,7 @@ namespace Wabbajack
.Select(x =>
{
var regex = new Regex("^(http|https):\\/\\/");
return x != null && regex.Match(x).Success;
return x != null && regex.Match(x.ToString()).Success;
})
.ObserveOnGuiThread());

View File

@ -1,4 +1,5 @@
using System.Diagnostics;
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using Wabbajack.Common;
@ -17,17 +18,17 @@ namespace Wabbajack
private void GitHub_Click(object sender, RoutedEventArgs e)
{
Utils.OpenWebsite("https://github.com/wabbajack-tools/wabbajack");
Utils.OpenWebsite(new Uri("https://github.com/wabbajack-tools/wabbajack"));
}
private void Discord_Click(object sender, RoutedEventArgs e)
{
Utils.OpenWebsite("https://discord.gg/wabbajack");
Utils.OpenWebsite(new Uri("https://discord.gg/wabbajack"));
}
private void Patreon_Click(object sender, RoutedEventArgs e)
{
Utils.OpenWebsite("https://www.patreon.com/user?u=11907933");
Utils.OpenWebsite(new Uri("https://www.patreon.com/user?u=11907933"));
}
}
}