mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
commit
00f702f1f8
@ -111,13 +111,13 @@ namespace Wabbajack.CacheServer
|
||||
|
||||
public static void Start()
|
||||
{
|
||||
new Thread(() =>
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
ValidateLists().Wait();
|
||||
await ValidateLists();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -125,9 +125,9 @@ namespace Wabbajack.CacheServer
|
||||
}
|
||||
|
||||
// Sleep for two hours
|
||||
Thread.Sleep(1000 * 60 * 60 * 2);
|
||||
await Task.Delay(1000 * 60 * 60 * 2);
|
||||
}
|
||||
}).Start();
|
||||
}).FireAndForget();
|
||||
}
|
||||
public static async Task ValidateLists()
|
||||
{
|
||||
|
21
Wabbajack.Common/Util/AsyncLock.cs
Normal file
21
Wabbajack.Common/Util/AsyncLock.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Wabbajack.Common
|
||||
{
|
||||
public class AsyncLock
|
||||
{
|
||||
private SemaphoreSlim _lock = new SemaphoreSlim(1, 1);
|
||||
|
||||
public async Task<IDisposable> Wait()
|
||||
{
|
||||
await _lock.WaitAsync();
|
||||
return Disposable.Create(() => _lock.Release());
|
||||
}
|
||||
}
|
||||
}
|
@ -1011,6 +1011,11 @@ namespace Wabbajack.Common
|
||||
p.WaitForExitAndWarn(TimeSpan.FromSeconds(30), $"Deletion process of {path}");
|
||||
}
|
||||
|
||||
public static bool IsUnderneathDirectory(string path, string dirPath)
|
||||
{
|
||||
return path.StartsWith(dirPath, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a file to JSON but in an encrypted format in the user's app local directory.
|
||||
/// The data will be encrypted so that it can only be read by this machine and this user.
|
||||
|
@ -130,6 +130,7 @@
|
||||
<Compile Include="StatusUpdate.cs" />
|
||||
<Compile Include="SteamHandler.cs" />
|
||||
<Compile Include="Utils.cs" />
|
||||
<Compile Include="Util\AsyncLock.cs" />
|
||||
<Compile Include="Util\TempFile.cs" />
|
||||
<Compile Include="Util\TempFolder.cs" />
|
||||
<Compile Include="WorkQueue.cs" />
|
||||
|
@ -75,8 +75,9 @@ namespace Wabbajack.Lib.Downloaders
|
||||
|
||||
public async Task<bool> DoDownload(Archive a, string destination, bool download)
|
||||
{
|
||||
if (download && !Directory.Exists(Directory.GetParent(destination).FullName))
|
||||
Directory.CreateDirectory(Directory.GetParent(destination).FullName);
|
||||
var parent = Directory.GetParent(destination);
|
||||
if (download && !Directory.Exists(parent.FullName))
|
||||
Directory.CreateDirectory(parent.FullName);
|
||||
|
||||
using (var fs = download ? File.OpenWrite(destination) : null)
|
||||
{
|
||||
@ -120,12 +121,6 @@ namespace Wabbajack.Lib.Downloaders
|
||||
|
||||
var contentSize = headerVar != null ? long.Parse(headerVar) : 1;
|
||||
|
||||
FileInfo fileInfo = new FileInfo(destination);
|
||||
if (!fileInfo.Directory.Exists)
|
||||
{
|
||||
Directory.CreateDirectory(fileInfo.Directory.FullName);
|
||||
}
|
||||
|
||||
using (var webs = stream)
|
||||
{
|
||||
var buffer = new byte[bufferSize];
|
||||
|
@ -17,6 +17,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
private FileSystemWatcher _watcher;
|
||||
private Subject<FileEvent> _fileEvents = new Subject<FileEvent>();
|
||||
private KnownFolder _downloadfolder;
|
||||
public readonly AsyncLock Lock = new AsyncLock();
|
||||
|
||||
class FileEvent
|
||||
{
|
||||
@ -81,7 +82,7 @@ namespace Wabbajack.Lib.Downloaders
|
||||
{
|
||||
var downloader = (ManualDownloader)GetDownloader();
|
||||
var absPath = Path.Combine(downloader._downloadfolder.Path, a.Name);
|
||||
lock (downloader)
|
||||
using (await downloader.Lock.Wait())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -95,8 +96,8 @@ namespace Wabbajack.Lib.Downloaders
|
||||
.Select(x => x.FirstOrDefault())
|
||||
.FirstOrDefaultAsync();
|
||||
Process.Start(Url);
|
||||
|
||||
absPath = watcher.Wait()?.FullPath;
|
||||
|
||||
absPath = (await watcher)?.FullPath;
|
||||
if (!File.Exists(absPath))
|
||||
throw new InvalidDataException($"File not found after manual download operation");
|
||||
File.Move(absPath, destination);
|
||||
|
@ -319,7 +319,7 @@ namespace Wabbajack.Lib
|
||||
File.WriteAllText(Path.Combine(OutputFolder, directive.To), data);
|
||||
}
|
||||
|
||||
public static IErrorResponse CheckValidInstallPath(string path)
|
||||
public static IErrorResponse CheckValidInstallPath(string path, string downloadFolder)
|
||||
{
|
||||
var ret = Utils.IsDirectoryPathValid(path);
|
||||
if (!ret.Succeeded) return ret;
|
||||
@ -339,8 +339,8 @@ namespace Wabbajack.Lib
|
||||
// Check folder is either empty, or a likely valid previous install
|
||||
if (!Directory.IsEmpty(path))
|
||||
{
|
||||
// Some probably naive check, but should be a good starting point to improve later
|
||||
if (!Directory.EnumerateFiles(path).Any(file =>
|
||||
// If we have a MO2 install, assume good to go
|
||||
if (Directory.EnumerateFiles(path).Any(file =>
|
||||
{
|
||||
var fileName = Path.GetFileName(file);
|
||||
if (fileName.Equals("ModOrganizer.exe", StringComparison.OrdinalIgnoreCase)) return true;
|
||||
@ -348,7 +348,19 @@ namespace Wabbajack.Lib
|
||||
return false;
|
||||
}))
|
||||
{
|
||||
return ErrorResponse.Fail($"Cannot install into a non-empty folder that does not look like a previous WJ installation.");
|
||||
return ErrorResponse.Success;
|
||||
}
|
||||
|
||||
// If we don't have a MO2 install, and there's any file that's not in the downloads folder, mark failure
|
||||
if (Directory.EnumerateFiles(path).Any(file =>
|
||||
{
|
||||
var fileName = Path.GetFileName(file);
|
||||
if (string.IsNullOrWhiteSpace(downloadFolder)) return true;
|
||||
return !Utils.IsUnderneathDirectory(file, downloadFolder);
|
||||
}))
|
||||
{
|
||||
return ErrorResponse.Fail($"Cannot install into a non-empty folder that does not look like a previous WJ installation.\n" +
|
||||
$"To override, delete all installed files from your target installation folder. Any files in your download folder are okay to keep.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,12 +62,10 @@ namespace Wabbajack.Lib.NexusApi
|
||||
|
||||
public async Task<string> Username() => (await UserStatus).name;
|
||||
|
||||
private static SemaphoreSlim _getAPIKeyLock = new SemaphoreSlim(1, 1);
|
||||
private static AsyncLock _getAPIKeyLock = new AsyncLock();
|
||||
private static async Task<string> GetApiKey()
|
||||
{
|
||||
await _getAPIKeyLock.WaitAsync();
|
||||
|
||||
try
|
||||
using (await _getAPIKeyLock.Wait())
|
||||
{
|
||||
// Clean up old location
|
||||
if (File.Exists(API_KEY_CACHE_FILE))
|
||||
@ -91,10 +89,6 @@ namespace Wabbajack.Lib.NexusApi
|
||||
|
||||
return await RequestAndCacheAPIKey();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_getAPIKeyLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<string> RequestAndCacheAPIKey()
|
||||
|
@ -45,11 +45,11 @@ namespace Wabbajack.Test
|
||||
protected async Task<ModList> CompileAndInstall(string profile)
|
||||
{
|
||||
var compiler = await ConfigureAndRunCompiler(profile);
|
||||
Install(compiler);
|
||||
await Install(compiler);
|
||||
return compiler.ModList;
|
||||
}
|
||||
|
||||
protected void Install(MO2Compiler compiler)
|
||||
protected async Task Install(MO2Compiler compiler)
|
||||
{
|
||||
var modlist = AInstaller.LoadFromFile(compiler.ModListOutputFile);
|
||||
var installer = new MO2Installer(
|
||||
@ -59,7 +59,7 @@ namespace Wabbajack.Test
|
||||
downloadFolder: utils.DownloadsFolder);
|
||||
installer.WarnOnOverwrite = false;
|
||||
installer.GameFolder = utils.GameFolder;
|
||||
installer.Begin().Wait();
|
||||
await installer.Begin();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,11 +56,11 @@ namespace Wabbajack.Test
|
||||
protected async Task<ModList> CompileAndInstall()
|
||||
{
|
||||
var vortexCompiler = await ConfigureAndRunCompiler();
|
||||
Install(vortexCompiler);
|
||||
await Install(vortexCompiler);
|
||||
return vortexCompiler.ModList;
|
||||
}
|
||||
|
||||
protected void Install(VortexCompiler vortexCompiler)
|
||||
protected async Task Install(VortexCompiler vortexCompiler)
|
||||
{
|
||||
var modList = AInstaller.LoadFromFile(vortexCompiler.ModListOutputFile);
|
||||
var installer = new MO2Installer(
|
||||
@ -71,7 +71,7 @@ namespace Wabbajack.Test
|
||||
{
|
||||
GameFolder = utils.GameFolder,
|
||||
};
|
||||
installer.Begin().Wait();
|
||||
await installer.Begin();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
80
Wabbajack.Test/AsyncLockTests.cs
Normal file
80
Wabbajack.Test/AsyncLockTests.cs
Normal file
@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Wabbajack.Test
|
||||
{
|
||||
[TestClass]
|
||||
public class AsyncLockTests
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task Typical()
|
||||
{
|
||||
var asyncLock = new AsyncLock();
|
||||
bool firstRun = false;
|
||||
var first = Task.Run(async () =>
|
||||
{
|
||||
using (await asyncLock.Wait())
|
||||
{
|
||||
await Task.Delay(500);
|
||||
firstRun = true;
|
||||
}
|
||||
});
|
||||
var second = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(200);
|
||||
using (await asyncLock.Wait())
|
||||
{
|
||||
Assert.IsTrue(firstRun);
|
||||
}
|
||||
});
|
||||
await Task.WhenAll(first, second);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Exception()
|
||||
{
|
||||
var asyncLock = new AsyncLock();
|
||||
bool firstRun = false;
|
||||
bool secondRun = false;
|
||||
// Throw exception inside a lock
|
||||
await Assert.ThrowsExceptionAsync<Exception>(() =>
|
||||
{
|
||||
return Task.Run(async () =>
|
||||
{
|
||||
using (await asyncLock.Wait())
|
||||
{
|
||||
await Task.Delay(500);
|
||||
firstRun = true;
|
||||
throw new Exception();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await Task.WhenAll(
|
||||
// Try to re-enter lock afterwards
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(200);
|
||||
using (await asyncLock.Wait())
|
||||
{
|
||||
Assert.IsTrue(firstRun);
|
||||
secondRun = true;
|
||||
}
|
||||
}),
|
||||
// Add a timeout to fail if we cannot
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(500);
|
||||
if (!secondRun)
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
88
Wabbajack.Test/MO2Tests.cs
Normal file
88
Wabbajack.Test/MO2Tests.cs
Normal file
@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Lib;
|
||||
|
||||
namespace Wabbajack.Test
|
||||
{
|
||||
[TestClass]
|
||||
public class MO2Tests
|
||||
{
|
||||
#region CheckValidInstallPath
|
||||
[TestMethod]
|
||||
public void CheckValidInstallPath_Empty()
|
||||
{
|
||||
using (var tempDir = new TempFolder())
|
||||
{
|
||||
Assert.IsTrue(MO2Installer.CheckValidInstallPath(tempDir.Dir.FullName, downloadFolder: null).Succeeded);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CheckValidInstallPath_DoesNotExist()
|
||||
{
|
||||
using (var tempDir = new TempFolder())
|
||||
{
|
||||
Assert.IsTrue(MO2Installer.CheckValidInstallPath(Path.Combine(tempDir.Dir.FullName, "Subfolder"), downloadFolder: null).Succeeded);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CheckValidInstallPath_Invalid()
|
||||
{
|
||||
using (var tempDir = new TempFolder())
|
||||
{
|
||||
Assert.IsFalse(MO2Installer.CheckValidInstallPath($"{tempDir.Dir.FullName}/*", downloadFolder: null).Succeeded);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CheckValidInstallPath_HasModlist()
|
||||
{
|
||||
using (var tempDir = new TempFolder())
|
||||
{
|
||||
File.Create(Path.Combine(tempDir.Dir.FullName, $"ModOrganizer.exe"));
|
||||
File.Create(Path.Combine(tempDir.Dir.FullName, $"modlist{ExtensionManager.Extension}"));
|
||||
Assert.IsFalse(MO2Installer.CheckValidInstallPath(tempDir.Dir.FullName, downloadFolder: null).Succeeded);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CheckValidInstallPath_ProperOverwrite()
|
||||
{
|
||||
using (var tempDir = new TempFolder())
|
||||
{
|
||||
File.Create(Path.Combine(tempDir.Dir.FullName, $"ModOrganizer.exe"));
|
||||
Assert.IsTrue(MO2Installer.CheckValidInstallPath(tempDir.Dir.FullName, downloadFolder: null).Succeeded);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CheckValidInstallPath_ImproperOverwrite()
|
||||
{
|
||||
using (var tempDir = new TempFolder())
|
||||
{
|
||||
File.Create(Path.Combine(tempDir.Dir.FullName, $"someFile.txt"));
|
||||
Assert.IsFalse(MO2Installer.CheckValidInstallPath(tempDir.Dir.FullName, downloadFolder: null).Succeeded);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CheckValidInstallPath_OverwriteFilesInDownloads()
|
||||
{
|
||||
using (var tempDir = new TempFolder())
|
||||
{
|
||||
var downloadsFolder = Path.Combine(tempDir.Dir.FullName, "downloads");
|
||||
Directory.CreateDirectory(downloadsFolder);
|
||||
File.Create(Path.Combine(tempDir.Dir.FullName, $"downloads/someFile.txt"));
|
||||
Assert.IsFalse(MO2Installer.CheckValidInstallPath(tempDir.Dir.FullName, downloadFolder: downloadsFolder).Succeeded);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -99,6 +99,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ACompilerTest.cs" />
|
||||
<Compile Include="AsyncLockTests.cs" />
|
||||
<Compile Include="AVortexCompilerTest.cs" />
|
||||
<Compile Include="CSP\ChannelTests.cs" />
|
||||
<Compile Include="CSP\CSPTests.cs" />
|
||||
@ -107,6 +108,7 @@
|
||||
<Compile Include="Extensions.cs" />
|
||||
<Compile Include="FilePickerTests.cs" />
|
||||
<Compile Include="MiscTests.cs" />
|
||||
<Compile Include="MO2Tests.cs" />
|
||||
<Compile Include="ModlistMetadataTests.cs" />
|
||||
<Compile Include="RestartingDownloadsTests.cs" />
|
||||
<Compile Include="SimpleHTTPServer.cs" />
|
||||
|
@ -41,7 +41,8 @@
|
||||
<Color x:Key="Primary">#BB86FC</Color>
|
||||
<Color x:Key="PrimaryTransparent">#00BB86FC</Color>
|
||||
<Color x:Key="PrimaryVariant">#3700B3</Color>
|
||||
<Color x:Key="DarkPrimaryVariant">#1b0059</Color>
|
||||
<Color x:Key="DarkPrimaryVariant">#270080</Color>
|
||||
<Color x:Key="DarkerPrimaryVariant">#1b0059</Color>
|
||||
<Color x:Key="Secondary">#03DAC6</Color>
|
||||
<Color x:Key="DarkSecondary">#0e8f83</Color>
|
||||
<Color x:Key="DarkerSecondary">#095952</Color>
|
||||
@ -117,6 +118,7 @@
|
||||
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource Primary}" />
|
||||
<SolidColorBrush x:Key="PrimaryVariantBrush" Color="{StaticResource PrimaryVariant}" />
|
||||
<SolidColorBrush x:Key="DarkPrimaryVariantBrush" Color="{StaticResource DarkPrimaryVariant}" />
|
||||
<SolidColorBrush x:Key="DarkerPrimaryVariantBrush" Color="{StaticResource DarkerPrimaryVariant}" />
|
||||
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource Secondary}" />
|
||||
<SolidColorBrush x:Key="DarkSecondaryBrush" Color="{StaticResource DarkSecondary}" />
|
||||
<SolidColorBrush x:Key="DarkerSecondaryBrush" Color="{StaticResource DarkerSecondary}" />
|
||||
|
@ -47,8 +47,6 @@ namespace Wabbajack
|
||||
PathType = FilePickerVM.PathTypeOptions.Folder,
|
||||
PromptTitle = "Select Installation Directory",
|
||||
};
|
||||
Location.AdditionalError = this.WhenAny(x => x.Location.TargetPath)
|
||||
.Select(x => MO2Installer.CheckValidInstallPath(x));
|
||||
DownloadLocation = new FilePickerVM()
|
||||
{
|
||||
ExistCheckOption = FilePickerVM.CheckOptions.Off,
|
||||
@ -57,6 +55,13 @@ namespace Wabbajack
|
||||
};
|
||||
DownloadLocation.AdditionalError = this.WhenAny(x => x.DownloadLocation.TargetPath)
|
||||
.Select(x => Utils.IsDirectoryPathValid(x));
|
||||
Location.AdditionalError = Observable.CombineLatest(
|
||||
this.WhenAny(x => x.Location.TargetPath),
|
||||
this.WhenAny(x => x.DownloadLocation.TargetPath),
|
||||
resultSelector: (target, download) =>
|
||||
{
|
||||
return MO2Installer.CheckValidInstallPath(target, download);
|
||||
});
|
||||
|
||||
CanInstall = Observable.CombineLatest(
|
||||
this.WhenAny(x => x.Location.InError),
|
||||
@ -83,7 +88,7 @@ namespace Wabbajack
|
||||
_CurrentSettings = installerVM.WhenAny(x => x.ModListLocation.TargetPath)
|
||||
.Select(path => path == null ? null : installerVM.MWVM.Settings.Installer.Mo2ModlistSettings.TryCreate(path))
|
||||
.ToProperty(this, nameof(CurrentSettings));
|
||||
(this).WhenAny(x => x.CurrentSettings)
|
||||
this.WhenAny(x => x.CurrentSettings)
|
||||
.Pairwise()
|
||||
.Subscribe(settingsPair =>
|
||||
{
|
||||
|
@ -49,7 +49,8 @@ namespace Wabbajack
|
||||
try
|
||||
{
|
||||
var ret = new MemoryStream();
|
||||
using (Stream stream = await new HttpClient().GetStreamAsync(url))
|
||||
using (var client = new HttpClient())
|
||||
using (Stream stream = await client.GetStreamAsync(url))
|
||||
{
|
||||
stream.CopyTo(ret);
|
||||
}
|
||||
@ -82,7 +83,7 @@ namespace Wabbajack
|
||||
}
|
||||
})
|
||||
.Replay(1)
|
||||
.RefCount();
|
||||
.RefCount(TimeSpan.FromMilliseconds(5000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,10 @@ using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Lib;
|
||||
using Wabbajack.Lib.Downloaders;
|
||||
|
||||
@ -33,6 +35,8 @@ namespace Wabbajack
|
||||
public IReactiveCommand SlideShowNextItemCommand { get; } = ReactiveCommand.Create(() => { });
|
||||
public IReactiveCommand VisitNexusSiteCommand { get; }
|
||||
|
||||
public const int PreloadAmount = 4;
|
||||
|
||||
public SlideShow(InstallerVM appState)
|
||||
{
|
||||
Installer = appState;
|
||||
@ -98,7 +102,11 @@ namespace Wabbajack
|
||||
_targetMod = Observable.CombineLatest(
|
||||
modVMs.QueryWhenChanged(),
|
||||
selectedIndex,
|
||||
resultSelector: (query, selected) => query.Items.ElementAtOrDefault(selected % (query.Count == 0 ? 1 : query.Count)))
|
||||
resultSelector: (query, selected) =>
|
||||
{
|
||||
var index = selected % (query.Count == 0 ? 1 : query.Count);
|
||||
return query.Items.ElementAtOrDefault(index);
|
||||
})
|
||||
.StartWith(default(ModVM))
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.ToProperty(this, nameof(TargetMod));
|
||||
@ -116,14 +124,22 @@ namespace Wabbajack
|
||||
.Select(x => x?.StartsWith("https://") ?? false)
|
||||
.ObserveOnGuiThread());
|
||||
|
||||
// ToDo
|
||||
// Can maybe add "preload" systems to prep upcoming images
|
||||
// This would entail subscribing to modVMs, narrowing it down to Top(X) or Page() somehow.
|
||||
// The result would not be used anywhere, just simply expressing interest in those mods'
|
||||
// images will implicitly cache them
|
||||
//
|
||||
// Page would be really clever to use, but it's not exactly right as its "window" won't follow the current index,
|
||||
// so at the boundary of a page, the next image won't be cached. Need like a Page() /w an offset parameter, or something.
|
||||
// Preload upcoming images
|
||||
var list = Observable.CombineLatest(
|
||||
modVMs.QueryWhenChanged(),
|
||||
selectedIndex,
|
||||
resultSelector: (query, selected) =>
|
||||
{
|
||||
// Retrieve the mods that should be preloaded
|
||||
var index = selected % (query.Count == 0 ? 1 : query.Count);
|
||||
var amountToTake = Math.Min(query.Count - index, PreloadAmount);
|
||||
return query.Items.Skip(index).Take(amountToTake).ToObservable();
|
||||
})
|
||||
.Select(i => i.ToObservableChangeSet())
|
||||
.Switch()
|
||||
.Transform(mod => mod.ImageObservable.Subscribe())
|
||||
.DisposeMany()
|
||||
.AsObservableList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,32 +12,35 @@
|
||||
<Grid>
|
||||
<Rectangle Fill="{StaticResource HeatedBorderBrush}" Opacity="{Binding ProgressPercent, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
|
||||
<ListBox
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="1"
|
||||
ItemsSource="{Binding StatusList}">
|
||||
ItemsSource="{Binding StatusList}"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="500" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid Background="{StaticResource WindowBackgroundBrush}">
|
||||
<mahapps:MetroProgressBar
|
||||
Grid.Column="0"
|
||||
Background="{StaticResource WindowBackgroundBrush}"
|
||||
BorderThickness="0"
|
||||
Foreground="Transparent"
|
||||
Maximum="1"
|
||||
Value="{Binding Status.ProgressPercent, Mode=OneWay}" />
|
||||
<mahapps:MetroProgressBar
|
||||
Grid.Column="0"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Foreground="{StaticResource PrimaryVariantBrush}"
|
||||
Foreground="{StaticResource DarkPrimaryVariantBrush}"
|
||||
Maximum="1"
|
||||
Opacity="{Binding Status.ProgressPercent, Mode=OneWay}"
|
||||
Value="{Binding Status.ProgressPercent, Mode=OneWay}" />
|
||||
<TextBlock Grid.Column="0" Text="{Binding Status.Msg}" />
|
||||
<Grid Height="1" VerticalAlignment="Bottom">
|
||||
<mahapps:MetroProgressBar
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Foreground="{StaticResource DarkSecondaryBrush}"
|
||||
Maximum="1"
|
||||
Value="{Binding Status.ProgressPercent, Mode=OneWay}" />
|
||||
</Grid>
|
||||
<TextBlock
|
||||
Margin="0,0,0,2"
|
||||
Text="{Binding Status.Msg}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap"
|
||||
ToolTip="{Binding Status.Msg}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<local:UserControlRx
|
||||
<local:UserControlRx
|
||||
x:Class="Wabbajack.DetailImageView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
@ -88,8 +88,8 @@
|
||||
x:Name="TitleTextShadow"
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Margin="-20,15,40,-10"
|
||||
Padding="40,10"
|
||||
Margin="-20,5,-20,-20"
|
||||
Padding="40,20"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Bottom"
|
||||
FontFamily="Lucida Sans"
|
||||
|
@ -72,7 +72,7 @@
|
||||
Value="{Binding ProgressPercent, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}">
|
||||
<mahapps:MetroProgressBar.Foreground>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
|
||||
<GradientStop Offset="0" Color="{StaticResource DarkPrimaryVariant}" />
|
||||
<GradientStop Offset="0" Color="{StaticResource DarkerPrimaryVariant}" />
|
||||
<GradientStop Offset="0.5" Color="{StaticResource PrimaryVariant}" />
|
||||
</LinearGradientBrush>
|
||||
</mahapps:MetroProgressBar.Foreground>
|
||||
|
@ -3,6 +3,7 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:icon="http://metro.mahapps.com/winfx/xaml/iconpacks"
|
||||
xmlns:local="clr-namespace:Wabbajack"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
d:DesignHeight="250"
|
||||
@ -11,7 +12,7 @@
|
||||
<UserControl.Resources>
|
||||
<Color x:Key="YellowColor">#fec701</Color>
|
||||
<SolidColorBrush x:Key="YellowBrush" Color="{StaticResource YellowColor}" />
|
||||
<Color x:Key="TransparentColor">#99000000</Color>
|
||||
<Color x:Key="TransparentColor">#AA000000</Color>
|
||||
<SolidColorBrush x:Key="TransparentBrush" Color="{StaticResource TransparentColor}" />
|
||||
<LinearGradientBrush x:Key="LinesBrush" MappingMode="Absolute" SpreadMethod="Repeat" StartPoint="0,0" EndPoint="48,36">
|
||||
<GradientStop Offset="0" Color="Black" />
|
||||
@ -36,10 +37,36 @@
|
||||
ShadowDepth="10" />
|
||||
</Rectangle.Effect>
|
||||
</Rectangle>
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Margin="30"
|
||||
Visibility="{Binding ShowHelp, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Converter={StaticResource bool2VisibilityConverter}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Margin="0,0,0,10"
|
||||
FontFamily="Lucida Sans"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
Foreground="{StaticResource YellowBrush}"
|
||||
Text="UNDER MAINTENANCE" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
FontFamily="Lucida Sans"
|
||||
FontSize="15"
|
||||
FontWeight="Bold"
|
||||
Foreground="{StaticResource YellowBrush}"
|
||||
Text="When mods are updated and the old versions are taken down, Wabbajack can no longer install the modlist for you. 
 
Modlist authors need to download the new mod and confirm its correctness before the modlist can be made available again."
|
||||
TextWrapping="WrapWithOverflow" />
|
||||
</Grid>
|
||||
<Viewbox
|
||||
Grid.Row="1"
|
||||
Margin="20"
|
||||
VerticalAlignment="Top">
|
||||
VerticalAlignment="Top"
|
||||
Visibility="{Binding ShowHelp, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Converter={StaticResource bool2VisibilityConverter}, ConverterParameter=False}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
@ -51,7 +78,6 @@
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
FontFamily="Lucida Sans"
|
||||
FontSize="100"
|
||||
FontWeight="Bold"
|
||||
@ -64,9 +90,26 @@
|
||||
ShadowDepth="10" />
|
||||
</TextBlock.Effect>
|
||||
</TextBlock>
|
||||
<Button
|
||||
Grid.Row="0"
|
||||
Width="45"
|
||||
Height="45"
|
||||
Margin="0,10"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Click="Help_Click">
|
||||
<Button.Style>
|
||||
<Style BasedOn="{StaticResource IconBareButtonStyle}" TargetType="Button">
|
||||
<Setter Property="Foreground" Value="{StaticResource YellowBrush}" />
|
||||
</Style>
|
||||
</Button.Style>
|
||||
<icon:PackIconMaterial
|
||||
Width="35"
|
||||
Height="35"
|
||||
Kind="HelpCircle" />
|
||||
</Button>
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
FontFamily="Lucida Sans"
|
||||
FontSize="100"
|
||||
FontWeight="Bold"
|
@ -20,9 +20,22 @@ namespace Wabbajack
|
||||
/// </summary>
|
||||
public partial class UnderMaintenanceOverlay : UserControl
|
||||
{
|
||||
public bool ShowHelp
|
||||
{
|
||||
get => (bool)GetValue(ShowHelpProperty);
|
||||
set => SetValue(ShowHelpProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty ShowHelpProperty = DependencyProperty.Register(nameof(ShowHelp), typeof(bool), typeof(UnderMaintenanceOverlay),
|
||||
new FrameworkPropertyMetadata(default(bool)));
|
||||
|
||||
public UnderMaintenanceOverlay()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void Help_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ShowHelp = !ShowHelp;
|
||||
}
|
||||
}
|
||||
}
|
@ -176,7 +176,7 @@
|
||||
<DependentUpon>AttentionBorder.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Converters\IsTypeVisibilityConverter.cs" />
|
||||
<Compile Include="UnderMaintenanceOverlay.xaml.cs">
|
||||
<Compile Include="Views\Common\UnderMaintenanceOverlay.xaml.cs">
|
||||
<DependentUpon>UnderMaintenanceOverlay.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UserInterventions\ShowLoginManager.cs" />
|
||||
@ -286,7 +286,7 @@
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="UnderMaintenanceOverlay.xaml">
|
||||
<Page Include="Views\Common\UnderMaintenanceOverlay.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
|
Loading…
Reference in New Issue
Block a user