diff --git a/Wabbajack/Extensions/EnumerableExt.cs b/Wabbajack.Common/Extensions/EnumerableExt.cs
similarity index 100%
rename from Wabbajack/Extensions/EnumerableExt.cs
rename to Wabbajack.Common/Extensions/EnumerableExt.cs
diff --git a/Wabbajack.Common/Util/TempFile.cs b/Wabbajack.Common/Util/TempFile.cs
new file mode 100644
index 00000000..195dc343
--- /dev/null
+++ b/Wabbajack.Common/Util/TempFile.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Wabbajack.Common
+{
+ public class TempFile : IDisposable
+ {
+ public FileInfo File { get; private set; }
+ public bool DeleteAfter = true;
+
+ public TempFile(bool deleteAfter = true, bool createFolder = true)
+ : this(new FileInfo(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())))
+ {
+ }
+
+ public TempFile(FileInfo file, bool deleteAfter = true, bool createFolder = true)
+ {
+ this.File = file;
+ if (createFolder && !file.Directory.Exists)
+ {
+ file.Directory.Create();
+ }
+ this.DeleteAfter = deleteAfter;
+ }
+
+ public void Dispose()
+ {
+ if (DeleteAfter)
+ {
+ this.File.Delete();
+ }
+ }
+ }
+}
diff --git a/Wabbajack.Common/Util/TempFolder.cs b/Wabbajack.Common/Util/TempFolder.cs
new file mode 100644
index 00000000..346ea344
--- /dev/null
+++ b/Wabbajack.Common/Util/TempFolder.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Wabbajack.Common
+{
+ public class TempFolder : IDisposable
+ {
+ public DirectoryInfo Dir { get; private set; }
+ public bool DeleteAfter = true;
+
+ public TempFolder(bool deleteAfter = true)
+ {
+ this.Dir = new DirectoryInfo(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()));
+ this.Dir.Create();
+ this.DeleteAfter = deleteAfter;
+ }
+
+ public TempFolder(DirectoryInfo dir, bool deleteAfter = true)
+ {
+ this.Dir = dir;
+ if (!dir.Exists)
+ {
+ this.Dir.Create();
+ }
+ this.DeleteAfter = deleteAfter;
+ }
+
+ public TempFolder(string addedFolderPath, bool deleteAfter = true)
+ : this(new DirectoryInfo(Path.Combine(Path.GetTempPath(), addedFolderPath)), deleteAfter: deleteAfter)
+ {
+ }
+
+ public void Dispose()
+ {
+ if (DeleteAfter)
+ {
+ Utils.DeleteDirectory(this.Dir.FullName);
+ }
+ }
+ }
+}
diff --git a/Wabbajack.Common/Wabbajack.Common.csproj b/Wabbajack.Common/Wabbajack.Common.csproj
index bfde1f2b..2efda655 100644
--- a/Wabbajack.Common/Wabbajack.Common.csproj
+++ b/Wabbajack.Common/Wabbajack.Common.csproj
@@ -75,9 +75,6 @@
MinimumRecommendedRules.ruleset
-
- ..\..\..\Users\tbald\.nuget\packages\syroot.windows.io.knownfolders\1.2.1\lib\net452\Syroot.KnownFolders.dll
-
@@ -103,6 +100,7 @@
+
@@ -130,6 +128,8 @@
+
+
diff --git a/Wabbajack/Extensions/ReactiveUIExt.cs b/Wabbajack.Lib/Extensions/ReactiveUIExt.cs
similarity index 100%
rename from Wabbajack/Extensions/ReactiveUIExt.cs
rename to Wabbajack.Lib/Extensions/ReactiveUIExt.cs
diff --git a/Wabbajack.Lib/FodyWeavers.xml b/Wabbajack.Lib/FodyWeavers.xml
new file mode 100644
index 00000000..63fc1484
--- /dev/null
+++ b/Wabbajack.Lib/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/Wabbajack.Lib/FodyWeavers.xsd b/Wabbajack.Lib/FodyWeavers.xsd
new file mode 100644
index 00000000..f3ac4762
--- /dev/null
+++ b/Wabbajack.Lib/FodyWeavers.xsd
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/Wabbajack/View Models/FilePickerVM.cs b/Wabbajack.Lib/UI/FilePickerVM.cs
similarity index 57%
rename from Wabbajack/View Models/FilePickerVM.cs
rename to Wabbajack.Lib/UI/FilePickerVM.cs
index 33d010ee..dc6146de 100644
--- a/Wabbajack/View Models/FilePickerVM.cs
+++ b/Wabbajack.Lib/UI/FilePickerVM.cs
@@ -1,4 +1,5 @@
-using Microsoft.WindowsAPICodePack.Dialogs;
+using DynamicData;
+using Microsoft.WindowsAPICodePack.Dialogs;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using System;
@@ -9,7 +10,7 @@ using System.Reactive.Linq;
using System.Windows.Input;
using Wabbajack.Lib;
-namespace Wabbajack
+namespace Wabbajack.Lib
{
public class FilePickerVM : ViewModel
{
@@ -21,10 +22,10 @@ namespace Wabbajack
Folder
}
- public enum ExistCheckOptions
+ public enum CheckOptions
{
Off,
- IfNotEmpty,
+ IfPathNotEmpty,
On
}
@@ -43,7 +44,10 @@ namespace Wabbajack
public PathTypeOptions PathType { get; set; }
[Reactive]
- public ExistCheckOptions ExistCheckOption { get; set; }
+ public CheckOptions ExistCheckOption { get; set; }
+
+ [Reactive]
+ public CheckOptions FilterCheckOption { get; set; } = CheckOptions.IfPathNotEmpty;
[Reactive]
public IObservable AdditionalError { get; set; }
@@ -60,46 +64,49 @@ namespace Wabbajack
private readonly ObservableAsPropertyHelper _errorTooltip;
public string ErrorTooltip => _errorTooltip.Value;
- public List Filters { get; } = new List();
+ public SourceList Filters { get; } = new SourceList();
public FilePickerVM(object parentVM = null)
{
Parent = parentVM;
SetTargetPathCommand = ConstructTypicalPickerCommand();
- // Check that file exists
-
var existsCheckTuple = Observable.CombineLatest(
this.WhenAny(x => x.ExistCheckOption),
this.WhenAny(x => x.PathType),
this.WhenAny(x => x.TargetPath)
- // Dont want to debounce the initial value, because we know it's null
- .Skip(1)
- .Debounce(TimeSpan.FromMilliseconds(200))
- .StartWith(default(string)),
+ // Dont want to debounce the initial value, because we know it's null
+ .Skip(1)
+ .Debounce(TimeSpan.FromMilliseconds(200))
+ .StartWith(default(string)),
resultSelector: (existsOption, type, path) => (ExistsOption: existsOption, Type: type, Path: path))
- .Publish()
+ .StartWith((ExistsOption: ExistCheckOption, Type: PathType, Path: TargetPath))
+ .Replay(1)
+ .RefCount();
+
+ var doExistsCheck = existsCheckTuple
+ .Select(t =>
+ {
+ // Don't do exists type if we don't know what path type we're tracking
+ if (t.Type == PathTypeOptions.Off) return false;
+ switch (t.ExistsOption)
+ {
+ case CheckOptions.Off:
+ return false;
+ case CheckOptions.IfPathNotEmpty:
+ return !string.IsNullOrWhiteSpace(t.Path);
+ case CheckOptions.On:
+ return true;
+ default:
+ throw new NotImplementedException();
+ }
+ })
+ .Replay(1)
.RefCount();
_exists = Observable.Interval(TimeSpan.FromSeconds(3))
// Only check exists on timer if desired
- .FilterSwitch(existsCheckTuple
- .Select(t =>
- {
- // Don't do exists type if we don't know what path type we're tracking
- if (t.Type == PathTypeOptions.Off) return false;
- switch (t.ExistsOption)
- {
- case ExistCheckOptions.Off:
- return false;
- case ExistCheckOptions.IfNotEmpty:
- return !string.IsNullOrWhiteSpace(t.Path);
- case ExistCheckOptions.On:
- return true;
- default:
- throw new NotImplementedException();
- }
- }))
+ .FilterSwitch(doExistsCheck)
.Unit()
// Also check though, when fields change
.Merge(this.WhenAny(x => x.PathType).Unit())
@@ -113,14 +120,14 @@ namespace Wabbajack
{
switch (t.ExistsOption)
{
- case ExistCheckOptions.IfNotEmpty:
- if (string.IsNullOrWhiteSpace(t.Path)) return true;
+ case CheckOptions.IfPathNotEmpty:
+ if (string.IsNullOrWhiteSpace(t.Path)) return false;
break;
- case ExistCheckOptions.On:
+ case CheckOptions.On:
break;
- case ExistCheckOptions.Off:
+ case CheckOptions.Off:
default:
- return true;
+ return false;
}
switch (t.Type)
{
@@ -130,24 +137,82 @@ namespace Wabbajack
return File.Exists(t.Path);
case PathTypeOptions.Folder:
return Directory.Exists(t.Path);
+ case PathTypeOptions.Off:
+ default:
+ return false;
+ }
+ })
+ .DistinctUntilChanged()
+ .ObserveOn(RxApp.MainThreadScheduler)
+ .StartWith(false)
+ .ToProperty(this, nameof(Exists));
+
+ var passesFilters = Observable.CombineLatest(
+ this.WhenAny(x => x.TargetPath),
+ this.WhenAny(x => x.PathType),
+ this.WhenAny(x => x.FilterCheckOption),
+ Filters.Connect().QueryWhenChanged(),
+ resultSelector: (target, type, checkOption, query) =>
+ {
+ switch (type)
+ {
+ case PathTypeOptions.Either:
+ case PathTypeOptions.File:
+ break;
default:
return true;
}
+ if (query.Count == 0) return true;
+ switch (checkOption)
+ {
+ case CheckOptions.Off:
+ return true;
+ case CheckOptions.IfPathNotEmpty:
+ if (string.IsNullOrWhiteSpace(target)) return true;
+ break;
+ case CheckOptions.On:
+ break;
+ default:
+ throw new NotImplementedException();
+ }
+
+ try
+ {
+ var extension = Path.GetExtension(target);
+ if (extension == null || !extension.StartsWith(".")) return false;
+ extension = extension.Substring(1);
+ if (!query.Any(filter => filter.Extensions.Any(ext => string.Equals(ext, extension)))) return false;
+ }
+ catch (ArgumentException)
+ {
+ return false;
+ }
+ return true;
})
- .StartWith(false)
- .DistinctUntilChanged()
- .ObserveOn(RxApp.MainThreadScheduler)
- .ToProperty(this, nameof(Exists));
+ .StartWith(true)
+ .Select(passed =>
+ {
+ if (passed) return ErrorResponse.Success;
+ return ErrorResponse.Fail($"Path does not pass designated filters");
+ })
+ .Publish()
+ .RefCount();
_errorState = Observable.CombineLatest(
- this.WhenAny(x => x.Exists)
+ this.WhenAny(x => x.Exists),
+ Observable.CombineLatest(
+ this.WhenAny(x => x.Exists),
+ doExistsCheck,
+ resultSelector: (exists, doExists) => !doExists || exists)
.Select(exists => ErrorResponse.Create(successful: exists, exists ? default(string) : "Path does not exist")),
+ passesFilters,
this.WhenAny(x => x.AdditionalError)
.Select(x => x ?? Observable.Return(ErrorResponse.Success))
.Switch(),
- resultSelector: (exist, err) =>
+ resultSelector: (exists, existCheck, filter, err) =>
{
- if (exist.Failed) return exist;
+ if (existCheck.Failed) return existCheck;
+ if (filter.Failed) return filter;
return ErrorResponse.Convert(err);
})
.ToProperty(this, nameof(ErrorState));
@@ -161,12 +226,15 @@ namespace Wabbajack
_errorTooltip = Observable.CombineLatest(
this.WhenAny(x => x.Exists)
.Select(exists => exists ? default(string) : "Path does not exist"),
+ passesFilters
+ .Select(x => x.Reason),
this.WhenAny(x => x.AdditionalError)
.Select(x => x ?? Observable.Return(ErrorResponse.Success))
.Switch(),
- resultSelector: (exists, err) =>
+ resultSelector: (exists, filters, err) =>
{
if (!string.IsNullOrWhiteSpace(exists)) return exists;
+ if (!string.IsNullOrWhiteSpace(filters)) return filters;
return err?.Reason;
})
.ToProperty(this, nameof(ErrorTooltip));
@@ -201,7 +269,7 @@ namespace Wabbajack
Multiselect = false,
ShowPlacesList = true,
};
- foreach (var filter in Filters)
+ foreach (var filter in Filters.Items)
{
dlg.Filters.Add(filter);
}
diff --git a/Wabbajack.Lib/Wabbajack.Lib.csproj b/Wabbajack.Lib/Wabbajack.Lib.csproj
index faa70202..512c3af5 100644
--- a/Wabbajack.Lib/Wabbajack.Lib.csproj
+++ b/Wabbajack.Lib/Wabbajack.Lib.csproj
@@ -59,7 +59,6 @@
-
@@ -119,6 +118,7 @@
+
@@ -145,6 +145,7 @@
+
@@ -210,6 +211,9 @@
11.0.1
+
+ 11.0.1
+
0.24.0
diff --git a/Wabbajack.Test/FilePickerTests.cs b/Wabbajack.Test/FilePickerTests.cs
new file mode 100644
index 00000000..61f8f748
--- /dev/null
+++ b/Wabbajack.Test/FilePickerTests.cs
@@ -0,0 +1,344 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reactive.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using DynamicData;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Wabbajack.Common;
+using Wabbajack.Lib;
+
+namespace Wabbajack.Test
+{
+ [TestClass]
+ public class FilePickerTests
+ {
+ public static TempFile CreateSetFile(FilePickerVM vm)
+ {
+ var temp = new TempFile();
+ using (new FileStream(temp.File.FullName, FileMode.CreateNew)) { }
+ vm.TargetPath = temp.File.FullName;
+ return temp;
+ }
+
+ public static TempFolder CreateSetFolder(FilePickerVM vm)
+ {
+ var temp = new TempFolder();
+ Directory.CreateDirectory(temp.Dir.FullName);
+ vm.TargetPath = temp.Dir.FullName;
+ return temp;
+ }
+
+ [TestMethod]
+ public async Task Stock()
+ {
+ var vm = new FilePickerVM();
+ Assert.AreEqual(FilePickerVM.PathTypeOptions.Off, vm.PathType);
+ Assert.AreEqual(FilePickerVM.CheckOptions.Off, vm.ExistCheckOption);
+ await Task.Delay(250);
+ Assert.IsFalse(vm.Exists);
+ Assert.IsTrue(vm.ErrorState.Succeeded);
+ Assert.IsFalse(vm.InError);
+ }
+
+ [TestMethod]
+ public async Task FileNoExistsCheck_DoesNotExist()
+ {
+ var vm = new FilePickerVM();
+ vm.PathType = FilePickerVM.PathTypeOptions.File;
+ vm.ExistCheckOption = FilePickerVM.CheckOptions.Off;
+ await Task.Delay(250);
+ Assert.IsFalse(vm.Exists);
+ Assert.IsTrue(vm.ErrorState.Succeeded);
+ Assert.IsFalse(vm.InError);
+ }
+
+ [TestMethod]
+ public async Task FileNoExistsCheck_Exists()
+ {
+ var vm = new FilePickerVM();
+ using (CreateSetFile(vm))
+ {
+ vm.PathType = FilePickerVM.PathTypeOptions.File;
+ vm.ExistCheckOption = FilePickerVM.CheckOptions.Off;
+ await Task.Delay(250);
+ Assert.IsFalse(vm.Exists);
+ Assert.IsTrue(vm.ErrorState.Succeeded);
+ Assert.IsFalse(vm.InError);
+ }
+ }
+
+ [TestMethod]
+ public async Task ExistCheckTypeOff_DoesNotExist()
+ {
+ var vm = new FilePickerVM();
+ vm.PathType = FilePickerVM.PathTypeOptions.Off;
+ vm.ExistCheckOption = FilePickerVM.CheckOptions.On;
+ await Task.Delay(250);
+ Assert.IsFalse(vm.Exists);
+ Assert.IsTrue(vm.ErrorState.Succeeded);
+ Assert.IsFalse(vm.InError);
+ }
+
+ [TestMethod]
+ public async Task ExistCheckTypeOff_Exists()
+ {
+ var vm = new FilePickerVM();
+ using (CreateSetFile(vm))
+ {
+ vm.PathType = FilePickerVM.PathTypeOptions.Off;
+ vm.ExistCheckOption = FilePickerVM.CheckOptions.On;
+ await Task.Delay(250);
+ Assert.IsFalse(vm.Exists);
+ Assert.IsTrue(vm.ErrorState.Succeeded);
+ Assert.IsFalse(vm.InError);
+ }
+ }
+
+ [TestMethod]
+ public async Task FileIfNotEmptyCheck_DoesNotExist()
+ {
+ var vm = new FilePickerVM();
+ vm.PathType = FilePickerVM.PathTypeOptions.File;
+ vm.ExistCheckOption = FilePickerVM.CheckOptions.IfPathNotEmpty;
+ await Task.Delay(250);
+ Assert.IsFalse(vm.Exists);
+ Assert.IsTrue(vm.ErrorState.Succeeded);
+ Assert.IsFalse(vm.InError);
+ }
+
+ [TestMethod]
+ public async Task FileIfNotEmptyCheck_SetPath_DoesNotExist()
+ {
+ var vm = new FilePickerVM();
+ vm.PathType = FilePickerVM.PathTypeOptions.File;
+ vm.TargetPath = "SomePath.jpg";
+ vm.ExistCheckOption = FilePickerVM.CheckOptions.IfPathNotEmpty;
+ await Task.Delay(250);
+ Assert.IsFalse(vm.Exists);
+ Assert.IsFalse(vm.ErrorState.Succeeded);
+ Assert.IsTrue(vm.InError);
+ }
+
+ [TestMethod]
+ public async Task FileIfNotEmptyCheck_Exists()
+ {
+ var vm = new FilePickerVM();
+ using (CreateSetFile(vm))
+ {
+ vm.PathType = FilePickerVM.PathTypeOptions.File;
+ vm.ExistCheckOption = FilePickerVM.CheckOptions.IfPathNotEmpty;
+ await Task.Delay(250);
+ Assert.IsTrue(vm.Exists);
+ Assert.IsTrue(vm.ErrorState.Succeeded);
+ Assert.IsFalse(vm.InError);
+ }
+ }
+
+ [TestMethod]
+ public async Task FileOnExistsCheck_DoesNotExist()
+ {
+ var vm = new FilePickerVM();
+ vm.PathType = FilePickerVM.PathTypeOptions.File;
+ vm.ExistCheckOption = FilePickerVM.CheckOptions.On;
+ await Task.Delay(250);
+ Assert.IsFalse(vm.Exists);
+ Assert.IsFalse(vm.ErrorState.Succeeded);
+ Assert.IsTrue(vm.InError);
+ }
+
+ [TestMethod]
+ public async Task FileOnExistsCheck_Exists()
+ {
+ var vm = new FilePickerVM();
+ using (CreateSetFile(vm))
+ {
+ vm.PathType = FilePickerVM.PathTypeOptions.File;
+ vm.ExistCheckOption = FilePickerVM.CheckOptions.On;
+ await Task.Delay(250);
+ Assert.IsTrue(vm.Exists);
+ Assert.IsTrue(vm.ErrorState.Succeeded);
+ Assert.IsFalse(vm.InError);
+ }
+ }
+
+ [TestMethod]
+ public async Task FolderIfNotEmptyCheck_DoesNotExist()
+ {
+ var vm = new FilePickerVM();
+ vm.PathType = FilePickerVM.PathTypeOptions.Folder;
+ vm.ExistCheckOption = FilePickerVM.CheckOptions.IfPathNotEmpty;
+ await Task.Delay(250);
+ Assert.IsFalse(vm.Exists);
+ Assert.IsTrue(vm.ErrorState.Succeeded);
+ Assert.IsFalse(vm.InError);
+ }
+
+ [TestMethod]
+ public async Task FolderIfNotEmptyCheck_SetPath_DoesNotExist()
+ {
+ var vm = new FilePickerVM();
+ vm.PathType = FilePickerVM.PathTypeOptions.Folder;
+ vm.TargetPath = "SomePath.jpg";
+ vm.ExistCheckOption = FilePickerVM.CheckOptions.IfPathNotEmpty;
+ await Task.Delay(250);
+ Assert.IsFalse(vm.Exists);
+ Assert.IsFalse(vm.ErrorState.Succeeded);
+ Assert.IsTrue(vm.InError);
+ }
+
+ [TestMethod]
+ public async Task FolderIfNotEmptyCheck_Exists()
+ {
+ var vm = new FilePickerVM();
+ using (CreateSetFolder(vm))
+ {
+ vm.PathType = FilePickerVM.PathTypeOptions.Folder;
+ vm.ExistCheckOption = FilePickerVM.CheckOptions.IfPathNotEmpty;
+ await Task.Delay(250);
+ Assert.IsTrue(vm.Exists);
+ Assert.IsTrue(vm.ErrorState.Succeeded);
+ Assert.IsFalse(vm.InError);
+ }
+ }
+
+ [TestMethod]
+ public async Task FolderOnExistsCheck_DoesNotExist()
+ {
+ var vm = new FilePickerVM();
+ vm.PathType = FilePickerVM.PathTypeOptions.Folder;
+ vm.ExistCheckOption = FilePickerVM.CheckOptions.On;
+ await Task.Delay(250);
+ Assert.IsFalse(vm.Exists);
+ Assert.IsFalse(vm.ErrorState.Succeeded);
+ Assert.IsTrue(vm.InError);
+ }
+
+ [TestMethod]
+ public async Task FolderOnExistsCheck_Exists()
+ {
+ var vm = new FilePickerVM();
+ using (CreateSetFolder(vm))
+ {
+ vm.PathType = FilePickerVM.PathTypeOptions.Folder;
+ vm.ExistCheckOption = FilePickerVM.CheckOptions.On;
+ await Task.Delay(250);
+ Assert.IsTrue(vm.Exists);
+ Assert.IsTrue(vm.ErrorState.Succeeded);
+ Assert.IsFalse(vm.InError);
+ }
+ }
+
+ [TestMethod]
+ public async Task AdditionalError_Success()
+ {
+ var vm = new FilePickerVM();
+ vm.AdditionalError = Observable.Return(ErrorResponse.Succeed());
+ await Task.Delay(250);
+ Assert.IsTrue(vm.ErrorState.Succeeded);
+ Assert.IsFalse(vm.InError);
+ }
+
+ [TestMethod]
+ public async Task AdditionalError_Fail()
+ {
+ var vm = new FilePickerVM();
+ vm.AdditionalError = Observable.Return(ErrorResponse.Fail());
+ await Task.Delay(250);
+ Assert.IsFalse(vm.ErrorState.Succeeded);
+ Assert.IsTrue(vm.InError);
+ }
+
+ [TestMethod]
+ public async Task FileExistsButSetToFolder()
+ {
+ var vm = new FilePickerVM();
+ using (CreateSetFile(vm))
+ {
+ vm.PathType = FilePickerVM.PathTypeOptions.Folder;
+ vm.ExistCheckOption = FilePickerVM.CheckOptions.On;
+ await Task.Delay(250);
+ Assert.IsFalse(vm.Exists);
+ Assert.IsFalse(vm.ErrorState.Succeeded);
+ Assert.IsTrue(vm.InError);
+ }
+ }
+
+ [TestMethod]
+ public async Task FolderExistsButSetToFile()
+ {
+ var vm = new FilePickerVM();
+ using (CreateSetFolder(vm))
+ {
+ vm.PathType = FilePickerVM.PathTypeOptions.File;
+ vm.ExistCheckOption = FilePickerVM.CheckOptions.On;
+ await Task.Delay(250);
+ Assert.IsFalse(vm.Exists);
+ Assert.IsFalse(vm.ErrorState.Succeeded);
+ Assert.IsTrue(vm.InError);
+ }
+ }
+
+ [TestMethod]
+ public async Task FileWithFilters_Passes()
+ {
+ var vm = new FilePickerVM();
+ using (CreateSetFile(vm))
+ {
+ vm.PathType = FilePickerVM.PathTypeOptions.File;
+ vm.ExistCheckOption = FilePickerVM.CheckOptions.Off;
+ vm.Filters.Add(new Microsoft.WindowsAPICodePack.Dialogs.CommonFileDialogFilter("test", $"*.{Path.GetExtension(vm.TargetPath)}"));
+ await Task.Delay(250);
+ Assert.IsFalse(vm.Exists);
+ Assert.IsTrue(vm.ErrorState.Succeeded);
+ Assert.IsFalse(vm.InError);
+ }
+ }
+
+ [TestMethod]
+ public async Task FileWithFilters_ExistsButFails()
+ {
+ var vm = new FilePickerVM();
+ using (CreateSetFile(vm))
+ {
+ vm.PathType = FilePickerVM.PathTypeOptions.File;
+ vm.ExistCheckOption = FilePickerVM.CheckOptions.Off;
+ vm.Filters.Add(new Microsoft.WindowsAPICodePack.Dialogs.CommonFileDialogFilter("test", $"*.{Path.GetExtension(vm.TargetPath)}z"));
+ await Task.Delay(250);
+ Assert.IsFalse(vm.Exists);
+ Assert.IsFalse(vm.ErrorState.Succeeded);
+ Assert.IsTrue(vm.InError);
+ }
+ }
+
+ [TestMethod]
+ public async Task FileWithFilters_PassesButDoesntExist()
+ {
+ var vm = new FilePickerVM();
+ vm.PathType = FilePickerVM.PathTypeOptions.File;
+ vm.ExistCheckOption = FilePickerVM.CheckOptions.Off;
+ vm.TargetPath = "SomePath.png";
+ vm.Filters.Add(new Microsoft.WindowsAPICodePack.Dialogs.CommonFileDialogFilter("test", $"*.{Path.GetExtension(vm.TargetPath)}"));
+ await Task.Delay(250);
+ Assert.IsFalse(vm.Exists);
+ Assert.IsTrue(vm.ErrorState.Succeeded);
+ Assert.IsFalse(vm.InError);
+ }
+
+ [TestMethod]
+ public async Task FileWithFilters_IfNotEmptyCheck_DoesntExist()
+ {
+ var vm = new FilePickerVM();
+ vm.PathType = FilePickerVM.PathTypeOptions.File;
+ vm.ExistCheckOption = FilePickerVM.CheckOptions.Off;
+ vm.FilterCheckOption = FilePickerVM.CheckOptions.IfPathNotEmpty;
+ vm.Filters.Add(new Microsoft.WindowsAPICodePack.Dialogs.CommonFileDialogFilter("test", $"*.{Path.GetExtension(vm.TargetPath)}"));
+ await Task.Delay(250);
+ Assert.IsFalse(vm.Exists);
+ Assert.IsTrue(vm.ErrorState.Succeeded);
+ Assert.IsFalse(vm.InError);
+ }
+ }
+}
diff --git a/Wabbajack.Test/Wabbajack.Test.csproj b/Wabbajack.Test/Wabbajack.Test.csproj
index b44c382b..626500ab 100644
--- a/Wabbajack.Test/Wabbajack.Test.csproj
+++ b/Wabbajack.Test/Wabbajack.Test.csproj
@@ -105,6 +105,7 @@
+
diff --git a/Wabbajack/View Models/Compilers/CompilerVM.cs b/Wabbajack/View Models/Compilers/CompilerVM.cs
index 58397d41..80f89219 100644
--- a/Wabbajack/View Models/Compilers/CompilerVM.cs
+++ b/Wabbajack/View Models/Compilers/CompilerVM.cs
@@ -66,7 +66,7 @@ namespace Wabbajack
OutputLocation = new FilePickerVM()
{
- ExistCheckOption = FilePickerVM.ExistCheckOptions.IfNotEmpty,
+ ExistCheckOption = FilePickerVM.CheckOptions.IfPathNotEmpty,
PathType = FilePickerVM.PathTypeOptions.Folder,
PromptTitle = "Select the folder to place the resulting modlist.wabbajack file",
};
diff --git a/Wabbajack/View Models/Compilers/MO2CompilerVM.cs b/Wabbajack/View Models/Compilers/MO2CompilerVM.cs
index 28bb50f7..af77bd93 100644
--- a/Wabbajack/View Models/Compilers/MO2CompilerVM.cs
+++ b/Wabbajack/View Models/Compilers/MO2CompilerVM.cs
@@ -43,13 +43,13 @@ namespace Wabbajack
Parent = parent;
ModlistLocation = new FilePickerVM()
{
- ExistCheckOption = FilePickerVM.ExistCheckOptions.On,
+ ExistCheckOption = FilePickerVM.CheckOptions.On,
PathType = FilePickerVM.PathTypeOptions.File,
PromptTitle = "Select modlist"
};
DownloadLocation = new FilePickerVM()
{
- ExistCheckOption = FilePickerVM.ExistCheckOptions.On,
+ ExistCheckOption = FilePickerVM.CheckOptions.On,
PathType = FilePickerVM.PathTypeOptions.Folder,
PromptTitle = "Select download location",
};
@@ -91,13 +91,44 @@ namespace Wabbajack
return ErrorResponse.Fail($"MO2 Folder could not be located from the given modlist location.{Environment.NewLine}Make sure your modlist is inside a valid MO2 distribution.");
});
+ // Load custom modlist settings per MO2 profile
+ _modlistSettings = Observable.CombineLatest(
+ this.WhenAny(x => x.ModlistLocation.ErrorState),
+ this.WhenAny(x => x.ModlistLocation.TargetPath),
+ resultSelector: (state, path) => (State: state, Path: path))
+ // A short throttle is a quick hack to make the above changes "atomic"
+ .Throttle(TimeSpan.FromMilliseconds(25))
+ .Select(u =>
+ {
+ if (u.State.Failed) return null;
+ var modlistSettings = _settings.ModlistSettings.TryCreate(u.Path);
+ return new ModlistSettingsEditorVM(modlistSettings)
+ {
+ ModListName = MOProfile
+ };
+ })
+ // Interject and save old while loading new
+ .Pairwise()
+ .Do(pair =>
+ {
+ pair.Previous?.Save();
+ pair.Current?.Init();
+ })
+ .Select(x => x.Current)
+ // Save to property
+ .ObserveOnGuiThread()
+ .ToProperty(this, nameof(ModlistSettings));
+
// Wire start command
BeginCommand = ReactiveCommand.CreateFromTask(
canExecute: Observable.CombineLatest(
this.WhenAny(x => x.ModlistLocation.InError),
this.WhenAny(x => x.DownloadLocation.InError),
parent.WhenAny(x => x.OutputLocation.InError),
- resultSelector: (ml, down, output) => !ml && !down && !output)
+ this.WhenAny(x => x.ModlistSettings)
+ .Select(x => x?.InError ?? Observable.Return(false))
+ .Switch(),
+ resultSelector: (ml, down, output, modlistSettings) => !ml && !down && !output && !modlistSettings)
.ObserveOnGuiThread(),
execute: async () =>
{
@@ -161,34 +192,6 @@ namespace Wabbajack
.Subscribe(_ => Unload())
.DisposeWith(CompositeDisposable);
- // Load custom modlist settings per MO2 profile
- _modlistSettings = Observable.CombineLatest(
- this.WhenAny(x => x.ModlistLocation.ErrorState),
- this.WhenAny(x => x.ModlistLocation.TargetPath),
- resultSelector: (state, path) => (State: state, Path: path))
- // A short throttle is a quick hack to make the above changes "atomic"
- .Throttle(TimeSpan.FromMilliseconds(25))
- .Select(u =>
- {
- if (u.State.Failed) return null;
- var modlistSettings = _settings.ModlistSettings.TryCreate(u.Path);
- return new ModlistSettingsEditorVM(modlistSettings)
- {
- ModListName = MOProfile
- };
- })
- // Interject and save old while loading new
- .Pairwise()
- .Do(pair =>
- {
- pair.Previous?.Save();
- pair.Current?.Init();
- })
- .Select(x => x.Current)
- // Save to property
- .ObserveOnGuiThread()
- .ToProperty(this, nameof(ModlistSettings));
-
// If Mo2 folder changes and download location is empty, set it for convenience
this.WhenAny(x => x.Mo2Folder)
.DelayInitial(TimeSpan.FromMilliseconds(100))
diff --git a/Wabbajack/View Models/Compilers/ModlistSettingsEditorVM.cs b/Wabbajack/View Models/Compilers/ModlistSettingsEditorVM.cs
index b7cc89bc..426f7c07 100644
--- a/Wabbajack/View Models/Compilers/ModlistSettingsEditorVM.cs
+++ b/Wabbajack/View Models/Compilers/ModlistSettingsEditorVM.cs
@@ -1,4 +1,7 @@
-using Microsoft.WindowsAPICodePack.Dialogs;
+using System;
+using System.Reactive.Linq;
+using DynamicData;
+using Microsoft.WindowsAPICodePack.Dialogs;
using ReactiveUI.Fody.Helpers;
using Wabbajack.Lib;
@@ -24,27 +27,30 @@ namespace Wabbajack
[Reactive]
public string Website { get; set; }
+ public IObservable InError { get; }
+
public ModlistSettingsEditorVM(CompilationModlistSettings settings)
{
this._settings = settings;
ImagePath = new FilePickerVM()
{
- ExistCheckOption = FilePickerVM.ExistCheckOptions.IfNotEmpty,
+ ExistCheckOption = FilePickerVM.CheckOptions.IfPathNotEmpty,
PathType = FilePickerVM.PathTypeOptions.File,
- Filters =
- {
- new CommonFileDialogFilter("Banner image", "*.png")
- }
};
+ ImagePath.Filters.Add(new CommonFileDialogFilter("Banner image", "*.png"));
ReadMeText = new FilePickerVM()
{
PathType = FilePickerVM.PathTypeOptions.File,
- ExistCheckOption = FilePickerVM.ExistCheckOptions.IfNotEmpty,
- Filters =
- {
- new CommonFileDialogFilter("Text", "*.txt"),
- }
+ ExistCheckOption = FilePickerVM.CheckOptions.IfPathNotEmpty,
};
+ ReadMeText.Filters.Add(new CommonFileDialogFilter("Text", "*.txt"));
+
+ InError = Observable.CombineLatest(
+ this.WhenAny(x => x.ImagePath.ErrorState).Select(err => err.Failed),
+ this.WhenAny(x => x.ReadMeText.ErrorState).Select(err => err.Failed),
+ resultSelector: (img, readme) => img || readme)
+ .Publish()
+ .RefCount();
}
public void Init()
diff --git a/Wabbajack/View Models/Compilers/VortexCompilerVM.cs b/Wabbajack/View Models/Compilers/VortexCompilerVM.cs
index 4fba5251..fde9626e 100644
--- a/Wabbajack/View Models/Compilers/VortexCompilerVM.cs
+++ b/Wabbajack/View Models/Compilers/VortexCompilerVM.cs
@@ -59,30 +59,53 @@ namespace Wabbajack
Parent = parent;
GameLocation = new FilePickerVM()
{
- ExistCheckOption = FilePickerVM.ExistCheckOptions.On,
+ ExistCheckOption = FilePickerVM.CheckOptions.On,
PathType = FilePickerVM.PathTypeOptions.Folder,
PromptTitle = "Select Game Folder Location"
};
DownloadsLocation = new FilePickerVM()
{
- ExistCheckOption = FilePickerVM.ExistCheckOptions.On,
+ ExistCheckOption = FilePickerVM.CheckOptions.On,
PathType = FilePickerVM.PathTypeOptions.Folder,
PromptTitle = "Select Downloads Folder"
};
StagingLocation = new FilePickerVM()
{
- ExistCheckOption = FilePickerVM.ExistCheckOptions.On,
+ ExistCheckOption = FilePickerVM.CheckOptions.On,
PathType = FilePickerVM.PathTypeOptions.Folder,
PromptTitle = "Select Staging Folder"
};
+ // Load custom ModList settings when game type changes
+ _modListSettings = this.WhenAny(x => x.SelectedGame)
+ .Select(game =>
+ {
+ var gameSettings = _settings.ModlistSettings.TryCreate(game.Game);
+ return new ModlistSettingsEditorVM(gameSettings.ModlistSettings);
+ })
+ // Interject and save old while loading new
+ .Pairwise()
+ .Do(pair =>
+ {
+ var (previous, current) = pair;
+ previous?.Save();
+ current?.Init();
+ })
+ .Select(x => x.Current)
+ // Save to property
+ .ObserveOnGuiThread()
+ .ToProperty(this, nameof(ModlistSettings));
+
// Wire start command
BeginCommand = ReactiveCommand.CreateFromTask(
canExecute: Observable.CombineLatest(
this.WhenAny(x => x.GameLocation.InError),
this.WhenAny(x => x.DownloadsLocation.InError),
this.WhenAny(x => x.StagingLocation.InError),
- (g, d, s) => !g && !d && !s)
+ this.WhenAny(x => x.ModlistSettings)
+ .Select(x => x?.InError ?? Observable.Return(false))
+ .Switch(),
+ (g, d, s, ml) => !g && !d && !s && !ml)
.ObserveOnGuiThread(),
execute: async () =>
{
@@ -178,26 +201,6 @@ namespace Wabbajack
})
.DisposeWith(CompositeDisposable);
- // Load custom ModList settings when game type changes
- _modListSettings = this.WhenAny(x => x.SelectedGame)
- .Select(game =>
- {
- var gameSettings = _settings.ModlistSettings.TryCreate(game.Game);
- return new ModlistSettingsEditorVM(gameSettings.ModlistSettings);
- })
- // Interject and save old while loading new
- .Pairwise()
- .Do(pair =>
- {
- var (previous, current) = pair;
- previous?.Save();
- current?.Init();
- })
- .Select(x => x.Current)
- // Save to property
- .ObserveOnGuiThread()
- .ToProperty(this, nameof(ModlistSettings));
-
// Find game commands
FindGameInSteamCommand = ReactiveCommand.Create(SetGameToSteamLocation);
FindGameInGogCommand = ReactiveCommand.Create(SetGameToGogLocation);
diff --git a/Wabbajack/View Models/Installers/InstallerVM.cs b/Wabbajack/View Models/Installers/InstallerVM.cs
index 18a82818..c3e4361d 100644
--- a/Wabbajack/View Models/Installers/InstallerVM.cs
+++ b/Wabbajack/View Models/Installers/InstallerVM.cs
@@ -109,7 +109,7 @@ namespace Wabbajack
ModListLocation = new FilePickerVM()
{
- ExistCheckOption = FilePickerVM.ExistCheckOptions.On,
+ ExistCheckOption = FilePickerVM.CheckOptions.On,
PathType = FilePickerVM.PathTypeOptions.File,
PromptTitle = "Select a modlist to install"
};
diff --git a/Wabbajack/View Models/Installers/MO2InstallerVM.cs b/Wabbajack/View Models/Installers/MO2InstallerVM.cs
index a35af134..d3ba8292 100644
--- a/Wabbajack/View Models/Installers/MO2InstallerVM.cs
+++ b/Wabbajack/View Models/Installers/MO2InstallerVM.cs
@@ -41,7 +41,7 @@ namespace Wabbajack
Location = new FilePickerVM()
{
- ExistCheckOption = FilePickerVM.ExistCheckOptions.Off,
+ ExistCheckOption = FilePickerVM.CheckOptions.Off,
PathType = FilePickerVM.PathTypeOptions.Folder,
PromptTitle = "Select Installation Directory",
};
@@ -49,7 +49,7 @@ namespace Wabbajack
.Select(x => Utils.IsDirectoryPathValid(x));
DownloadLocation = new FilePickerVM()
{
- ExistCheckOption = FilePickerVM.ExistCheckOptions.Off,
+ ExistCheckOption = FilePickerVM.CheckOptions.Off,
PathType = FilePickerVM.PathTypeOptions.Folder,
PromptTitle = "Select a location for MO2 downloads",
};
diff --git a/Wabbajack/Wabbajack.csproj b/Wabbajack/Wabbajack.csproj
index 7b95922a..6d3b316b 100644
--- a/Wabbajack/Wabbajack.csproj
+++ b/Wabbajack/Wabbajack.csproj
@@ -231,19 +231,16 @@
DetailImageView.xaml
-
TopProgressView.xaml
-
CompilerView.xaml
-
FilePicker.xaml