From 84d7eb0f49c8ddc5899c96e85242fb9eb302c29d Mon Sep 17 00:00:00 2001 From: Justin Swanson Date: Sun, 3 Nov 2019 00:01:19 -0600 Subject: [PATCH 1/2] Image cache and slideshow refactor --- Wabbajack.Lib/NexusApi/NexusApi.cs | 28 --- Wabbajack.Lib/UI/Slide.cs | 33 --- Wabbajack.Lib/Wabbajack.Lib.csproj | 1 - Wabbajack/Extensions/EnumerableExt.cs | 36 +++ Wabbajack/View Models/InstallerVM.cs | 79 ++----- Wabbajack/View Models/ModListVM.cs | 89 ++++++++ Wabbajack/View Models/ModVM.cs | 97 ++++++++ Wabbajack/View Models/SlideShow.cs | 309 ++++++++------------------ Wabbajack/Wabbajack.csproj | 3 + 9 files changed, 331 insertions(+), 344 deletions(-) delete mode 100644 Wabbajack.Lib/UI/Slide.cs create mode 100644 Wabbajack/Extensions/EnumerableExt.cs create mode 100644 Wabbajack/View Models/ModListVM.cs create mode 100644 Wabbajack/View Models/ModVM.cs diff --git a/Wabbajack.Lib/NexusApi/NexusApi.cs b/Wabbajack.Lib/NexusApi/NexusApi.cs index 5e90cd2e..d7e6cd4b 100644 --- a/Wabbajack.Lib/NexusApi/NexusApi.cs +++ b/Wabbajack.Lib/NexusApi/NexusApi.cs @@ -303,34 +303,6 @@ namespace Wabbajack.Lib.NexusApi } } - - public static IEnumerable CachedSlideShow - { - get - { - if (!Directory.Exists(Consts.NexusCacheDirectory)) return new Slide[] { }; - - return Directory.EnumerateFiles(Consts.NexusCacheDirectory) - .Where(f => f.EndsWith(".json")) - .Select(f => - { - try - { - return f.FromJSON(); - } - catch (Exception) - { - File.Delete(f); - return null; - } - }) - .Where(m => m != null) - .Where(m => m._internal_version == CACHED_VERSION_NUMBER && m.picture_url != null) - .Select(m => new Slide(m.name,m.mod_id,m.summary,m.author,m.contains_adult_content,GetModURL(m.game_name,m.mod_id),m.picture_url)); - } - } - - private class DownloadLink { public string URI { get; set; } diff --git a/Wabbajack.Lib/UI/Slide.cs b/Wabbajack.Lib/UI/Slide.cs deleted file mode 100644 index d4d0ddc7..00000000 --- a/Wabbajack.Lib/UI/Slide.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Media.Imaging; - -namespace Wabbajack.Lib -{ - public class Slide - { - public Slide(string modName, string modID, string modDescription, string modAuthor, bool isNSFW, string modUrl, string imageURL) - { - ModName = modName; - ModDescription = modDescription; - ModAuthor = modAuthor; - IsNSFW = isNSFW; - ModURL = modUrl; - ModID = modID; - ImageURL = imageURL; - } - - public string ModName { get; } - public string ModDescription { get; } - public string ModAuthor { get; } - public bool IsNSFW { get; } - public string ModURL { get; } - public string ModID { get; } - public BitmapImage Image { get; set; } - public string ImageURL { get; } - - } -} diff --git a/Wabbajack.Lib/Wabbajack.Lib.csproj b/Wabbajack.Lib/Wabbajack.Lib.csproj index 6b0795d4..ab091bec 100644 --- a/Wabbajack.Lib/Wabbajack.Lib.csproj +++ b/Wabbajack.Lib/Wabbajack.Lib.csproj @@ -103,7 +103,6 @@ - diff --git a/Wabbajack/Extensions/EnumerableExt.cs b/Wabbajack/Extensions/EnumerableExt.cs new file mode 100644 index 00000000..9f9397ee --- /dev/null +++ b/Wabbajack/Extensions/EnumerableExt.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Wabbajack +{ + public static class EnumerableExt + { + #region Shuffle + /// https://stackoverflow.com/questions/5807128/an-extension-method-on-ienumerable-needed-for-shuffling + + public static IEnumerable Shuffle(this IEnumerable source, Random rng) + { + if (source == null) throw new ArgumentNullException("source"); + if (rng == null) throw new ArgumentNullException("rng"); + + return source.ShuffleIterator(rng); + } + + private static IEnumerable ShuffleIterator( + this IEnumerable source, Random rng) + { + var buffer = source.ToList(); + for (int i = 0; i < buffer.Count; i++) + { + int j = rng.Next(i, buffer.Count); + yield return buffer[j]; + + buffer[j] = buffer[i]; + } + } + #endregion + } +} diff --git a/Wabbajack/View Models/InstallerVM.cs b/Wabbajack/View Models/InstallerVM.cs index 7da46ad6..e3134c8f 100644 --- a/Wabbajack/View Models/InstallerVM.cs +++ b/Wabbajack/View Models/InstallerVM.cs @@ -39,8 +39,8 @@ namespace Wabbajack public BitmapImage WabbajackLogo { get; } = UIUtils.BitmapImageFromResource("Wabbajack.Resources.Wabba_Mouth.png"); - private readonly ObservableAsPropertyHelper _ModList; - public ModList ModList => _ModList.Value; + private readonly ObservableAsPropertyHelper _ModList; + public ModListVM ModList => _ModList.Value; [Reactive] public string ModListPath { get; set; } @@ -109,9 +109,9 @@ namespace Wabbajack .ObserveOn(RxApp.TaskpoolScheduler) .Select(source => { - if (source == null) return default; - var modlist = Installer.LoadFromFile(source); - if (modlist == null) + if (source == null) return default(ModListVM); + var modList = Installer.LoadFromFile(source); + if (modList == null) { MessageBox.Show("Invalid Modlist, or file not found.", "Invalid Modlist", MessageBoxButton.OK, MessageBoxImage.Error); @@ -125,12 +125,12 @@ namespace Wabbajack window.Show(); this.MWVM.MainWindow.Close(); }); - return default; + return default(ModListVM); } - return modlist; + return new ModListVM(modList, source); }) .ObserveOnGuiThread() - .StartWith(default(ModList)) + .StartWith(default(ModListVM)) .ToProperty(this, nameof(this.ModList)); this._HTMLReport = this.WhenAny(x => x.ModList) .Select(modList => modList?.ReportHTML) @@ -147,57 +147,13 @@ namespace Wabbajack this.Slideshow = new SlideShow(this); - // Locate and create modlist image if it exists - var modListImage = Observable.CombineLatest( - this.WhenAny(x => x.ModList), - this.WhenAny(x => x.ModListPath), - (modList, modListPath) => (modList, modListPath)) - .ObserveOn(RxApp.TaskpoolScheduler) - .Select(u => - { - if (u.modList == null - || u.modListPath == null - || !File.Exists(u.modListPath) - || string.IsNullOrEmpty(u.modList.Image) - || u.modList.Image.Length != 36) - { - return WabbajackLogo; - } - try - { - using (var fs = new FileStream(u.modListPath, FileMode.Open, FileAccess.Read, FileShare.Read)) - using (var ar = new ZipArchive(fs, ZipArchiveMode.Read)) - using (var ms = new MemoryStream()) - { - var entry = ar.GetEntry(u.modList.Image); - using (var e = entry.Open()) - e.CopyTo(ms); - var image = new BitmapImage(); - image.BeginInit(); - image.CacheOption = BitmapCacheOption.OnLoad; - image.StreamSource = ms; - image.EndInit(); - image.Freeze(); - - return image; - } - } - catch (Exception ex) - { - this.Log().Warn(ex, "Error loading modlist splash image."); - return WabbajackLogo; - } - }) - .ObserveOnGuiThread() - .StartWith(default(BitmapImage)) - .Replay(1) - .RefCount(); - // Set display items to modlist if configuring or complete, // or to the current slideshow data if installing this._Image = Observable.CombineLatest( - modListImage - .StartWith(default(BitmapImage)), + this.WhenAny(x => x.ModList) + .SelectMany(x => x?.ImageObservable ?? Observable.Empty()) + .NotNull() + .StartWith(WabbajackLogo), this.WhenAny(x => x.Slideshow.Image) .StartWith(default(BitmapImage)), this.WhenAny(x => x.Installing), @@ -205,19 +161,22 @@ namespace Wabbajack .ToProperty(this, nameof(this.Image)); this._TitleText = Observable.CombineLatest( this.WhenAny(x => x.ModList.Name), - this.WhenAny(x => x.Slideshow.ModName), + this.WhenAny(x => x.Slideshow.TargetMod.ModName) + .StartWith(default(string)), this.WhenAny(x => x.Installing), resultSelector: (modList, mod, installing) => installing ? mod : modList) .ToProperty(this, nameof(this.TitleText)); this._AuthorText = Observable.CombineLatest( this.WhenAny(x => x.ModList.Author), - this.WhenAny(x => x.Slideshow.AuthorName), + this.WhenAny(x => x.Slideshow.TargetMod.ModAuthor) + .StartWith(default(string)), this.WhenAny(x => x.Installing), resultSelector: (modList, mod, installing) => installing ? mod : modList) .ToProperty(this, nameof(this.AuthorText)); this._Description = Observable.CombineLatest( this.WhenAny(x => x.ModList.Description), - this.WhenAny(x => x.Slideshow.Description), + this.WhenAny(x => x.Slideshow.TargetMod.ModDescription) + .StartWith(default(string)), this.WhenAny(x => x.Installing), resultSelector: (modList, mod, installing) => installing ? mod : modList) .ToProperty(this, nameof(this.Description)); @@ -289,7 +248,7 @@ namespace Wabbajack { this.Installing = true; this.InstallingMode = true; - var installer = new Installer(this.ModListPath, this.ModList, Location) + var installer = new Installer(this.ModListPath, this.ModList.SourceModList, Location) { DownloadFolder = DownloadLocation }; diff --git a/Wabbajack/View Models/ModListVM.cs b/Wabbajack/View Models/ModListVM.cs new file mode 100644 index 00000000..95768222 --- /dev/null +++ b/Wabbajack/View Models/ModListVM.cs @@ -0,0 +1,89 @@ +using ReactiveUI; +using ReactiveUI.Fody.Helpers; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Reactive.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; +using Wabbajack.Common; +using Wabbajack.Lib; + +namespace Wabbajack +{ + public class ModListVM : ViewModel + { + public ModList SourceModList { get; } + public string ModListPath { get; } + public string Name => this.SourceModList.Name; + public string ReportHTML => this.SourceModList.ReportHTML; + public string Readme => this.SourceModList.Readme; + public string ImageURL => this.SourceModList.Image; + public string Author => this.SourceModList.Author; + public string Description => this.SourceModList.Description; + public string Website => this.SourceModList.Website; + + // 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 ImageObservable { get; } + + public ModListVM(ModList sourceModList, string modListPath) + { + this.ModListPath = modListPath; + this.SourceModList = sourceModList; + + this.ImageObservable = Observable.Return(this.ImageURL) + .ObserveOn(RxApp.TaskpoolScheduler) + .Select(url => + { + try + { + if (!File.Exists(url)) return default(MemoryStream); + if (string.IsNullOrWhiteSpace(sourceModList.Image)) return default(MemoryStream); + if (sourceModList.Image.Length != 36) return default(MemoryStream); + using (var fs = new FileStream(this.ModListPath, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var ar = new ZipArchive(fs, ZipArchiveMode.Read)) + { + var ms = new MemoryStream(); + var entry = ar.GetEntry(sourceModList.Image); + using (var e = entry.Open()) + { + e.CopyTo(ms); + } + return ms; + } + } + catch (Exception ex) + { + Utils.LogToFile($"Exception while caching Mod List image {this.Name}\n{ex.ExceptionToString()}"); + return default(MemoryStream); + } + }) + .ObserveOn(RxApp.MainThreadScheduler) + .Select(memStream => + { + try + { + var image = new BitmapImage(); + image.BeginInit(); + image.CacheOption = BitmapCacheOption.OnLoad; + image.StreamSource = memStream; + image.EndInit(); + image.Freeze(); + return image; + } + catch (Exception ex) + { + Utils.LogToFile($"Exception while caching Mod List image {this.Name}\n{ex.ExceptionToString()}"); + return default(BitmapImage); + } + }) + .Replay(1) + .RefCount(); + } + } +} diff --git a/Wabbajack/View Models/ModVM.cs b/Wabbajack/View Models/ModVM.cs new file mode 100644 index 00000000..b8d10579 --- /dev/null +++ b/Wabbajack/View Models/ModVM.cs @@ -0,0 +1,97 @@ +using ReactiveUI; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Reactive.Linq; +using System.Text; +using System.Threading.Tasks; +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; } + + // 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 ImageObservable { get; } + + public ModVM(NexusDownloader.State m) + { + this.ModName = NexusApiUtils.FixupSummary(m.ModName); + this.ModID = m.ModID; + this.ModDescription = NexusApiUtils.FixupSummary(m.Summary); + this.ModAuthor = NexusApiUtils.FixupSummary(m.Author); + this.IsNSFW = m.Adult; + this.ModURL = m.NexusURL; + this.ImageURL = m.SlideShowPic; + this.ImageObservable = Observable.Return(this.ImageURL) + .ObserveOn(RxApp.TaskpoolScheduler) + .SelectTask(async url => + { + try + { + var ret = new MemoryStream(); + using (Stream stream = await new HttpClient().GetStreamAsync(url)) + { + stream.CopyTo(ret); + } + + ret.Seek(0, SeekOrigin.Begin); + return ret; + } + catch (Exception ex) + { + Utils.LogToFile($"Exception while caching slide {this.ModName} ({this.ModID})\n{ex.ExceptionToString()}"); + return default(MemoryStream); + } + }) + .ObserveOn(RxApp.MainThreadScheduler) + .Select(memStream => + { + if (memStream == null) return default(BitmapImage); + try + { + var image = new BitmapImage(); + image.BeginInit(); + image.CacheOption = BitmapCacheOption.OnLoad; + image.StreamSource = memStream; + image.EndInit(); + image.Freeze(); + return image; + } + catch (Exception ex) + { + Utils.LogToFile($"Exception while caching slide {this.ModName} ({this.ModID})\n{ex.ExceptionToString()}"); + return default(BitmapImage); + } + finally + { + memStream?.Dispose(); + } + }) + .Replay(1) + .RefCount(); + } + } +} diff --git a/Wabbajack/View Models/SlideShow.cs b/Wabbajack/View Models/SlideShow.cs index 1349c189..887a22a6 100644 --- a/Wabbajack/View Models/SlideShow.cs +++ b/Wabbajack/View Models/SlideShow.cs @@ -1,261 +1,126 @@ +using DynamicData; using ReactiveUI; using ReactiveUI.Fody.Helpers; -using Splat; using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.IO.Compression; using System.Linq; -using System.Net.Http; using System.Reactive; -using System.Reactive.Disposables; using System.Reactive.Linq; -using System.Threading.Tasks; using System.Windows.Media.Imaging; -using Wabbajack.Common; using Wabbajack.Lib; using Wabbajack.Lib.Downloaders; -using Wabbajack.Lib.NexusApi; namespace Wabbajack { public class SlideShow : ViewModel { - private readonly Random _random; - private Slide _lastSlide; - private const bool UseSync = false; - private const int MaxCacheSize = 10; - - public List SlideShowElements { get; set; } - - public Dictionary CachedSlides { get; } - - public Queue SlidesQueue { get; } + private readonly Random _random = new Random(); public InstallerVM Installer { get; } - public BitmapImage NextIcon { get; } = UIUtils.BitmapImageFromResource("Wabbajack.Resources.Icons.next.png"); - [Reactive] public bool ShowNSFW { get; set; } - [Reactive] - public bool GCAfterUpdating { get; set; } = true; - [Reactive] public bool Enable { get; set; } = true; - [Reactive] - public BitmapImage Image { get; set; } + private readonly ObservableAsPropertyHelper _Image; + public BitmapImage Image => _Image.Value; - [Reactive] - public string ModName { get; set; } = "Wabbajack"; - - [Reactive] - public string AuthorName { get; set; } = "Halgari & the Wabbajack Team"; - - [Reactive] - public string Description { get; set; } - - [Reactive] - public string NexusSiteURL { get; set; } = "https://github.com/wabbajack-tools/wabbajack"; + private readonly ObservableAsPropertyHelper _TargetMod; + public ModVM TargetMod => _TargetMod.Value; public IReactiveCommand SlideShowNextItemCommand { get; } = ReactiveCommand.Create(() => { }); public IReactiveCommand VisitNexusSiteCommand { get; } public SlideShow(InstallerVM appState) { - SlideShowElements = NexusApiClient.CachedSlideShow.ToList(); - CachedSlides = new Dictionary(); - SlidesQueue = new Queue(); - _random = new Random(); - Installer = appState; + this.Installer = appState; + + // Wire target slideshow index + var intervalSeconds = 10; + // Compile all the sources that trigger a slideshow update, any of which trigger a counter update + var selectedIndex = Observable.Merge( + // If user requests one manually + this.SlideShowNextItemCommand.StartingExecution(), + // If the natural timer fires + Observable.Merge( + // Start with an initial timer + Observable.Return(Observable.Interval(TimeSpan.FromSeconds(intervalSeconds))), + // but reset timer if user requests one + this.SlideShowNextItemCommand.StartingExecution() + .Select(_ => Observable.Interval(TimeSpan.FromSeconds(intervalSeconds)))) + // When a new timer comes in, swap to it + .Switch() + .Unit()) + // When filter switch enabled, fire an initial signal + .StartWith(Unit.Default) + // Only subscribe to slideshow triggers if enabled and installing + .FilterSwitch( + Observable.CombineLatest( + this.WhenAny(x => x.Enable), + this.WhenAny(x => x.Installer.Installing), + resultSelector: (enabled, installing) => enabled && installing)) + // Block spam + .Debounce(TimeSpan.FromMilliseconds(250)) + .Scan( + seed: 0, + accumulator: (i, _) => i + 1) + .Publish() + .RefCount(); + + // Dynamic list changeset of mod VMs to display + var modVMs = this.WhenAny(x => x.Installer.ModList) + // Whenever modlist changes, grab the list of its slides + .Select(modList => + { + if (modList == null) + { + return Observable.Empty() + .ToObservableChangeSet(x => x.ModID); + } + return modList.SourceModList.Archives + .Select(m => m.State) + .OfType() + .Select(nexus => new ModVM(nexus)) + // Shuffle it + .Shuffle(this._random) + .AsObservableChangeSet(x => x.ModID); + }) + // Switch to the new list after every modlist change + .Switch() + // Filter out any NSFW slides if we don't want them + .AutoRefreshOnObservable(slide => this.WhenAny(x => x.ShowNSFW)) + .Filter(slide => !slide.IsNSFW || this.ShowNSFW) + .RefCount(); + + // Find target mod to display by combining dynamic list with currently desired index + this._TargetMod = Observable.CombineLatest( + modVMs.QueryWhenChanged(), + selectedIndex, + resultSelector: (query, selected) => query.Items.ElementAtOrDefault(selected % query.Count)) + .StartWith(default(ModVM)) + .ObserveOn(RxApp.MainThreadScheduler) + .ToProperty(this, nameof(this.TargetMod)); + + // Mark interest and materialize image of target mod + this._Image = this.WhenAny(x => x.TargetMod) + // We want to Switch here, not SelectMany, as we want to hotswap to newest target without waiting on old ones + .Select(x => x?.ImageObservable ?? Observable.Return(default(BitmapImage))) + .Switch() + .ToProperty(this, nameof(this.Image)); this.VisitNexusSiteCommand = ReactiveCommand.Create( - execute: () => Process.Start(this.NexusSiteURL), - canExecute: this.WhenAny(x => x.NexusSiteURL) + execute: () => Process.Start(this.TargetMod.ModURL), + canExecute: this.WhenAny(x => x.TargetMod.ModURL) .Select(x => x?.StartsWith("https://") ?? false) .ObserveOnGuiThread()); - // Apply modlist properties when it changes - this.WhenAny(x => x.Installer.ModList) - .NotNull() - .ObserveOnGuiThread() - .Do(modList => - { - this.SlideShowElements = modList.Archives - .Select(m => m.State) - .OfType() - .Select(m => - new Slide(NexusApiUtils.FixupSummary(m.ModName), m.ModID, - NexusApiUtils.FixupSummary(m.Summary), NexusApiUtils.FixupSummary(m.Author), - m.Adult, m.NexusURL, m.SlideShowPic)).ToList(); - }) - .ObserveOn(RxApp.TaskpoolScheduler) - .Do(modList => - { - // This takes a while, and is currently blocking - this.PreloadSlideShow(); - }) - .Subscribe() - .DisposeWith(this.CompositeDisposable); - - /// Wire slideshow updates - // Merge all the sources that trigger a slideshow update - Observable.Merge( - // If the natural timer fires - Observable.Interval(TimeSpan.FromSeconds(10)) - .Unit() - // Only if enabled - .FilterSwitch(this.WhenAny(x => x.Enable)), - // If user requests one manually - this.SlideShowNextItemCommand.StartingExecution()) - // When installing fire an initial signal - .StartWith(Unit.Default) - // Only subscribe to slideshow triggers if installing - .FilterSwitch(this.WhenAny(x => x.Installer.Installing)) - // Don't ever update more than once every half second. ToDo: Update to debounce - .Throttle(TimeSpan.FromMilliseconds(500), RxApp.MainThreadScheduler) - .ObserveOn(RxApp.MainThreadScheduler) - .Subscribe(_ => this.UpdateSlideShowItem()) - .DisposeWith(this.CompositeDisposable); - } - - public void PreloadSlideShow() - { - var turns = 0; - for (var i = 0; i < SlideShowElements.Count; i++) - { - if (turns >= 3) - break; - - if (QueueRandomSlide(true, false)) - turns++; - } - } - - public void UpdateSlideShowItem() - { - if (SlidesQueue.Count == 0) return; - var slide = SlidesQueue.Peek(); - - while (CachedSlides.Count >= MaxCacheSize) - { - var idx = _random.Next(0, SlideShowElements.Count); - var randomSlide = SlideShowElements[idx]; - while (!CachedSlides.ContainsKey(randomSlide.ModID) || SlidesQueue.Contains(randomSlide)) - { - idx = _random.Next(0, SlideShowElements.Count); - randomSlide = SlideShowElements[idx]; - } - - //if (SlidesQueue.Contains(randomSlide)) continue; - CachedSlides.Remove(randomSlide.ModID); - if (this.GCAfterUpdating) - GC.Collect(); - } - - if (!slide.IsNSFW || (slide.IsNSFW && ShowNSFW)) - { - this.Image = UIUtils.BitmapImageFromResource("Wabbajack.Resources.none.jpg"); - if (slide.ImageURL != null && slide.Image != null) - { - if (!CachedSlides.ContainsKey(slide.ModID)) return; - this.Image = slide.Image; - } - - this.ModName = slide.ModName; - this.AuthorName = slide.ModAuthor; - this.Description = slide.ModDescription; - this.NexusSiteURL = slide.ModURL; - } - - SlidesQueue.Dequeue(); - QueueRandomSlide(false, true); - } - - private void CacheImage(Slide slide) - { - Utils.LogToFile($"Caching slide for {slide.ModName} at {slide.ImageURL}"); - using (var ms = new MemoryStream()) - { - try - { - if (UseSync) - { - System.Windows.Application.Current.Dispatcher.Invoke(() => - { - using (var stream = new HttpClient().GetStreamSync(slide.ImageURL)) - stream.CopyTo(ms); - }); - } - else - { - using (Task stream = new HttpClient().GetStreamAsync(slide.ImageURL)) - { - stream.Wait(); - stream.Result.CopyTo(ms); - } - } - - ms.Seek(0, SeekOrigin.Begin); - System.Windows.Application.Current.Dispatcher.Invoke(() => - { - var image = new BitmapImage(); - image.BeginInit(); - image.CacheOption = BitmapCacheOption.OnLoad; - image.StreamSource = ms; - image.EndInit(); - image.Freeze(); - - slide.Image = image; - }); - } - catch (Exception e) - { - Utils.LogToFile($"Exception while caching slide {slide.ModName} ({slide.ModID})\n{e.ExceptionToString()}"); - } - } - } - - private bool QueueRandomSlide(bool init, bool checkLast) - { - var result = false; - var idx = _random.Next(0, SlideShowElements.Count); - var element = SlideShowElements[idx]; - - if (checkLast && SlideShowElements.Count > 1) - { - while (element == _lastSlide && (!element.IsNSFW || (element.IsNSFW && ShowNSFW))) - { - idx = _random.Next(0, SlideShowElements.Count); - element = SlideShowElements[idx]; - } - } - - if (element.ImageURL == null) - { - if (!init) SlidesQueue.Enqueue(element); - } - else - { - if (!CachedSlides.ContainsKey(element.ModID)) - { - CacheImage(element); - CachedSlides.Add(element.ModID, element); - SlidesQueue.Enqueue(element); - result = true; - } - else - { - if(!init) SlidesQueue.Enqueue(element); - } - - _lastSlide = element; - } - - return result; + // ToDo + // Can maybe add "preload" systems to prep upcoming images + // This would entail subscribing to modVMs, narrowing it down to Top(X) somehow. + // The result would not be used anywhere, just simply expressing interest in those mods' + // images will implicitly cache them } } } \ No newline at end of file diff --git a/Wabbajack/Wabbajack.csproj b/Wabbajack/Wabbajack.csproj index 31a67fc2..39c9700d 100644 --- a/Wabbajack/Wabbajack.csproj +++ b/Wabbajack/Wabbajack.csproj @@ -162,6 +162,9 @@ Designer + + + CompilerView.xaml From a95a5415dce455294f129e988e6107eb1da677b5 Mon Sep 17 00:00:00 2001 From: Justin Swanson Date: Sun, 3 Nov 2019 00:41:11 -0600 Subject: [PATCH 2/2] Added Page() as a possible route for precaching slideshow --- Wabbajack/View Models/SlideShow.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Wabbajack/View Models/SlideShow.cs b/Wabbajack/View Models/SlideShow.cs index 887a22a6..1133edb0 100644 --- a/Wabbajack/View Models/SlideShow.cs +++ b/Wabbajack/View Models/SlideShow.cs @@ -118,9 +118,12 @@ namespace Wabbajack // ToDo // Can maybe add "preload" systems to prep upcoming images - // This would entail subscribing to modVMs, narrowing it down to Top(X) somehow. + // 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. } } } \ No newline at end of file