wabbajack/Wabbajack/View Models/SlideShow.cs

298 lines
12 KiB
C#
Raw Normal View History

using ReactiveUI;
using Splat;
using System;
using System.Collections.Generic;
2019-10-14 01:15:41 +00:00
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.NexusApi;
2019-10-13 20:24:33 +00:00
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<Slide> SlideShowElements { get; set; }
public Dictionary<string, Slide> CachedSlides { get; }
public Queue<Slide> SlidesQueue { get; }
public AppState AppState { get; }
2019-10-22 01:08:26 +00:00
public BitmapImage NextIcon { get; } = UIUtils.BitmapImageFromResource("Wabbajack.Resources.Icons.next.png");
public BitmapImage WabbajackLogo { get; } = UIUtils.BitmapImageFromResource("Wabbajack.Resources.Banner_Dark.png");
private bool _ShowNSFW;
public bool ShowNSFW { get => _ShowNSFW; set => this.RaiseAndSetIfChanged(ref _ShowNSFW, value); }
private bool _GCAfterUpdating = true;
public bool GCAfterUpdating { get => _GCAfterUpdating; set => this.RaiseAndSetIfChanged(ref _GCAfterUpdating, value); }
2019-10-13 22:08:50 +00:00
private bool _Enable = true;
public bool Enable { get => _Enable; set => this.RaiseAndSetIfChanged(ref _Enable, value); }
2019-10-13 22:08:50 +00:00
private BitmapImage _Image;
public BitmapImage Image { get => _Image; set => this.RaiseAndSetIfChanged(ref _Image, value); }
2019-10-13 22:08:50 +00:00
private string _ModName = "Wabbajack";
public string ModName { get => _ModName; set => this.RaiseAndSetIfChanged(ref _ModName, value); }
2019-10-13 22:08:50 +00:00
private string _AuthorName = "Halgari & the Wabbajack Team";
public string AuthorName { get => _AuthorName; set => this.RaiseAndSetIfChanged(ref _AuthorName, value); }
2019-10-13 22:08:50 +00:00
private string _Summary;
public string Summary { get => _Summary; set => this.RaiseAndSetIfChanged(ref _Summary, value); }
2019-10-14 01:15:41 +00:00
private string _NexusSiteURL = "https://github.com/wabbajack-tools/wabbajack";
public string NexusSiteURL { get => _NexusSiteURL; set => this.RaiseAndSetIfChanged(ref _NexusSiteURL, value); }
public IReactiveCommand SlideShowNextItemCommand { get; } = ReactiveCommand.Create(() => { });
2019-10-14 01:15:41 +00:00
public IReactiveCommand VisitNexusSiteCommand { get; }
public SlideShow(AppState appState)
{
SlideShowElements = NexusApiClient.CachedSlideShow.ToList();
CachedSlides = new Dictionary<string, Slide>();
SlidesQueue = new Queue<Slide>();
_random = new Random();
AppState = appState;
2019-10-14 01:15:41 +00:00
this.VisitNexusSiteCommand = ReactiveCommand.Create(
execute: () => Process.Start(this.NexusSiteURL),
canExecute: this.WhenAny(x => x.NexusSiteURL)
.Select(x => x?.StartsWith("https://") ?? false)
.ObserveOnGuiThread());
// Apply modlist properties when it changes
this.WhenAny(x => x.AppState.ModList)
.NotNull()
.Subscribe(modList =>
{
2019-10-14 01:15:41 +00:00
this.NexusSiteURL = modList.Website;
2019-10-13 22:08:50 +00:00
this.ModName = modList.Name;
this.AuthorName = modList.Author;
this.Summary = modList.Description;
})
.DisposeWith(this.CompositeDisposable);
// Update splashscreen when modlist changes
Observable.CombineLatest(
2019-10-13 22:08:50 +00:00
(this).WhenAny(x => x.AppState.ModList),
(this).WhenAny(x => x.AppState.ModListPath),
(this).WhenAny(x => x.Enable),
(modList, modListPath, enabled) => (modList, modListPath, enabled))
// Do any potential unzipping on a background thread
.ObserveOn(RxApp.TaskpoolScheduler)
.Select(u =>
{
2019-10-13 22:08:50 +00:00
if (u.enabled
&& u.modList != null
&& u.modListPath != null
&& File.Exists(u.modListPath)
&& !string.IsNullOrEmpty(u.modList.Image)
&& u.modList.Image.Length == 36)
{
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 splash image.");
}
}
return this.WabbajackLogo;
})
.ObserveOn(RxApp.MainThreadScheduler)
.StartWith(this.WabbajackLogo)
2019-10-13 22:08:50 +00:00
.Subscribe(bitmap => this.Image = bitmap)
.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(),
// If user requests one manually
this.SlideShowNextItemCommand.StartingExecution())
// When enabled, fire an initial signal
.StartWith(Unit.Default)
// Only subscribe to slideshow triggers if enabled and installing
.FilterSwitch(
Observable.CombineLatest(
2019-10-13 22:08:50 +00:00
this.WhenAny(x => x.Enable),
this.WhenAny(x => x.AppState.Installing),
resultSelector: (enabled, installing) => enabled && 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];
2019-10-11 17:29:11 +00:00
while (!CachedSlides.ContainsKey(randomSlide.ModID) || SlidesQueue.Contains(randomSlide))
{
idx = _random.Next(0, SlideShowElements.Count);
randomSlide = SlideShowElements[idx];
}
2019-10-11 17:29:11 +00:00
//if (SlidesQueue.Contains(randomSlide)) continue;
CachedSlides.Remove(randomSlide.ModID);
if (this.GCAfterUpdating)
GC.Collect();
}
if (!slide.IsNSFW || (slide.IsNSFW && ShowNSFW))
{
2019-10-13 22:08:50 +00:00
this.Image = AppState._noneImage;
if (slide.ImageURL != null && slide.Image != null)
{
if (!CachedSlides.ContainsKey(slide.ModID)) return;
2019-10-13 22:08:50 +00:00
this.Image = slide.Image;
}
2019-10-13 22:08:50 +00:00
this.ModName = slide.ModName;
this.AuthorName = slide.ModAuthor;
this.Summary = slide.ModDescription;
2019-10-14 01:15:41 +00:00
this.NexusSiteURL = slide.ModURL;
}
SlidesQueue.Dequeue();
QueueRandomSlide(false, true);
}
private void CacheImage(Slide slide)
{
2019-10-12 19:15:19 +00:00
Utils.LogToFile($"Caching slide for {slide.ModName} at {slide.ImageURL}");
using (var ms = new MemoryStream())
{
2019-10-12 19:15:19 +00:00
try
{
2019-10-12 19:15:19 +00:00
if (UseSync)
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
2019-10-12 19:15:19 +00:00
{
using (var stream = new HttpClient().GetStreamSync(slide.ImageURL))
stream.CopyTo(ms);
});
}
else
{
using (Task<Stream> stream = new HttpClient().GetStreamAsync(slide.ImageURL))
{
stream.Wait();
stream.Result.CopyTo(ms);
}
}
ms.Seek(0, SeekOrigin.Begin);
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
2019-10-12 19:15:19 +00:00
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = ms;
image.EndInit();
image.Freeze();
slide.Image = image;
});
}
2019-10-12 19:15:19 +00:00
catch (Exception e)
{
2019-10-12 19:15:19 +00:00
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;
}
}
}