From fb2408010d271e74fe32754185e80971e7321e53 Mon Sep 17 00:00:00 2001 From: mtkennerly Date: Fri, 7 Jun 2024 22:44:32 -0400 Subject: [PATCH] Reorganize and separate code --- src/Api.cs | 27 +- src/Cli.cs | 545 +++++++++++++++++ src/Etc.cs | 48 ++ src/Interactor.cs | 183 ++++++ src/LudusaviPlaynite.cs | 731 +++++------------------ src/LudusaviPlayniteSettings.cs | 100 +++- src/LudusaviPlayniteSettingsView.xaml.cs | 2 +- src/Manifest.cs | 24 + src/Models.cs | 383 +----------- src/Translator.cs | 2 +- 10 files changed, 1086 insertions(+), 959 deletions(-) create mode 100644 src/Cli.cs create mode 100644 src/Interactor.cs create mode 100644 src/Manifest.cs diff --git a/src/Api.cs b/src/Api.cs index 6b7fc4f..8a07bc0 100644 --- a/src/Api.cs +++ b/src/Api.cs @@ -4,11 +4,12 @@ using Playnite.SDK; using Playnite.SDK.Models; using System.Linq; -using System.Diagnostics; -using System.Text; namespace LudusaviPlaynite.Api { + /// + /// Used for running Ludusavi's `api` command. + /// public class Runner { ILogger logger; @@ -73,20 +74,30 @@ public Runner(ILogger logger, LudusaviPlayniteSettings settings) } } - public void FindTitle(Game game, string name, bool hasAltTitle) + public void FindTitle(Game game) { - var names = new List { name }; + var names = new List { settings.GetGameNameWithAlt(game) }; + int? steamId = null; + var normalized = false; - if (!Etc.IsOnPc(game) && settings.RetryNonPcGamesWithoutSuffix) + if (settings.AlternativeTitle(game) == null) { - names.Add(game.Name); + // The Steam ID would take priority over an alt title. + + steamId = Etc.SteamId(game); + normalized = settings.RetryUnrecognizedGameWithNormalization; + + if (!Etc.IsOnPc(game) && settings.RetryNonPcGamesWithoutSuffix) + { + names.Add(game.Name); + } } var inner = new Requests.FindTitle { names = names, - steamId = Etc.SteamId(game), - normalized = this.settings.RetryUnrecognizedGameWithNormalization, + steamId = steamId, + normalized = normalized, }; var request = new Request diff --git a/src/Cli.cs b/src/Cli.cs new file mode 100644 index 0000000..f74c3fa --- /dev/null +++ b/src/Cli.cs @@ -0,0 +1,545 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using Playnite.SDK; +using Playnite.SDK.Models; +using System.Linq; +using System.Diagnostics; + +namespace LudusaviPlaynite.Cli +{ + /// + /// Manages Ludusavi CLI state and invocations. + /// + public class App + { + private ILogger logger { get; set; } + private LudusaviPlayniteSettings settings { get; set; } + + public LudusaviVersion version { get; private set; } = new LudusaviVersion(new Version(0, 0, 0)); + public Dictionary titles { get; private set; } = new Dictionary(); + public Dictionary> backups { get; private set; } = new Dictionary>(); + public Dictionary backupPaths { get; private set; } = new Dictionary(); + public List manifestGames { get; private set; } = new List(); + public List manifestGamesWithSaveDataByTitle { get; private set; } = new List(); + public List manifestGamesWithSaveDataBySteamId { get; private set; } = new List(); + + public App(ILogger logger, LudusaviPlayniteSettings settings) + { + this.logger = logger; + this.settings = settings; + } + + public void Launch() + { + var p = new Process(); + p.StartInfo.UseShellExecute = false; + p.StartInfo.FileName = settings.ExecutablePath.Trim(); + p.Start(); + } + + private (int, string) InvokeDirect(Invocation invocation, bool standalone = false) + { + var fullArgs = invocation.Render(settings, version); + logger.Debug(string.Format("Running Ludusavi: {0}", fullArgs)); + + try + { + var (code, stdout) = Etc.RunCommand(settings.ExecutablePath.Trim(), fullArgs); + if (standalone) + { + logger.Debug(string.Format("Ludusavi exited with {0}", code)); + } + return (code, stdout); + } + catch (Exception e) + { + logger.Debug(e, "Ludusavi could not be executed"); + return (-1, null); + } + } + + public (int, Output.Response?) Invoke(Invocation invocation) + { + var (code, stdout) = InvokeDirect(invocation); + + Output.Response? response; + try + { + response = JsonConvert.DeserializeObject(stdout); + logger.Debug(string.Format("Ludusavi exited with {0} and valid JSON content", code)); + } + catch (Exception e) + { + response = null; + logger.Debug(e, string.Format("Ludusavi exited with {0} and invalid JSON content", code)); + } + + return (code, response); + } + + public Version GetVersion() + { + int code; + string stdout; + try + { + (code, stdout) = Etc.RunCommand(settings.ExecutablePath.Trim(), "--version"); + var version = stdout.Trim().Split(' ').Last(); + return new Version(version); + } + catch (Exception e) + { + logger.Debug(e, "Could not determine Ludusavi version"); + return new Version(0, 0, 0); + } + } + + public void RefreshVersion() + { + this.version = new LudusaviVersion(GetVersion()); + } + + public bool RefreshBackups() + { + if (!(this.version.supportsMultiBackup())) + { + return false; + } + + var (code, response) = Invoke(new Invocation(Mode.Backups).PathIf(settings.BackupPath, settings.OverrideBackupPath)); + if (response?.Games != null) + { + foreach (var pair in response?.Games) + { + this.backups[pair.Key] = pair.Value.Backups; + if (!string.IsNullOrEmpty(pair.Value.BackupPath)) + { + this.backupPaths[pair.Key] = pair.Value.BackupPath; + } + } + } + + return true; + } + + public void RefreshTitles(List games) + { + this.titles.Clear(); + + if (!(this.version.supportsApiCommand())) + { + return; + } + + var runner = new Api.Runner(logger, this.settings); + foreach (var game in games) + { + runner.FindTitle(game); + } + + var (code, output) = runner.Invoke(); + + var i = 0; + if (output?.responses != null) + { + foreach (var response in output?.responses) + { + if (response.findTitle?.titles.Count() == 1) + { + this.titles[Etc.GetTitleId(games[i])] = response.findTitle?.titles[0]; + } + + i += 1; + } + } + } + + public void RefreshGames() + { + if (!this.version.supportsManifestShow()) + { + return; + } + + var (code, stdout) = InvokeDirect(new Invocation(Mode.ManifestShow), true); + if (code == 0 && stdout != null) + { + var manifest = JsonConvert.DeserializeObject(stdout); + this.manifestGames = manifest.Keys.ToList(); + + this.manifestGamesWithSaveDataByTitle = new List(); + this.manifestGamesWithSaveDataBySteamId = new List(); + foreach (var game in manifest) + { + var files = game.Value.Files != null && game.Value.Files.Count > 0; + var registry = game.Value.Registry != null && game.Value.Registry.Count > 0; + var steamId = game.Value.Steam?.Id ?? 0; + if (files || registry) + { + this.manifestGamesWithSaveDataByTitle.Add(game.Key); + if (steamId != 0) + { + this.manifestGamesWithSaveDataBySteamId.Add(steamId); + } + } + } + } + } + } + + public class LudusaviVersion + { + public Version inner; + + public LudusaviVersion(Version version) + { + this.inner = version; + } + + public bool supportsMultiBackup() + { + return this.inner >= new Version(0, 12, 0); + } + + public bool supportsRestoreBySteamId() + { + // This version fixed a defect when restoring by Steam ID. + return this.inner >= new Version(0, 12, 0); + } + + public bool supportsFindCommand() + { + return this.inner >= new Version(0, 14, 0); + } + + public bool supportsApiCommand() + { + return this.inner >= new Version(0, 24, 0); + } + + public bool supportsCustomizingBackupFormat() + { + return this.inner >= new Version(0, 14, 0); + } + + public bool supportsManifestShow() + { + return this.inner >= new Version(0, 16, 0); + } + + public bool requiresMergeFlag() + { + return this.inner < new Version(0, 18, 0); + } + + public bool hasGlobalManifestUpdateFlag() + { + return this.inner >= new Version(0, 18, 0); + } + } + + public class Invocation + { + private Mode mode; + private List games; + private string path; + private bool bySteamId; + private int? steamId; + private string backup; + private bool findBackup; + private bool normalized; + + public Invocation(Mode mode) + { + this.mode = mode; + this.games = new List(); + this.steamId = null; + } + + public Invocation PathIf(string value, bool condition) + { + if (condition) + { + this.path = value; + } + return this; + } + + public Invocation AddGame(string value) + { + this.games.Add(value); + return this; + } + + public Invocation Game(string value) + { + this.games.Clear(); + this.games.Add(value); + this.bySteamId = false; + return this; + } + + public Invocation BySteamId(string value) + { + this.bySteamId = true; + this.games.Clear(); + this.games.Add(value); + return this; + } + + public Invocation SteamId(int value) + { + this.steamId = value; + return this; + } + + public Invocation Backup(string backup) + { + this.backup = backup; + return this; + } + + public Invocation FindBackup() + { + this.findBackup = true; + return this; + } + + public Invocation Normalized() + { + this.normalized = true; + return this; + } + + private string Quote(string text) + { + return string.Format("\"{0}\"", text); + } + + public string Render(LudusaviPlayniteSettings settings, Cli.LudusaviVersion version) + { + var parts = new List(); + + if (version.hasGlobalManifestUpdateFlag()) + { + parts.Add("--try-manifest-update"); + } + + switch (this.mode) + { + case Mode.Backup: + parts.Add("backup"); + parts.Add("--force"); + if (version.requiresMergeFlag()) + { + parts.Add("--merge"); + } + if (!version.hasGlobalManifestUpdateFlag()) + { + parts.Add("--try-update"); + } + break; + case Mode.Backups: + parts.Add("backups"); + break; + case Mode.Find: + parts.Add("find"); + break; + case Mode.Restore: + parts.Add("restore"); + parts.Add("--force"); + break; + case Mode.ManifestShow: + parts.Add("manifest"); + parts.Add("show"); + break; + } + + parts.Add("--api"); + + if (this.path != null && this.path != "") + { + parts.Add("--path"); + parts.Add(Quote(this.path)); + } + + if (this.bySteamId) + { + parts.Add("--by-steam-id"); + } + + if (this.steamId != null) + { + parts.Add("--steam-id"); + parts.Add(this.steamId.ToString()); + } + + if (this.backup != null) + { + parts.Add("--backup"); + parts.Add(Quote(this.backup)); + } + + if (this.findBackup) + { + parts.Add("--backup"); + } + + if (this.normalized) + { + parts.Add("--normalized"); + } + + if (this.mode == Mode.Backup && version.supportsCustomizingBackupFormat()) + { + if (settings.OverrideBackupFormat) + { + parts.Add("--format"); + switch (settings.BackupFormat) + { + case BackupFormatType.Simple: + parts.Add("simple"); + break; + case BackupFormatType.Zip: + parts.Add("zip"); + break; + } + } + if (settings.OverrideBackupCompression) + { + parts.Add("--compression"); + switch (settings.BackupCompression) + { + case BackupCompressionType.None: + parts.Add("none"); + break; + case BackupCompressionType.Deflate: + parts.Add("deflate"); + break; + case BackupCompressionType.Bzip2: + parts.Add("bzip2"); + break; + case BackupCompressionType.Zstd: + parts.Add("zstd"); + break; + } + } + if (settings.OverrideBackupRetention) + { + parts.Add("--full-limit"); + parts.Add(settings.FullBackupLimit.ToString()); + parts.Add("--differential-limit"); + parts.Add(settings.DifferentialBackupLimit.ToString()); + } + } + + if (this.games.Count > 0) + { + parts.Add("--"); + foreach (var game in this.games) + { + parts.Add(Quote(game.Replace("\"", "\"\""))); + } + } + + return String.Join(" ", parts); + } + } +} + +namespace LudusaviPlaynite.Cli.Output +{ + public struct EmptyConcern + { } + + public struct Errors + { + [JsonProperty("someGamesFailed")] + public bool SomeGamesFailed; + [JsonProperty("unknownGames")] + public List UnknownGames; + [JsonProperty("cloudConflict")] + public EmptyConcern? CloudConflict; + [JsonProperty("cloudSyncFailed")] + public EmptyConcern? CloudSyncFailed; + } + + public struct Overall + { + [JsonProperty("totalGames")] + public int TotalGames; + [JsonProperty("totalBytes")] + public ulong TotalBytes; + [JsonProperty("processedGames")] + public int ProcessedGames; + [JsonProperty("processedBytes")] + public ulong ProcessedBytes; + [JsonProperty("changedGames")] + public ChangeCount? ChangedGames; + } + + public struct ChangeCount + { + [JsonProperty("new")] + public int New; + [JsonProperty("different")] + public ulong Different; + [JsonProperty("same")] + public int Same; + } + + public struct File + { + [JsonProperty("failed")] + public bool Failed; + [JsonProperty("bytes")] + public ulong Bytes; + [JsonProperty("change")] + public string Change; + } + + public struct Registry + { + [JsonProperty("failed")] + public bool Failed; + } + + public struct Backup + { + [JsonProperty("name")] + public string Name; + [JsonProperty("when")] + public DateTime When; + [JsonProperty("comment")] + public string Comment; + [JsonProperty("os")] + public string Os; + } + + public struct Game + { + [JsonProperty("decision")] + public string Decision; + [JsonProperty("change")] + public string Change; + [JsonProperty("files")] + public Dictionary Files; + [JsonProperty("registry")] + public Dictionary Registry; + [JsonProperty("backups")] + public List Backups; + [JsonProperty("backupPath")] + public string BackupPath; + } + + public struct Response + { + [JsonProperty("errors")] + public Errors Errors; + [JsonProperty("overall")] + public Overall Overall; + [JsonProperty("games")] + public Dictionary Games; + } +} diff --git a/src/Etc.cs b/src/Etc.cs index 6afb58f..658d77d 100644 --- a/src/Etc.cs +++ b/src/Etc.cs @@ -7,6 +7,9 @@ namespace LudusaviPlaynite { + /// + /// Miscellaneous utilities. + /// public static class Etc { public static Version RECOMMENDED_APP_VERSION = new Version(0, 24, 0); @@ -120,6 +123,16 @@ public static Platform GetGamePlatform(Game game) return game?.Platforms?.ElementAtOrDefault(0); } + public static bool ShouldSkipGame(Game game) + { + return Etc.HasTag(game, Tags.SKIP); + } + + public static bool HasTag(Game game, string tagName) + { + return game.Tags?.Any(tag => tag.Name == tagName) ?? false; + } + public static string NormalizePath(string path) { return HOME_DIR.Replace(path, Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)).Replace("/", "\\"); @@ -137,6 +150,41 @@ public static bool OpenDir(string path) return false; } } + + public static string GetBackupDisplayLine(Cli.Output.Backup backup) + { + var ret = backup.When.ToLocalTime().ToString(); + + if (!string.IsNullOrEmpty(backup.Os) && backup.Os != "windows") + { + ret += string.Format(" [{0}]", backup.Os); + } + if (!string.IsNullOrEmpty(backup.Comment)) + { + var line = ""; + var parts = backup.Comment.Split(); + + foreach (var part in backup.Comment.Split()) + { + if (line != "") + { + line += " "; + } + line += part; + if (line.Length > 60) + { + ret += string.Format("\n {0}", line); + line = ""; + } + } + if (line != "") + { + ret += string.Format("\n {0}", line); + } + } + + return ret; + } } public static class Tags diff --git a/src/Interactor.cs b/src/Interactor.cs new file mode 100644 index 0000000..e10319e --- /dev/null +++ b/src/Interactor.cs @@ -0,0 +1,183 @@ +using Playnite.SDK; +using Playnite.SDK.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; + +namespace LudusaviPlaynite +{ + /// + /// Interact with the user (e.g., ask confirmation) and/or the Playnite API (e.g., edit tags). + /// + public class Interactor + { + IPlayniteAPI PlayniteApi; + LudusaviPlayniteSettings settings; + Translator translator; + + public Interactor(IPlayniteAPI api, LudusaviPlayniteSettings settings, Translator translator) + { + this.PlayniteApi = api; + this.settings = settings; + this.translator = translator; + } + + public bool UserConsents(string message) + { + var choice = PlayniteApi.Dialogs.ShowMessage(message, "", System.Windows.MessageBoxButton.YesNo); + return choice == MessageBoxResult.Yes; + } + + public Choice AskUser(string message) + { + var yes = new MessageBoxOption(translator.YesButton(), true, false); + var always = new MessageBoxOption(translator.YesRememberedButton(), false, false); + var no = new MessageBoxOption(translator.NoButton(), false, false); + var never = new MessageBoxOption(translator.NoRememberedButton(), false, false); + + var choice = PlayniteApi.Dialogs.ShowMessage( + message, + "", + MessageBoxImage.None, + new List { always, never, yes, no } + ); + + if (choice == yes) + { + return Choice.Yes; + } + else if (choice == always) + { + return Choice.Always; + } + else if (choice == no) + { + return Choice.No; + } + else if (choice == never) + { + return Choice.Never; + } + else + { + throw new InvalidOperationException(String.Format("AskUser got unexpected answer: {0}", choice.Title)); + } + } + + public void NotifyInfo(string message) + { + NotifyInfo(message, () => { }); + } + + public void NotifyInfo(string message, Action action) + { + if (settings.IgnoreBenignNotifications) + { + return; + } + PlayniteApi.Notifications.Add(new NotificationMessage(Guid.NewGuid().ToString(), message, NotificationType.Info, action)); + } + + public void NotifyInfo(string message, OperationTiming timing) + { + if (timing == OperationTiming.DuringPlay) + { + return; + } + else + { + NotifyInfo(message); + } + } + + public void NotifyError(string message) + { + NotifyError(message, () => { }); + } + + public void NotifyError(string message, Action action) + { + PlayniteApi.Notifications.Add(new NotificationMessage(Guid.NewGuid().ToString(), message, NotificationType.Error, action)); + } + + public void NotifyError(string message, OperationTiming timing) + { + if (timing == OperationTiming.DuringPlay) + { + return; + } + else + { + NotifyError(message); + } + } + + public void ShowError(string message) + { + PlayniteApi.Dialogs.ShowErrorMessage(message, translator.Ludusavi()); + } + + public bool AddTag(Game game, string tagName) + { + var dbTag = PlayniteApi.Database.Tags.FirstOrDefault(tag => tag.Name == tagName); + if (dbTag == null) + { + dbTag = PlayniteApi.Database.Tags.Add(tagName); + } + + var dbGame = PlayniteApi.Database.Games[game.Id]; + if (dbGame.TagIds == null) + { + dbGame.TagIds = new List(); + } + var added = dbGame.TagIds.AddMissing(dbTag.Id); + PlayniteApi.Database.Games.Update(dbGame); + return added; + } + + public bool RemoveTag(Game game, string tagName) + { + if (game.Tags == null || game.Tags.All(tag => tag.Name != tagName)) + { + return false; + } + + var dbTag = PlayniteApi.Database.Tags.FirstOrDefault(tag => tag.Name == tagName); + if (dbTag == null) + { + return false; + } + + var dbGame = PlayniteApi.Database.Games[game.Id]; + if (dbGame.TagIds == null) + { + return false; + } + var removed = dbGame.TagIds.RemoveAll(id => id == dbTag.Id); + PlayniteApi.Database.Games.Update(dbGame); + return removed > 0; + } + + public void UpdateTagsForChoice(Game game, Choice choice, string alwaysTag, string neverTag, string fallbackTag = null) + { + if (choice == Choice.Always) + { + if (fallbackTag != null) + { + RemoveTag(game, fallbackTag); + } + AddTag(game, alwaysTag); + } + else if (choice == Choice.Never) + { + if (fallbackTag != null && Etc.HasTag(game, alwaysTag)) + { + AddTag(game, fallbackTag); + } + RemoveTag(game, alwaysTag); + AddTag(game, neverTag); + } + } + } +} diff --git a/src/LudusaviPlaynite.cs b/src/LudusaviPlaynite.cs index 113f53b..f6422e4 100644 --- a/src/LudusaviPlaynite.cs +++ b/src/LudusaviPlaynite.cs @@ -1,17 +1,13 @@ -using Newtonsoft.Json; -using Playnite.SDK; +using Playnite.SDK; using Playnite.SDK.Models; using Playnite.SDK.Plugins; using Playnite.SDK.Events; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Windows; using System.Windows.Controls; namespace LudusaviPlaynite @@ -22,17 +18,12 @@ public class LudusaviPlaynite : GenericPlugin public LudusaviPlayniteSettings settings { get; set; } public override Guid Id { get; } = Guid.Parse("72e2de43-d859-44d8-914e-4277741c8208"); + public Cli.App app; + public Interactor interactor; private Translator translator; private bool pendingOperation { get; set; } private bool playedSomething { get; set; } private Game lastGamePlayed { get; set; } - private LudusaviVersion appVersion { get; set; } = new LudusaviVersion(new Version(0, 0, 0)); - private Dictionary titles { get; set; } = new Dictionary(); - private Dictionary> backups { get; set; } = new Dictionary>(); - private Dictionary backupPaths { get; set; } = new Dictionary(); - private List manifestGames { get; set; } = new List(); - private List manifestGamesWithSaveDataByTitle { get; set; } = new List(); - private List manifestGamesWithSaveDataBySteamId { get; set; } = new List(); private Timer duringPlayBackupTimer { get; set; } private int duringPlayBackupTotal { get; set; } private int duringPlayBackupFailed { get; set; } @@ -41,6 +32,8 @@ public LudusaviPlaynite(IPlayniteAPI api) : base(api) { translator = new Translator(PlayniteApi.ApplicationSettings.Language); settings = new LudusaviPlayniteSettings(this, translator); + app = new Cli.App(LudusaviPlaynite.logger, this.settings); + interactor = new Interactor(api, settings, translator); Properties = new GenericPluginProperties { HasSettings = true @@ -56,7 +49,7 @@ public override IEnumerable GetMainMenuItems(GetMainMenuItemsArgs Description = translator.Launch_Label(), MenuSection = "@" + translator.Ludusavi(), Action = args => { - LaunchLudusavi(); + app.Launch(); } }, new MainMenuItem @@ -76,7 +69,7 @@ public override IEnumerable GetMainMenuItems(GetMainMenuItemsArgs { return; } - if (UserConsents(translator.BackUpAllGames_Confirm())) + if (interactor.UserConsents(translator.BackUpAllGames_Confirm())) { await Task.Run(() => BackUpAllGames()); } @@ -99,7 +92,7 @@ public override IEnumerable GetMainMenuItems(GetMainMenuItemsArgs { return; } - if (UserConsents(translator.RestoreAllGames_Confirm())) + if (interactor.UserConsents(translator.RestoreAllGames_Confirm())) { await Task.Run(() => RestoreAllGames()); } @@ -127,7 +120,7 @@ public override IEnumerable GetGameMenuItems(GetGameMenuItemsArgs { return; } - if (UserConsents(translator.BackUpSelectedGames_Confirm(args.Games.Select(x => GetGameName(x)).ToList()))) + if (interactor.UserConsents(translator.BackUpSelectedGames_Confirm(args.Games.Select(x => settings.GetGameName(x)).ToList()))) { foreach (var game in args.Games) { @@ -149,7 +142,7 @@ public override IEnumerable GetGameMenuItems(GetGameMenuItemsArgs items.Add( new GameMenuItem { - Description = GetBackupDisplayLine(backup), + Description = Etc.GetBackupDisplayLine(backup), MenuSection = string.Format("{0} | {1}", translator.Ludusavi(), translator.RestoreSelectedGames_Label()), Action = async args => { @@ -172,7 +165,7 @@ public override IEnumerable GetGameMenuItems(GetGameMenuItemsArgs { return; } - if (UserConsents(translator.RestoreSelectedGames_Confirm(args.Games.Select(x => GetGameName(x)).ToList()))) + if (interactor.UserConsents(translator.RestoreSelectedGames_Confirm(args.Games.Select(x => settings.GetGameName(x)).ToList()))) { foreach (var game in args.Games) { @@ -200,7 +193,7 @@ public override IEnumerable GetGameMenuItems(GetGameMenuItemsArgs { if (!Etc.OpenDir(backupPath)) { - ShowError(this.translator.CannotOpenFolder()); + interactor.ShowError(this.translator.CannotOpenFolder()); } } } @@ -222,9 +215,9 @@ public override IEnumerable GetGameMenuItems(GetGameMenuItemsArgs { GenericItemOption result = null; - if (this.appVersion.supportsManifestShow() && this.manifestGames.Count > 0) + if (this.app.version.supportsManifestShow() && this.app.manifestGames.Count > 0) { - var options = this.manifestGames.Select(x => new GenericItemOption(x, "")).ToList(); + var options = this.app.manifestGames.Select(x => new GenericItemOption(x, "")).ToList(); result = PlayniteApi.Dialogs.ChooseItemWithSearch( new List(options), (query) => @@ -235,7 +228,7 @@ public override IEnumerable GetGameMenuItems(GetGameMenuItemsArgs } else { - return this.manifestGames + return this.app.manifestGames .Where(x => x.ToLower().Contains(query.ToLower())) .Select(x => new GenericItemOption(x, "")).ToList(); } @@ -255,8 +248,7 @@ public override IEnumerable GetGameMenuItems(GetGameMenuItemsArgs { settings.AlternativeTitles[title] = result.Name; SavePluginSettings(settings); - RefreshLudusaviTitles(); - RefreshLudusaviBackups(); + Refresh(RefreshContext.ConfiguredTitle); } } } @@ -273,8 +265,7 @@ public override IEnumerable GetGameMenuItems(GetGameMenuItemsArgs { settings.AlternativeTitles.Remove(title); SavePluginSettings(settings); - RefreshLudusaviTitles(); - RefreshLudusaviBackups(); + Refresh(RefreshContext.ConfiguredTitle); } } ); @@ -286,7 +277,7 @@ public override IEnumerable GetGameMenuItems(GetGameMenuItemsArgs var candidate = entry.Key; var conflicts = entry.Value; - if (menuArgs.Games.Any(x => !HasTag(x, candidate))) + if (menuArgs.Games.Any(x => !Etc.HasTag(x, candidate))) { items.Add( new GameMenuItem @@ -295,21 +286,21 @@ public override IEnumerable GetGameMenuItems(GetGameMenuItemsArgs MenuSection = translator.Ludusavi(), Action = async args => { - if (UserConsents(translator.AddTagForSelectedGames_Confirm(candidate, args.Games.Select(x => x.Name)))) + if (interactor.UserConsents(translator.AddTagForSelectedGames_Confirm(candidate, args.Games.Select(x => x.Name)))) { foreach (var game in args.Games) { { await Task.Run(() => { - AddTag(game, candidate); + interactor.AddTag(game, candidate); foreach (var conflict in conflicts) { - var removed = RemoveTag(game, conflict); + var removed = interactor.RemoveTag(game, conflict); string replacement; if (removed && Tags.REPLACEMENTS.TryGetValue((candidate, conflict), out replacement)) { - AddTag(game, replacement); + interactor.AddTag(game, replacement); } } }); @@ -321,7 +312,7 @@ await Task.Run(() => ); } - if (menuArgs.Games.Any(x => HasTag(x, candidate))) + if (menuArgs.Games.Any(x => Etc.HasTag(x, candidate))) { items.Add( new GameMenuItem @@ -330,14 +321,14 @@ await Task.Run(() => MenuSection = translator.Ludusavi(), Action = async args => { - if (UserConsents(translator.RemoveTagForSelectedGames_Confirm(candidate, args.Games.Select(x => x.Name)))) + if (interactor.UserConsents(translator.RemoveTagForSelectedGames_Confirm(candidate, args.Games.Select(x => x.Name)))) { foreach (var game in args.Games) { { await Task.Run(() => { - RemoveTag(game, candidate); + interactor.RemoveTag(game, candidate); }); } } @@ -368,14 +359,11 @@ public override void OnApplicationStarted(OnApplicationStartedEventArgs args) Task.Run(() => { - RefreshLudusaviVersion(); - RefreshLudusaviTitles(); - RefreshLudusaviBackups(); - RefreshLudusaviGames(); + Refresh(RefreshContext.Startup); - if (appVersion.version < Etc.RECOMMENDED_APP_VERSION && new Version(settings.SuggestedUpgradeTo) < Etc.RECOMMENDED_APP_VERSION) + if (app.version.inner < Etc.RECOMMENDED_APP_VERSION && new Version(settings.SuggestedUpgradeTo) < Etc.RECOMMENDED_APP_VERSION) { - NotifyInfo( + interactor.NotifyInfo( translator.UpgradePrompt(Etc.RECOMMENDED_APP_VERSION.ToString()), () => { @@ -398,7 +386,7 @@ public override void OnGameStarting(OnGameStartingEventArgs args) playedSomething = true; lastGamePlayed = args.Game; Game game = args.Game; - var prefs = GetPlayPreferences(game); + var prefs = settings.GetPlayPreferences(game); if (prefs.Game.Restore.Do) { @@ -428,18 +416,18 @@ public override void OnGameStopped(OnGameStoppedEventArgs arg) playedSomething = true; lastGamePlayed = arg.Game; Game game = arg.Game; - var prefs = GetPlayPreferences(game); + var prefs = settings.GetPlayPreferences(game); if (this.duringPlayBackupTimer != null) { this.duringPlayBackupTimer.Change(Timeout.Infinite, Timeout.Infinite); if (this.duringPlayBackupFailed == 0) { - NotifyInfo(translator.BackUpDuringPlay_Success(game.Name, this.duringPlayBackupTotal)); + interactor.NotifyInfo(translator.BackUpDuringPlay_Success(game.Name, this.duringPlayBackupTotal)); } else { - NotifyError(translator.BackUpDuringPlay_Failure(game.Name, this.duringPlayBackupTotal, this.duringPlayBackupFailed)); + interactor.NotifyError(translator.BackUpDuringPlay_Failure(game.Name, this.duringPlayBackupTotal, this.duringPlayBackupFailed)); } } @@ -467,176 +455,76 @@ public override UserControl GetSettingsView(bool firstRunSettings) return new LudusaviPlayniteSettingsView(this, this.translator); } - public void RefreshLudusaviVersion() + public void Refresh(RefreshContext context) { - this.appVersion = new LudusaviVersion(GetLudusaviVersion()); - } - - public void RefreshLudusaviBackups() - { - if (!(this.appVersion.supportsMultiBackup())) - { - return; - } - - var (code, response) = InvokeLudusavi(new Invocation(Mode.Backups).PathIf(settings.BackupPath, settings.OverrideBackupPath)); - if (response?.Games != null) + switch (context) { - foreach (var pair in response?.Games) - { - this.backups.Add(pair.Key, pair.Value.Backups); - if (!string.IsNullOrEmpty(pair.Value.BackupPath)) - { - this.backupPaths.Add(pair.Key, pair.Value.BackupPath); - } - } - } - - if (this.settings.TagGamesWithBackups) - { - TagGamesWithBackups(); - } - } - - public void RefreshLudusaviTitles() - { - if (!(this.appVersion.supportsApiCommand())) - { - return; - } - - var runner = new Api.Runner(LudusaviPlaynite.logger, this.settings); - var games = this.PlayniteApi.Database.Games.ToList(); - foreach (var game in games) - { - runner.FindTitle(game, GetGameNameWithAlt(game), AlternativeTitle(game) != null); - } - - var (code, output) = runner.Invoke(); - - var i = 0; - if (output?.responses != null) - { - foreach (var response in output?.responses) - { - if (response.findTitle?.titles.Count() == 1) - { - this.titles.Add(Etc.GetTitleId(games[i]), response.findTitle?.titles[0]); - } - - i += 1; - } + case RefreshContext.Startup: + app.RefreshVersion(); + app.RefreshTitles(PlayniteApi.Database.Games.ToList()); + RefreshBackups(); + RefreshGames(); + break; + case RefreshContext.EditedConfig: + app.RefreshVersion(); + app.RefreshTitles(PlayniteApi.Database.Games.ToList()); + app.RefreshBackups(); + break; + case RefreshContext.ConfiguredTitle: + app.RefreshTitles(PlayniteApi.Database.Games.ToList()); + RefreshBackups(); + break; + case RefreshContext.CreatedBackup: + RefreshBackups(); + break; } } - public void RefreshLudusaviGames() + private void HandleSuccessDuringPlay() { - if (!this.appVersion.supportsManifestShow()) - { - return; - } - - var (code, stdout) = InvokeLudusaviDirect(new Invocation(Mode.ManifestShow), true); - if (code == 0 && stdout != null) - { - var manifest = JsonConvert.DeserializeObject(stdout); - this.manifestGames = manifest.Keys.ToList(); - - this.manifestGamesWithSaveDataByTitle = new List(); - this.manifestGamesWithSaveDataBySteamId = new List(); - foreach (var game in manifest) - { - var files = game.Value.Files != null && game.Value.Files.Count > 0; - var registry = game.Value.Registry != null && game.Value.Registry.Count > 0; - var steamId = game.Value.Steam?.Id ?? 0; - if (files || registry) - { - this.manifestGamesWithSaveDataByTitle.Add(game.Key); - if (steamId != 0) - { - this.manifestGamesWithSaveDataBySteamId.Add(steamId); - } - } - } - - if (this.settings.TagGamesWithUnknownSaveData) - { - TagGamesWithUnknownSaveData(); - } - } + this.duringPlayBackupTotal += 1; } - private void NotifyInfo(string message) + private void HandleFailureDuringPlay() { - NotifyInfo(message, () => { }); + this.duringPlayBackupTotal += 1; + this.duringPlayBackupFailed += 1; } - private void NotifyInfo(string message, Action action) + private void RefreshGames() { - if (settings.IgnoreBenignNotifications) + app.RefreshGames(); + if (this.settings.TagGamesWithUnknownSaveData) { - return; + TagGamesWithUnknownSaveData(); } - PlayniteApi.Notifications.Add(new NotificationMessage(Guid.NewGuid().ToString(), message, NotificationType.Info, action)); } - private void NotifyInfo(string message, OperationTiming timing) + private void RefreshBackups() { - if (timing == OperationTiming.DuringPlay) - { - this.duringPlayBackupTotal += 1; - } - else + if (app.RefreshBackups() && this.settings.TagGamesWithBackups) { - NotifyInfo(message); - } - } - - private void NotifyError(string message) - { - NotifyError(message, () => { }); - } - - private void NotifyError(string message, Action action) - { - PlayniteApi.Notifications.Add(new NotificationMessage(Guid.NewGuid().ToString(), message, NotificationType.Error, action)); - } - - private void NotifyError(string message, OperationTiming timing) - { - if (timing == OperationTiming.DuringPlay) - { - this.duringPlayBackupTotal += 1; - this.duringPlayBackupFailed += 1; - } - else - { - NotifyError(message); + TagGamesWithBackups(); } } - public void ShowError(string message) - { - PlayniteApi.Dialogs.ShowErrorMessage(message, translator.Ludusavi()); - } - - public void NotifyResponseErrors(ApiResponse? response) + public void NotifyResponseErrors(Cli.Output.Response? response) { if (response?.Errors.CloudSyncFailed != null) { var prefix = translator.Ludusavi(); var error = translator.CloudSyncFailed(); - NotifyError($"{prefix}: {error}"); + interactor.NotifyError($"{prefix}: {error}"); } if (response?.Errors.CloudConflict != null) { var prefix = translator.Ludusavi(); var error = translator.CloudConflict(); - NotifyError($"{prefix}: {error}", () => LaunchLudusavi()); + interactor.NotifyError($"{prefix}: {error}", () => app.Launch()); } } - private void ShowFullResults(ApiResponse response) + private void ShowFullResults(Cli.Output.Response response) { var tempFile = Path.GetTempPath() + Guid.NewGuid().ToString() + ".html"; using (StreamWriter sw = File.CreateText(tempFile)) @@ -661,71 +549,6 @@ private void ShowFullResults(ApiResponse response) { } } - private Version GetLudusaviVersion() - { - int code; - string stdout; - try - { - (code, stdout) = Etc.RunCommand(settings.ExecutablePath.Trim(), "--version"); - var version = stdout.Trim().Split(' ').Last(); - return new Version(version); - } - catch (Exception e) - { - logger.Debug(e, "Could not determine Ludusavi version"); - return new Version(0, 0, 0); - } - } - - private (int, string) InvokeLudusaviDirect(Invocation invocation, bool standalone = false) - { - var fullArgs = invocation.Render(settings, appVersion); - logger.Debug(string.Format("Running Ludusavi: {0}", fullArgs)); - - try - { - var (code, stdout) = Etc.RunCommand(settings.ExecutablePath.Trim(), fullArgs); - if (standalone) - { - logger.Debug(string.Format("Ludusavi exited with {0}", code)); - } - return (code, stdout); - } - catch (Exception e) - { - logger.Debug(e, "Ludusavi could not be executed"); - return (-1, null); - } - } - - private (int, ApiResponse?) InvokeLudusavi(Invocation invocation) - { - var (code, stdout) = InvokeLudusaviDirect(invocation); - - ApiResponse? response; - try - { - response = JsonConvert.DeserializeObject(stdout); - logger.Debug(string.Format("Ludusavi exited with {0} and valid JSON content", code)); - } - catch (Exception e) - { - response = null; - logger.Debug(e, string.Format("Ludusavi exited with {0} and invalid JSON content", code)); - } - - return (code, response); - } - - private void LaunchLudusavi() - { - var p = new Process(); - p.StartInfo.UseShellExecute = false; - p.StartInfo.FileName = settings.ExecutablePath.Trim(); - p.Start(); - } - private bool CanPerformOperation() { if (pendingOperation) @@ -751,104 +574,14 @@ private bool CanPerformOperationOnLastGamePlayed() return CanPerformOperation(); } - private bool UserConsents(string message) - { - var choice = PlayniteApi.Dialogs.ShowMessage(message, "", System.Windows.MessageBoxButton.YesNo); - return choice == MessageBoxResult.Yes; - } - - private Choice AskUser(string message) - { - var yes = new MessageBoxOption(translator.YesButton(), true, false); - var always = new MessageBoxOption(translator.YesRememberedButton(), false, false); - var no = new MessageBoxOption(translator.NoButton(), false, false); - var never = new MessageBoxOption(translator.NoRememberedButton(), false, false); - - var choice = PlayniteApi.Dialogs.ShowMessage( - message, - "", - MessageBoxImage.None, - new List { always, never, yes, no } - ); - - if (choice == yes) - { - return Choice.Yes; - } - else if (choice == always) - { - return Choice.Always; - } - else if (choice == no) - { - return Choice.No; - } - else if (choice == never) - { - return Choice.Never; - } - else - { - throw new InvalidOperationException(String.Format("AskUser got unexpected answer: {0}", choice.Title)); - } - } - - private bool ShouldSkipGame(Game game) - { - return HasTag(game, Tags.SKIP); - } - string GetTitle(Game game) { - return Etc.GetDictValue(this.titles, Etc.GetTitleId(game), null); - } - - string GetGameName(Game game) - { - if (!Etc.IsOnPc(game) && settings.AddSuffixForNonPcGameNames) - { - return string.Format("{0}{1}", game.Name, settings.SuffixForNonPcGameNames.Replace("", game.Platforms[0].Name)); - } - else - { - return game.Name; - } - } - - string GetGameNameWithAlt(Game game) - { - var alt = AlternativeTitle(game); - if (alt != null) - { - return alt; - } - else - { - return GetGameName(game); - } - } - - private string AlternativeTitle(Game game) - { - return Etc.GetDictValue(settings.AlternativeTitles, game.Name, null); - } - - private string GetDisplayName(Game game, BackupCriteria criteria) - { - switch (criteria) - { - case BackupCriteria.Game: - return GetGameName(game); - case BackupCriteria.Platform: - return Etc.GetGamePlatform(game)?.Name ?? "unknown platform"; - default: - throw new InvalidOperationException(String.Format("GetDisplayName got unexpected criteria: {0}", criteria)); - } + return Etc.GetDictValue(this.app.titles, Etc.GetTitleId(game), null); } private string FindGame(Game game, string name, OperationTiming timing, BackupCriteria criteria, Mode mode) { - if (this.appVersion.supportsApiCommand() && criteria.ByGame()) + if (this.app.version.supportsApiCommand() && criteria.ByGame()) { var title = GetTitle(game); if (title != null) @@ -857,18 +590,18 @@ private string FindGame(Game game, string name, OperationTiming timing, BackupCr } } - if (!this.appVersion.supportsFindCommand()) + if (!this.app.version.supportsFindCommand()) { return null; } - var invocation = new Invocation(Mode.Find).PathIf(settings.BackupPath, settings.OverrideBackupPath).Game(name); + var invocation = new Cli.Invocation(Mode.Find).PathIf(settings.BackupPath, settings.OverrideBackupPath).Game(name); if (mode == Mode.Backup) { invocation.FindBackup(); } - if (criteria.ByGame() && AlternativeTitle(game) == null) + if (criteria.ByGame() && settings.AlternativeTitle(game) == null) { // There can't be an alt title because the Steam ID/etc would take priority over it. @@ -886,24 +619,26 @@ private string FindGame(Game game, string name, OperationTiming timing, BackupCr } } - var (code, response) = InvokeLudusavi(invocation); + var (code, response) = app.Invoke(invocation); if (response == null) { - NotifyError(translator.UnableToRunLudusavi(), timing); + interactor.NotifyError(translator.UnableToRunLudusavi(), timing); + HandleFailureDuringPlay(); return null; } var officialName = response?.Games.Keys.FirstOrDefault(); if (code != 0 || officialName == null) { - NotifyError(translator.UnrecognizedGame(name), timing); + interactor.NotifyError(translator.UnrecognizedGame(name), timing); + HandleFailureDuringPlay(); return null; } return officialName; } - private void InitiateOperationSync(Game game, Operation operation, OperationTiming timing, BackupCriteria criteria, ApiBackup? backup = null) + private void InitiateOperationSync(Game game, Operation operation, OperationTiming timing, BackupCriteria criteria, Cli.Output.Backup? backup = null) { if (game == null) { @@ -926,9 +661,9 @@ private void InitiateOperationSync(Game game, Operation operation, OperationTimi return; } - var prefs = GetPlayPreferences(game); + var prefs = settings.GetPlayPreferences(game); var ask = prefs.ShouldAsk(timing, criteria, operation); - var displayName = GetDisplayName(game, criteria); + var displayName = settings.GetDisplayName(game, criteria); var consented = !ask; if (ask) @@ -938,10 +673,10 @@ private void InitiateOperationSync(Game game, Operation operation, OperationTimi switch (operation) { case Operation.Backup: - consented = UserConsents(translator.BackUpOneGame_Confirm(displayName)); + consented = interactor.UserConsents(translator.BackUpOneGame_Confirm(displayName)); break; case Operation.Restore: - consented = UserConsents(translator.RestoreOneGame_Confirm(displayName)); + consented = interactor.UserConsents(translator.RestoreOneGame_Confirm(displayName)); break; } } @@ -951,10 +686,10 @@ private void InitiateOperationSync(Game game, Operation operation, OperationTimi switch (operation) { case Operation.Backup: - choice = AskUser(translator.BackUpOneGame_Confirm(displayName)); + choice = interactor.AskUser(translator.BackUpOneGame_Confirm(displayName)); break; case Operation.Restore: - choice = AskUser(translator.RestoreOneGame_Confirm(displayName)); + choice = interactor.AskUser(translator.RestoreOneGame_Confirm(displayName)); break; } @@ -966,10 +701,10 @@ private void InitiateOperationSync(Game game, Operation operation, OperationTimi switch (criteria) { case BackupCriteria.Game: - UpdateTagsForChoice(game, choice, Tags.GAME_BACKUP, Tags.GAME_NO_BACKUP); + interactor.UpdateTagsForChoice(game, choice, Tags.GAME_BACKUP, Tags.GAME_NO_BACKUP); break; case BackupCriteria.Platform: - UpdateTagsForChoice(game, choice, Tags.PLATFORM_BACKUP, Tags.PLATFORM_NO_BACKUP); + interactor.UpdateTagsForChoice(game, choice, Tags.PLATFORM_BACKUP, Tags.PLATFORM_NO_BACKUP); break; } break; @@ -977,10 +712,10 @@ private void InitiateOperationSync(Game game, Operation operation, OperationTimi switch (criteria) { case BackupCriteria.Game: - UpdateTagsForChoice(game, choice, Tags.GAME_BACKUP_AND_RESTORE, Tags.GAME_NO_RESTORE, Tags.GAME_BACKUP); + interactor.UpdateTagsForChoice(game, choice, Tags.GAME_BACKUP_AND_RESTORE, Tags.GAME_NO_RESTORE, Tags.GAME_BACKUP); break; case BackupCriteria.Platform: - UpdateTagsForChoice(game, choice, Tags.PLATFORM_BACKUP_AND_RESTORE, Tags.PLATFORM_NO_RESTORE, Tags.PLATFORM_BACKUP); + interactor.UpdateTagsForChoice(game, choice, Tags.PLATFORM_BACKUP_AND_RESTORE, Tags.PLATFORM_NO_RESTORE, Tags.PLATFORM_BACKUP); break; } break; @@ -1002,13 +737,13 @@ private void InitiateOperationSync(Game game, Operation operation, OperationTimi var error = RestoreOneGame(game, backup, criteria); if (timing == OperationTiming.BeforePlay && !String.IsNullOrEmpty(error.Message) && !error.Empty) { - ShowError(error.Message); + interactor.ShowError(error.Message); } break; } } - private async Task InitiateOperation(Game game, Operation operation, OperationTiming timing, BackupCriteria criteria, ApiBackup? backup = null) + private async Task InitiateOperation(Game game, Operation operation, OperationTiming timing, BackupCriteria criteria, Cli.Output.Backup? backup = null) { await Task.Run(() => InitiateOperationSync(game, operation, timing, criteria, backup)); } @@ -1016,11 +751,11 @@ private async Task InitiateOperation(Game game, Operation operation, OperationTi private void BackUpOneGame(Game game, OperationTiming timing, BackupCriteria criteria) { pendingOperation = true; - var name = criteria.ByPlatform() ? game.Platforms[0].Name : GetGameNameWithAlt(game); + var name = criteria.ByPlatform() ? game.Platforms[0].Name : settings.GetGameNameWithAlt(game); var displayName = game.Name; var refresh = true; - if (this.appVersion.supportsFindCommand()) + if (this.app.version.supportsFindCommand()) { var found = FindGame(game, name, timing, criteria, Mode.Backup); if (found == null) @@ -1035,47 +770,51 @@ private void BackUpOneGame(Game game, OperationTiming timing, BackupCriteria cri } } - var invocation = new Invocation(Mode.Backup).PathIf(settings.BackupPath, settings.OverrideBackupPath).Game(name); + var invocation = new Cli.Invocation(Mode.Backup).PathIf(settings.BackupPath, settings.OverrideBackupPath).Game(name); - var (code, response) = InvokeLudusavi(invocation); + var (code, response) = app.Invoke(invocation); - if (!this.appVersion.supportsFindCommand() && criteria.ByGame() && AlternativeTitle(game) == null) + if (!this.app.version.supportsFindCommand() && criteria.ByGame() && settings.AlternativeTitle(game) == null) { if (response?.Errors.UnknownGames != null && Etc.IsOnSteam(game)) { - (code, response) = InvokeLudusavi(invocation.BySteamId(game.GameId)); + (code, response) = app.Invoke(invocation.BySteamId(game.GameId)); } if (response?.Errors.UnknownGames != null && !Etc.IsOnPc(game) && settings.RetryNonPcGamesWithoutSuffix) { - (code, response) = InvokeLudusavi(invocation.Game(game.Name)); + (code, response) = app.Invoke(invocation.Game(game.Name)); } } if (response == null) { - NotifyError(translator.UnableToRunLudusavi(), timing); + interactor.NotifyError(translator.UnableToRunLudusavi(), timing); + HandleFailureDuringPlay(); } else { - var result = new OperationResult { Name = displayName, Response = (ApiResponse)response }; + var result = new OperationResult { Name = displayName, Response = (Cli.Output.Response)response }; if (code == 0) { if (response?.Overall.TotalGames > 0) { if ((response?.Overall.ChangedGames?.Same ?? 0) == 0) { - NotifyInfo(translator.BackUpOneGame_Success(result), timing); + interactor.NotifyInfo(translator.BackUpOneGame_Success(result), timing); + HandleSuccessDuringPlay(); } else { refresh = false; - NotifyInfo(translator.BackUpOneGame_Unchanged(result), timing); + interactor.NotifyInfo(translator.BackUpOneGame_Unchanged(result), timing); + HandleSuccessDuringPlay(); } } else { refresh = false; - NotifyError(translator.BackUpOneGame_Empty(result), timing); + interactor.NotifyError(translator.BackUpOneGame_Empty(result), timing); + HandleFailureDuringPlay(); } } else @@ -1083,11 +822,13 @@ private void BackUpOneGame(Game game, OperationTiming timing, BackupCriteria cri if (response?.Errors.UnknownGames != null) { refresh = false; - NotifyError(translator.BackUpOneGame_Empty(result), timing); + interactor.NotifyError(translator.BackUpOneGame_Empty(result), timing); + HandleFailureDuringPlay(); } else { - NotifyError(translator.BackUpOneGame_Failure(result), timing); + interactor.NotifyError(translator.BackUpOneGame_Failure(result), timing); + HandleFailureDuringPlay(); } } } @@ -1095,7 +836,7 @@ private void BackUpOneGame(Game game, OperationTiming timing, BackupCriteria cri NotifyResponseErrors(response); if (refresh) { - RefreshLudusaviBackups(); + Refresh(RefreshContext.CreatedBackup); } pendingOperation = false; } @@ -1103,28 +844,28 @@ private void BackUpOneGame(Game game, OperationTiming timing, BackupCriteria cri private void BackUpAllGames() { pendingOperation = true; - var (code, response) = InvokeLudusavi(new Invocation(Mode.Backup).PathIf(settings.BackupPath, settings.OverrideBackupPath)); + var (code, response) = app.Invoke(new Cli.Invocation(Mode.Backup).PathIf(settings.BackupPath, settings.OverrideBackupPath)); if (response == null) { - NotifyError(translator.UnableToRunLudusavi()); + interactor.NotifyError(translator.UnableToRunLudusavi()); } else { - var result = new OperationResult { Response = (ApiResponse)response }; + var result = new OperationResult { Response = (Cli.Output.Response)response }; if (code == 0) { - NotifyInfo(translator.BackUpAllGames_Success(result), () => ShowFullResults(result.Response)); + interactor.NotifyInfo(translator.BackUpAllGames_Success(result), () => ShowFullResults(result.Response)); } else { - NotifyError(translator.BackUpAllGames_Failure(result), () => ShowFullResults(result.Response)); + interactor.NotifyError(translator.BackUpAllGames_Failure(result), () => ShowFullResults(result.Response)); } } NotifyResponseErrors(response); - RefreshLudusaviBackups(); + Refresh(RefreshContext.CreatedBackup); pendingOperation = false; } @@ -1134,7 +875,7 @@ private void BackUpOneGameDuringPlay(Game game) { return; } - var prefs = GetPlayPreferences(game); + var prefs = settings.GetPlayPreferences(game); Task.Run(() => { if (prefs.Game.Backup.Do && !prefs.Game.Backup.Ask && settings.DoBackupDuringPlay) @@ -1149,7 +890,7 @@ private void BackUpOneGameDuringPlay(Game game) }); } - private RestorationError RestoreOneGame(Game game, ApiBackup? backup, BackupCriteria criteria) + private RestorationError RestoreOneGame(Game game, Cli.Output.Backup? backup, BackupCriteria criteria) { RestorationError error = new RestorationError { @@ -1158,10 +899,10 @@ private RestorationError RestoreOneGame(Game game, ApiBackup? backup, BackupCrit }; pendingOperation = true; - var name = criteria.ByPlatform() ? game.Platforms[0].Name : GetGameNameWithAlt(game); + var name = criteria.ByPlatform() ? game.Platforms[0].Name : settings.GetGameNameWithAlt(game); var displayName = game.Name; - if (this.appVersion.supportsFindCommand()) + if (this.app.version.supportsFindCommand()) { var found = FindGame(game, name, OperationTiming.Free, criteria, Mode.Restore); if (found == null) @@ -1178,29 +919,29 @@ private RestorationError RestoreOneGame(Game game, ApiBackup? backup, BackupCrit } } - var invocation = new Invocation(Mode.Restore).PathIf(settings.BackupPath, settings.OverrideBackupPath).Game(name).Backup(backup?.Name); + var invocation = new Cli.Invocation(Mode.Restore).PathIf(settings.BackupPath, settings.OverrideBackupPath).Game(name).Backup(backup?.Name); - var (code, response) = InvokeLudusavi(invocation); - if (!this.appVersion.supportsFindCommand() && criteria.ByGame() && AlternativeTitle(game) == null) + var (code, response) = app.Invoke(invocation); + if (!this.app.version.supportsFindCommand() && criteria.ByGame() && settings.AlternativeTitle(game) == null) { - if (response?.Errors.UnknownGames != null && Etc.IsOnSteam(game) && this.appVersion.supportsRestoreBySteamId()) + if (response?.Errors.UnknownGames != null && Etc.IsOnSteam(game) && this.app.version.supportsRestoreBySteamId()) { - (code, response) = InvokeLudusavi(invocation.BySteamId(game.GameId)); + (code, response) = app.Invoke(invocation.BySteamId(game.GameId)); } if (response?.Errors.UnknownGames != null && !Etc.IsOnPc(game) && settings.RetryNonPcGamesWithoutSuffix) { - (code, response) = InvokeLudusavi(invocation.Game(game.Name)); + (code, response) = app.Invoke(invocation.Game(game.Name)); } } if (response == null) { error.Message = translator.UnableToRunLudusavi(); - NotifyError(error.Message); + interactor.NotifyError(error.Message); } else { - var result = new OperationResult { Name = displayName, Response = (ApiResponse)response }; + var result = new OperationResult { Name = displayName, Response = (Cli.Output.Response)response }; if (code == 0) { if (response?.Overall.TotalGames == 0) @@ -1208,15 +949,15 @@ private RestorationError RestoreOneGame(Game game, ApiBackup? backup, BackupCrit // This applies to Ludusavi v0.23.0 and later error.Message = translator.RestoreOneGame_Empty(result); error.Empty = true; - NotifyError(error.Message); + interactor.NotifyError(error.Message); } else if ((response?.Overall.ChangedGames?.Same ?? 0) == 0) { - NotifyInfo(translator.RestoreOneGame_Success(result)); + interactor.NotifyInfo(translator.RestoreOneGame_Success(result)); } else { - NotifyInfo(translator.RestoreOneGame_Unchanged(result)); + interactor.NotifyInfo(translator.RestoreOneGame_Unchanged(result)); } } else @@ -1226,12 +967,12 @@ private RestorationError RestoreOneGame(Game game, ApiBackup? backup, BackupCrit // This applies to Ludusavi versions before v0.23.0 error.Message = translator.RestoreOneGame_Empty(result); error.Empty = true; - NotifyError(error.Message); + interactor.NotifyError(error.Message); } else { error.Message = translator.RestoreOneGame_Failure(result); - NotifyError(error.Message); + interactor.NotifyError(error.Message); } } } @@ -1244,23 +985,23 @@ private RestorationError RestoreOneGame(Game game, ApiBackup? backup, BackupCrit private void RestoreAllGames() { pendingOperation = true; - var (code, response) = InvokeLudusavi(new Invocation(Mode.Restore).PathIf(settings.BackupPath, settings.OverrideBackupPath)); + var (code, response) = app.Invoke(new Cli.Invocation(Mode.Restore).PathIf(settings.BackupPath, settings.OverrideBackupPath)); if (response == null) { - NotifyError(translator.UnableToRunLudusavi()); + interactor.NotifyError(translator.UnableToRunLudusavi()); } else { - var result = new OperationResult { Response = (ApiResponse)response }; + var result = new OperationResult { Response = (Cli.Output.Response)response }; if (code == 0) { - NotifyInfo(translator.RestoreAllGames_Success(result), () => ShowFullResults(result.Response)); + interactor.NotifyInfo(translator.RestoreAllGames_Success(result), () => ShowFullResults(result.Response)); } else { - NotifyError(translator.RestoreAllGames_Failure(result), () => ShowFullResults(result.Response)); + interactor.NotifyError(translator.RestoreAllGames_Failure(result), () => ShowFullResults(result.Response)); } } @@ -1269,84 +1010,17 @@ private void RestoreAllGames() pendingOperation = false; } - private bool HasTag(Game game, string tagName) - { - return game.Tags?.Any(tag => tag.Name == tagName) ?? false; - } - - private bool AddTag(Game game, string tagName) - { - var dbTag = PlayniteApi.Database.Tags.FirstOrDefault(tag => tag.Name == tagName); - if (dbTag == null) - { - dbTag = PlayniteApi.Database.Tags.Add(tagName); - } - - var dbGame = PlayniteApi.Database.Games[game.Id]; - if (dbGame.TagIds == null) - { - dbGame.TagIds = new List(); - } - var added = dbGame.TagIds.AddMissing(dbTag.Id); - PlayniteApi.Database.Games.Update(dbGame); - return added; - } - - private bool RemoveTag(Game game, string tagName) - { - if (game.Tags == null || game.Tags.All(tag => tag.Name != tagName)) - { - return false; - } - - var dbTag = PlayniteApi.Database.Tags.FirstOrDefault(tag => tag.Name == tagName); - if (dbTag == null) - { - return false; - } - - var dbGame = PlayniteApi.Database.Games[game.Id]; - if (dbGame.TagIds == null) - { - return false; - } - var removed = dbGame.TagIds.RemoveAll(id => id == dbTag.Id); - PlayniteApi.Database.Games.Update(dbGame); - return removed > 0; - } - - private void UpdateTagsForChoice(Game game, Choice choice, string alwaysTag, string neverTag, string fallbackTag = null) - { - if (choice == Choice.Always) - { - if (fallbackTag != null) - { - RemoveTag(game, fallbackTag); - } - AddTag(game, alwaysTag); - } - else if (choice == Choice.Never) - { - if (fallbackTag != null && HasTag(game, alwaysTag)) - { - AddTag(game, fallbackTag); - } - RemoveTag(game, alwaysTag); - AddTag(game, neverTag); - } - } - private void TagGamesWithBackups() { foreach (var game in PlayniteApi.Database.Games) { if (IsBackedUp(game)) { - AddTag(game, Tags.BACKED_UP); + interactor.AddTag(game, Tags.BACKED_UP); } else { - RemoveTag(game, Tags.BACKED_UP); + interactor.RemoveTag(game, Tags.BACKED_UP); } } } @@ -1357,67 +1031,15 @@ private void TagGamesWithUnknownSaveData() { if (!GameHasKnownSaveData(game)) { - AddTag(game, Tags.UNKNOWN_SAVE_DATA); + interactor.AddTag(game, Tags.UNKNOWN_SAVE_DATA); } else { - RemoveTag(game, Tags.UNKNOWN_SAVE_DATA); + interactor.RemoveTag(game, Tags.UNKNOWN_SAVE_DATA); } } } - private PlayPreferences GetPlayPreferences(Game game) - { - if (ShouldSkipGame(game)) - { - return new PlayPreferences(); - } - - var gameBackupDo = (settings.DoBackupOnGameStopped || HasTag(game, Tags.GAME_BACKUP) || HasTag(game, Tags.GAME_BACKUP_AND_RESTORE)) - && !HasTag(game, Tags.GAME_NO_BACKUP) - && (Etc.IsOnPc(game) || !settings.OnlyBackupOnGameStoppedIfPc || HasTag(game, Tags.GAME_BACKUP) || HasTag(game, Tags.GAME_BACKUP_AND_RESTORE)); - var platformBackupDo = (settings.DoPlatformBackupOnNonPcGameStopped || HasTag(game, Tags.PLATFORM_BACKUP) || HasTag(game, Tags.PLATFORM_BACKUP_AND_RESTORE)) - && !HasTag(game, Tags.PLATFORM_NO_BACKUP) - && !Etc.IsOnPc(game) - && Etc.GetGamePlatform(game) != null; - - var prefs = new PlayPreferences - { - Game = new OperationPreferences - { - Backup = new OperationPreference - { - Do = gameBackupDo, - Ask = settings.AskBackupOnGameStopped && !HasTag(game, Tags.GAME_BACKUP) && !HasTag(game, Tags.GAME_BACKUP_AND_RESTORE), - }, - Restore = new OperationPreference - { - Do = gameBackupDo - && (settings.DoRestoreOnGameStarting || HasTag(game, Tags.GAME_BACKUP_AND_RESTORE)) - && !HasTag(game, Tags.GAME_NO_RESTORE), - Ask = settings.AskBackupOnGameStopped && !HasTag(game, Tags.GAME_BACKUP_AND_RESTORE), - }, - }, - Platform = new OperationPreferences - { - Backup = new OperationPreference - { - Do = platformBackupDo, - Ask = settings.AskPlatformBackupOnNonPcGameStopped && !HasTag(game, Tags.PLATFORM_BACKUP) && !HasTag(game, Tags.PLATFORM_BACKUP_AND_RESTORE), - }, - Restore = new OperationPreference - { - Do = platformBackupDo - && (settings.DoPlatformRestoreOnNonPcGameStarting || HasTag(game, Tags.PLATFORM_BACKUP_AND_RESTORE)) - && !HasTag(game, Tags.PLATFORM_NO_RESTORE), - Ask = settings.AskPlatformBackupOnNonPcGameStopped && !HasTag(game, Tags.PLATFORM_BACKUP_AND_RESTORE), - }, - }, - }; - - return prefs; - } - private bool IsBackedUp(Game game) { return GetBackups(game).Count > 0; @@ -1426,22 +1048,22 @@ private bool IsBackedUp(Game game) private bool GameHasKnownSaveData(Game game) { string title; - if (this.appVersion.supportsApiCommand()) + if (this.app.version.supportsApiCommand()) { title = GetTitle(game); } else { // Ideally, we would use the `find` command, but that's too slow to run in bulk. - title = AlternativeTitle(game) ?? GetGameName(game); + title = settings.AlternativeTitle(game) ?? settings.GetGameName(game); } - if (title != null && this.manifestGamesWithSaveDataByTitle.Contains(title)) + if (title != null && this.app.manifestGamesWithSaveDataByTitle.Contains(title)) { return true; } - if (Etc.TrySteamId(game, out var id) && this.manifestGamesWithSaveDataBySteamId.Contains(id)) + if (Etc.TrySteamId(game, out var id) && this.app.manifestGamesWithSaveDataBySteamId.Contains(id)) { return true; } @@ -1449,12 +1071,12 @@ private bool GameHasKnownSaveData(Game game) return false; } - private List GetBackups(Game game) + private List GetBackups(Game game) { - if (this.appVersion.supportsApiCommand()) + if (this.app.version.supportsApiCommand()) { var title = GetTitle(game); - var backups = Etc.GetDictValue(this.backups, title, new List()); + var backups = Etc.GetDictValue(this.app.backups, title, new List()); // Sort newest backups to the top. backups.Sort((x, y) => y.When.CompareTo(x.When)); @@ -1462,22 +1084,22 @@ private List GetBackups(Game game) return backups; } - var ret = new List(); - var alt = AlternativeTitle(game); + var ret = new List(); + var alt = settings.AlternativeTitle(game); if (alt != null) { - ret = Etc.GetDictValue(this.backups, alt, new List()); + ret = Etc.GetDictValue(this.app.backups, alt, new List()); } else { ret = Etc.GetDictValue( - this.backups, - GetGameName(game), + this.app.backups, + settings.GetGameName(game), Etc.GetDictValue( - this.backups, + this.app.backups, game.Name, - new List() + new List() ) ); } @@ -1490,42 +1112,7 @@ private List GetBackups(Game game) private string GetBackupPath(Game game) { - return Etc.GetDictValue(this.backupPaths, GetTitle(game) ?? GetGameNameWithAlt(game), null); - } - - private string GetBackupDisplayLine(ApiBackup backup) - { - var ret = backup.When.ToLocalTime().ToString(); - - if (!string.IsNullOrEmpty(backup.Os) && backup.Os != "windows") - { - ret += string.Format(" [{0}]", backup.Os); - } - if (!string.IsNullOrEmpty(backup.Comment)) - { - var line = ""; - var parts = backup.Comment.Split(); - - foreach (var part in backup.Comment.Split()) - { - if (line != "") - { - line += " "; - } - line += part; - if (line.Length > 60) - { - ret += string.Format("\n {0}", line); - line = ""; - } - } - if (line != "") - { - ret += string.Format("\n {0}", line); - } - } - - return ret; + return Etc.GetDictValue(this.app.backupPaths, GetTitle(game) ?? settings.GetGameNameWithAlt(game), null); } } } diff --git a/src/LudusaviPlayniteSettings.cs b/src/LudusaviPlayniteSettings.cs index fb6e014..5cf6bcb 100644 --- a/src/LudusaviPlayniteSettings.cs +++ b/src/LudusaviPlayniteSettings.cs @@ -1,5 +1,6 @@ using Newtonsoft.Json; using Playnite.SDK; +using Playnite.SDK.Models; using System; using System.Collections.Generic; using System.ComponentModel; @@ -278,9 +279,7 @@ public void EndEdit() // Code executed when user decides to confirm changes made since BeginEdit was called. // This method should save settings made to Option1 and Option2. plugin.SavePluginSettings(this); - this.plugin.RefreshLudusaviVersion(); - this.plugin.RefreshLudusaviTitles(); - this.plugin.RefreshLudusaviBackups(); + this.plugin.Refresh(RefreshContext.EditedConfig); } public bool VerifySettings(out List errors) @@ -291,5 +290,100 @@ public bool VerifySettings(out List errors) errors = new List(); return true; } + + public string GetGameName(Game game) + { + if (!Etc.IsOnPc(game) && this.AddSuffixForNonPcGameNames) + { + return string.Format("{0}{1}", game.Name, this.SuffixForNonPcGameNames.Replace("", game.Platforms[0].Name)); + } + else + { + return game.Name; + } + } + + public string GetGameNameWithAlt(Game game) + { + var alt = AlternativeTitle(game); + if (alt != null) + { + return alt; + } + else + { + return GetGameName(game); + } + } + + public string AlternativeTitle(Game game) + { + return Etc.GetDictValue(this.AlternativeTitles, game.Name, null); + } + + public string GetDisplayName(Game game, BackupCriteria criteria) + { + switch (criteria) + { + case BackupCriteria.Game: + return GetGameName(game); + case BackupCriteria.Platform: + return Etc.GetGamePlatform(game)?.Name ?? "unknown platform"; + default: + throw new InvalidOperationException(String.Format("GetDisplayName got unexpected criteria: {0}", criteria)); + } + } + + public PlayPreferences GetPlayPreferences(Game game) + { + if (Etc.ShouldSkipGame(game)) + { + return new PlayPreferences(); + } + + var gameBackupDo = (this.DoBackupOnGameStopped || Etc.HasTag(game, Tags.GAME_BACKUP) || Etc.HasTag(game, Tags.GAME_BACKUP_AND_RESTORE)) + && !Etc.HasTag(game, Tags.GAME_NO_BACKUP) + && (Etc.IsOnPc(game) || !this.OnlyBackupOnGameStoppedIfPc || Etc.HasTag(game, Tags.GAME_BACKUP) || Etc.HasTag(game, Tags.GAME_BACKUP_AND_RESTORE)); + var platformBackupDo = (this.DoPlatformBackupOnNonPcGameStopped || Etc.HasTag(game, Tags.PLATFORM_BACKUP) || Etc.HasTag(game, Tags.PLATFORM_BACKUP_AND_RESTORE)) + && !Etc.HasTag(game, Tags.PLATFORM_NO_BACKUP) + && !Etc.IsOnPc(game) + && Etc.GetGamePlatform(game) != null; + + var prefs = new PlayPreferences + { + Game = new OperationPreferences + { + Backup = new OperationPreference + { + Do = gameBackupDo, + Ask = this.AskBackupOnGameStopped && !Etc.HasTag(game, Tags.GAME_BACKUP) && !Etc.HasTag(game, Tags.GAME_BACKUP_AND_RESTORE), + }, + Restore = new OperationPreference + { + Do = gameBackupDo + && (this.DoRestoreOnGameStarting || Etc.HasTag(game, Tags.GAME_BACKUP_AND_RESTORE)) + && !Etc.HasTag(game, Tags.GAME_NO_RESTORE), + Ask = this.AskBackupOnGameStopped && !Etc.HasTag(game, Tags.GAME_BACKUP_AND_RESTORE), + }, + }, + Platform = new OperationPreferences + { + Backup = new OperationPreference + { + Do = platformBackupDo, + Ask = this.AskPlatformBackupOnNonPcGameStopped && !Etc.HasTag(game, Tags.PLATFORM_BACKUP) && !Etc.HasTag(game, Tags.PLATFORM_BACKUP_AND_RESTORE), + }, + Restore = new OperationPreference + { + Do = platformBackupDo + && (this.DoPlatformRestoreOnNonPcGameStarting || Etc.HasTag(game, Tags.PLATFORM_BACKUP_AND_RESTORE)) + && !Etc.HasTag(game, Tags.PLATFORM_NO_RESTORE), + Ask = this.AskPlatformBackupOnNonPcGameStopped && !Etc.HasTag(game, Tags.PLATFORM_BACKUP_AND_RESTORE), + }, + }, + }; + + return prefs; + } } } diff --git a/src/LudusaviPlayniteSettingsView.xaml.cs b/src/LudusaviPlayniteSettingsView.xaml.cs index 8f3f9a3..71f91d5 100644 --- a/src/LudusaviPlayniteSettingsView.xaml.cs +++ b/src/LudusaviPlayniteSettingsView.xaml.cs @@ -37,7 +37,7 @@ public void OnOpenBackupPath(object sender, RoutedEventArgs e) { if (!Etc.OpenDir(plugin.settings.BackupPath)) { - this.plugin.ShowError(this.translator.CannotOpenFolder()); + this.plugin.interactor.ShowError(this.translator.CannotOpenFolder()); } } } diff --git a/src/Manifest.cs b/src/Manifest.cs new file mode 100644 index 0000000..277a609 --- /dev/null +++ b/src/Manifest.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace LudusaviPlaynite.Manifest +{ + public class Data : Dictionary + { } + + public struct Game + { + [JsonProperty("files")] + public Dictionary Files; + [JsonProperty("registry")] + public Dictionary Registry; + [JsonProperty("steam")] + public Steam Steam; + } + + public class Steam + { + [JsonProperty("id")] + public int? Id; + } +} diff --git a/src/Models.cs b/src/Models.cs index d9186d6..1dde3f9 100644 --- a/src/Models.cs +++ b/src/Models.cs @@ -1,6 +1,4 @@ -using Newtonsoft.Json; using System; -using System.Collections.Generic; namespace LudusaviPlaynite { @@ -25,214 +23,6 @@ public enum Mode ManifestShow, } - public class Invocation - { - private Mode mode; - private List games; - private string path; - private bool bySteamId; - private int? steamId; - private string backup; - private bool findBackup; - private bool normalized; - - public Invocation(Mode mode) - { - this.mode = mode; - this.games = new List(); - this.steamId = null; - } - - public Invocation PathIf(string value, bool condition) - { - if (condition) - { - this.path = value; - } - return this; - } - - public Invocation AddGame(string value) - { - this.games.Add(value); - return this; - } - - public Invocation Game(string value) - { - this.games.Clear(); - this.games.Add(value); - this.bySteamId = false; - return this; - } - - public Invocation BySteamId(string value) - { - this.bySteamId = true; - this.games.Clear(); - this.games.Add(value); - return this; - } - - public Invocation SteamId(int value) - { - this.steamId = value; - return this; - } - - public Invocation Backup(string backup) - { - this.backup = backup; - return this; - } - - public Invocation FindBackup() - { - this.findBackup = true; - return this; - } - - public Invocation Normalized() - { - this.normalized = true; - return this; - } - - private string Quote(string text) - { - return string.Format("\"{0}\"", text); - } - - public string Render(LudusaviPlayniteSettings settings, LudusaviVersion version) - { - var parts = new List(); - - if (version.hasGlobalManifestUpdateFlag()) - { - parts.Add("--try-manifest-update"); - } - - switch (this.mode) - { - case Mode.Backup: - parts.Add("backup"); - parts.Add("--force"); - if (version.requiresMergeFlag()) - { - parts.Add("--merge"); - } - if (!version.hasGlobalManifestUpdateFlag()) - { - parts.Add("--try-update"); - } - break; - case Mode.Backups: - parts.Add("backups"); - break; - case Mode.Find: - parts.Add("find"); - break; - case Mode.Restore: - parts.Add("restore"); - parts.Add("--force"); - break; - case Mode.ManifestShow: - parts.Add("manifest"); - parts.Add("show"); - break; - } - - parts.Add("--api"); - - if (this.path != null && this.path != "") - { - parts.Add("--path"); - parts.Add(Quote(this.path)); - } - - if (this.bySteamId) - { - parts.Add("--by-steam-id"); - } - - if (this.steamId != null) - { - parts.Add("--steam-id"); - parts.Add(this.steamId.ToString()); - } - - if (this.backup != null) - { - parts.Add("--backup"); - parts.Add(Quote(this.backup)); - } - - if (this.findBackup) - { - parts.Add("--backup"); - } - - if (this.normalized) - { - parts.Add("--normalized"); - } - - if (this.mode == Mode.Backup && version.supportsCustomizingBackupFormat()) - { - if (settings.OverrideBackupFormat) - { - parts.Add("--format"); - switch (settings.BackupFormat) - { - case BackupFormatType.Simple: - parts.Add("simple"); - break; - case BackupFormatType.Zip: - parts.Add("zip"); - break; - } - } - if (settings.OverrideBackupCompression) - { - parts.Add("--compression"); - switch (settings.BackupCompression) - { - case BackupCompressionType.None: - parts.Add("none"); - break; - case BackupCompressionType.Deflate: - parts.Add("deflate"); - break; - case BackupCompressionType.Bzip2: - parts.Add("bzip2"); - break; - case BackupCompressionType.Zstd: - parts.Add("zstd"); - break; - } - } - if (settings.OverrideBackupRetention) - { - parts.Add("--full-limit"); - parts.Add(settings.FullBackupLimit.ToString()); - parts.Add("--differential-limit"); - parts.Add(settings.DifferentialBackupLimit.ToString()); - } - } - - if (this.games.Count > 0) - { - parts.Add("--"); - foreach (var game in this.games) - { - parts.Add(Quote(game.Replace("\"", "\"\""))); - } - } - - return String.Join(" ", parts); - } - } - public enum Choice { Yes, @@ -317,7 +107,7 @@ public static bool ShouldAsk(this PlayPreferences prefs, OperationTiming timing, public struct OperationResult { public string Name; - public ApiResponse Response; + public Cli.Output.Response Response; } public enum BackupCriteria @@ -339,6 +129,14 @@ public static bool ByPlatform(this BackupCriteria criteria) } } + public enum RefreshContext + { + Startup, + EditedConfig, + ConfiguredTitle, + CreatedBackup, + } + public enum OperationTiming { Free, @@ -352,167 +150,4 @@ public struct RestorationError public string Message; public bool Empty; } - - public class LudusaviVersion - { - public Version version; - - public LudusaviVersion(Version version) - { - this.version = version; - } - - public bool supportsMultiBackup() - { - return this.version >= new Version(0, 12, 0); - } - - public bool supportsRestoreBySteamId() - { - // This version fixed a defect when restoring by Steam ID. - return this.version >= new Version(0, 12, 0); - } - - public bool supportsFindCommand() - { - return this.version >= new Version(0, 14, 0); - } - - public bool supportsApiCommand() - { - return this.version >= new Version(0, 24, 0); - } - - public bool supportsCustomizingBackupFormat() - { - return this.version >= new Version(0, 14, 0); - } - - public bool supportsManifestShow() - { - return this.version >= new Version(0, 16, 0); - } - - public bool requiresMergeFlag() - { - return this.version < new Version(0, 18, 0); - } - - public bool hasGlobalManifestUpdateFlag() - { - return this.version >= new Version(0, 18, 0); - } - } - - public struct ApiEmptyConcern - { } - - public struct ApiErrors - { - [JsonProperty("someGamesFailed")] - public bool SomeGamesFailed; - [JsonProperty("unknownGames")] - public List UnknownGames; - [JsonProperty("cloudConflict")] - public ApiEmptyConcern? CloudConflict; - [JsonProperty("cloudSyncFailed")] - public ApiEmptyConcern? CloudSyncFailed; - } - - public struct ApiOverall - { - [JsonProperty("totalGames")] - public int TotalGames; - [JsonProperty("totalBytes")] - public ulong TotalBytes; - [JsonProperty("processedGames")] - public int ProcessedGames; - [JsonProperty("processedBytes")] - public ulong ProcessedBytes; - [JsonProperty("changedGames")] - public ApiChangeCount? ChangedGames; - } - - public struct ApiChangeCount - { - [JsonProperty("new")] - public int New; - [JsonProperty("different")] - public ulong Different; - [JsonProperty("same")] - public int Same; - } - - public struct ApiFile - { - [JsonProperty("failed")] - public bool Failed; - [JsonProperty("bytes")] - public ulong Bytes; - [JsonProperty("change")] - public string Change; - } - - public struct ApiRegistry - { - [JsonProperty("failed")] - public bool Failed; - } - - public struct ApiBackup - { - [JsonProperty("name")] - public string Name; - [JsonProperty("when")] - public DateTime When; - [JsonProperty("comment")] - public string Comment; - [JsonProperty("os")] - public string Os; - } - - public struct ApiGame - { - [JsonProperty("decision")] - public string Decision; - [JsonProperty("change")] - public string Change; - [JsonProperty("files")] - public Dictionary Files; - [JsonProperty("registry")] - public Dictionary Registry; - [JsonProperty("backups")] - public List Backups; - [JsonProperty("backupPath")] - public string BackupPath; - } - - public struct ApiResponse - { - [JsonProperty("errors")] - public ApiErrors Errors; - [JsonProperty("overall")] - public ApiOverall Overall; - [JsonProperty("games")] - public Dictionary Games; - } - - public class Manifest : Dictionary - { } - - public struct ManifestGame - { - [JsonProperty("files")] - public Dictionary Files; - [JsonProperty("registry")] - public Dictionary Registry; - [JsonProperty("steam")] - public ManifestSteam Steam; - } - - public class ManifestSteam - { - [JsonProperty("id")] - public int? Id; - } } diff --git a/src/Translator.cs b/src/Translator.cs index 83d79d4..95b8fb8 100644 --- a/src/Translator.cs +++ b/src/Translator.cs @@ -463,7 +463,7 @@ public string RestoreAllGames_Failure(OperationResult result) ) + ChangeSummary(result); } - public string FullListGameLineItem(string name, ApiGame game) + public string FullListGameLineItem(string name, Cli.Output.Game game) { var size = AdjustedSize(Convert.ToUInt64(game.Files.Sum(x => Convert.ToDecimal(x.Value.Bytes)))); var failed = game.Files.Any(x => x.Value.Failed) || game.Registry.Any(x => x.Value.Failed);