From 401234fa7edae57335dff174811c4e4437ef5c91 Mon Sep 17 00:00:00 2001 From: Josef Nemec Date: Wed, 29 Mar 2017 20:05:17 +0200 Subject: [PATCH 1/8] Small text fixes #1 #2 --- source/PlayniteUI/Windows/MainWindow.xaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/PlayniteUI/Windows/MainWindow.xaml b/source/PlayniteUI/Windows/MainWindow.xaml index bd34253ef..0f915684e 100644 --- a/source/PlayniteUI/Windows/MainWindow.xaml +++ b/source/PlayniteUI/Windows/MainWindow.xaml @@ -53,12 +53,12 @@ VerticalOffset="{Binding ActualHeight, ElementName=ImageLogo}"> - - + + - - + + @@ -78,7 +78,7 @@ - Date: Wed, 29 Mar 2017 20:05:43 +0200 Subject: [PATCH 2/8] Removed games remain selected #3 --- .../Controls/GamesImagesView.xaml.cs | 21 +++++++++++++++++++ .../PlayniteUI/Controls/GamesListView.xaml.cs | 20 ++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/source/PlayniteUI/Controls/GamesImagesView.xaml.cs b/source/PlayniteUI/Controls/GamesImagesView.xaml.cs index 13dff76a2..0ffaede52 100644 --- a/source/PlayniteUI/Controls/GamesImagesView.xaml.cs +++ b/source/PlayniteUI/Controls/GamesImagesView.xaml.cs @@ -15,6 +15,7 @@ using System.Windows.Shapes; using Playnite.Database; using Playnite.Models; +using System.Collections.ObjectModel; namespace PlayniteUI.Controls { @@ -35,6 +36,12 @@ public IEnumerable ItemsSource set { ItemsView.ItemsSource = value; + + if (value is ObservableCollection) + { + ((ObservableCollection)value).CollectionChanged -= GamesGridView_CollectionChanged; + ((ObservableCollection)value).CollectionChanged += GamesGridView_CollectionChanged; + } } } @@ -43,6 +50,20 @@ public GamesImagesView() InitializeComponent(); } + private void GamesGridView_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + if (e.Action != System.Collections.Specialized.NotifyCollectionChangedAction.Remove) + { + return; + } + + if (e.OldItems.Contains(GameDetails.DataContext)) + { + GameDetails.DataContext = null; + CloseDetailBorder_MouseLeftButtonDown(this, null); + } + } + private void CloseDetailBorder_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { lastDetailsWidht = ColumnDetails.Width; diff --git a/source/PlayniteUI/Controls/GamesListView.xaml.cs b/source/PlayniteUI/Controls/GamesListView.xaml.cs index 2fcc7627f..b1b1d1858 100644 --- a/source/PlayniteUI/Controls/GamesListView.xaml.cs +++ b/source/PlayniteUI/Controls/GamesListView.xaml.cs @@ -15,6 +15,7 @@ using System.Windows.Shapes; using Playnite.Database; using Playnite.Models; +using System.Collections.ObjectModel; namespace PlayniteUI.Controls { @@ -33,6 +34,12 @@ public IEnumerable ItemsSource set { ListGames.ItemsSource = value; + + if (value is ObservableCollection) + { + ((ObservableCollection)value).CollectionChanged -= GamesGridView_CollectionChanged; + ((ObservableCollection)value).CollectionChanged += GamesGridView_CollectionChanged; + } } } @@ -41,6 +48,19 @@ public GamesListView() InitializeComponent(); } + private void GamesGridView_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + if (e.Action != System.Collections.Specialized.NotifyCollectionChangedAction.Remove) + { + return; + } + + if (e.OldItems.Contains(GameDetails.DataContext)) + { + GameDetails.DataContext = null; + } + } + private void GamesListList_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (ListGames.SelectedItem == null) From e78ec728b80dfac7126b3f4e03f8668be3cc5596 Mon Sep 17 00:00:00 2001 From: Josef Nemec Date: Thu, 30 Mar 2017 22:17:12 +0200 Subject: [PATCH 3/8] Fixed various issues related to games loading games. Siwtched from MSTest to Nunit. --- source/Playnite/Database/GameDatabase.cs | 492 ++++++------------ source/Playnite/Playnite.csproj | 10 +- .../Providers/GOG/{GOG.cs => GogLibrary.cs} | 134 +++-- source/Playnite/Providers/GOG/IGogLibrary.cs | 23 + source/Playnite/Providers/GameMetadata.cs | 27 + .../Providers/Origin/IOriginLibrary.cs | 25 + .../Origin/{Origin.cs => OriginLibrary.cs} | 97 +++- .../Playnite/Providers/Steam/ISteamLibrary.cs | 25 + .../Steam/{Steam.cs => SteamLibrary.cs} | 167 ++++-- .../Database/GameDatabaseTests.cs | 313 +++++++++-- source/PlayniteTests/GamesStatsTests.cs | 6 +- .../MetaProviders/WikipediaTests.cs | 6 +- source/PlayniteTests/PlayniteTests.csproj | 21 +- source/PlayniteTests/ProgramsTests.cs | 6 +- .../GOG/{GogTests.cs => GogLibraryTests.cs} | 21 +- .../Providers/GOG/WebApiClientTests.cs | 8 +- .../{OriginTests.cs => OriginLibraryTests.cs} | 37 +- .../Providers/Origin/WebApiClientTests.cs | 8 +- .../Providers/Steam/SteamApiClientTests.cs | 6 +- .../{SteamTests.cs => SteamLibraryTests.cs} | 24 +- .../Providers/Steam/WebApiClientTests.cs | 6 +- source/PlayniteTests/SettingsTests.cs | 14 +- source/PlayniteTests/packages.config | 3 + .../PlayniteUI/Controls/GamePopupMenu.xaml.cs | 2 +- .../Controls/GamePopupMenuMulti.xaml.cs | 4 +- .../Controls/GamesImagesView.xaml.cs | 22 +- .../PlayniteUI/Controls/GamesListView.xaml.cs | 20 +- source/PlayniteUI/GamesEditor.cs | 6 +- .../Windows/CategoryConfigWindow.xaml.cs | 4 +- .../PlayniteUI/Windows/GameEditWindow.xaml.cs | 4 +- source/PlayniteUI/Windows/MainWindow.xaml.cs | 56 +- .../ImageUrlToImageSourceConverterTests.cs | 15 +- source/PlayniteUITests/GamesEditorTests.cs | 6 +- source/PlayniteUITests/PlayniteUITests.csproj | 9 +- source/PlayniteUITests/packages.config | 1 + 35 files changed, 1014 insertions(+), 614 deletions(-) rename source/Playnite/Providers/GOG/{GOG.cs => GogLibrary.cs} (57%) create mode 100644 source/Playnite/Providers/GOG/IGogLibrary.cs create mode 100644 source/Playnite/Providers/GameMetadata.cs create mode 100644 source/Playnite/Providers/Origin/IOriginLibrary.cs rename source/Playnite/Providers/Origin/{Origin.cs => OriginLibrary.cs} (67%) create mode 100644 source/Playnite/Providers/Steam/ISteamLibrary.cs rename source/Playnite/Providers/Steam/{Steam.cs => SteamLibrary.cs} (51%) rename source/PlayniteTests/Providers/GOG/{GogTests.cs => GogLibraryTests.cs} (78%) rename source/PlayniteTests/Providers/Origin/{OriginTests.cs => OriginLibraryTests.cs} (54%) rename source/PlayniteTests/Providers/Steam/{SteamTests.cs => SteamLibraryTests.cs} (71%) diff --git a/source/Playnite/Database/GameDatabase.cs b/source/Playnite/Database/GameDatabase.cs index 1ae86ed87..9908a61de 100644 --- a/source/Playnite/Database/GameDatabase.cs +++ b/source/Playnite/Database/GameDatabase.cs @@ -11,9 +11,39 @@ using Playnite.Providers.Steam; using Playnite.Providers.Origin; using System.Windows; +using Playnite.Providers; namespace Playnite.Database { + public class FileDefinition + { + public string Path + { + get; set; + } + + public string Name + { + get; set; + } + + public byte[] Data + { + get; set; + } + + public FileDefinition() + { + } + + public FileDefinition(string path, string name, byte[] data) + { + Path = path; + Name = name; + Data = data; + } + } + public class GameDatabase { // LiteDB file storage is not thread safe, so we need to lock all file operations. @@ -41,7 +71,7 @@ public LiteDatabase Database return database; } } - + private LiteCollection dbGames; private ObservableCollection games = new ObservableCollection(); @@ -53,13 +83,36 @@ public ObservableCollection Games } } + private IGogLibrary gogLibrary; + private ISteamLibrary steamLibrary; + private IOriginLibrary originLibrary; + + public string SteamUserName + { + get; set; + } = string.Empty; + + public GameDatabase() + { + gogLibrary = new GogLibrary(); + steamLibrary = new SteamLibrary(); + originLibrary = new OriginLibrary(); + } + + public GameDatabase(IGogLibrary gogLibrary, ISteamLibrary steamLibrary, IOriginLibrary originLibrary) + { + this.gogLibrary = gogLibrary; + this.steamLibrary = steamLibrary; + this.originLibrary = originLibrary; + } + private void CheckDbState() { if (dbGames == null) { throw new Exception("Database is not opened."); } - } + } public LiteDatabase OpenDatabase(string path, bool loadGames = false) { @@ -94,13 +147,25 @@ public void LoadGamesFromDb(Settings settings) { continue; } + else if (game.Provider == Provider.Steam && !game.IsInstalled && !settings.SteamSettings.LibraryDownloadEnabled) + { + continue; + } - if (game.Provider == Provider.GOG && !settings.GOGSettings.IntegrationEnabled) + if (game.Provider == Provider.GOG && !settings.OriginSettings.IntegrationEnabled) + { + continue; + } + else if (game.Provider == Provider.GOG && !game.IsInstalled && !settings.OriginSettings.LibraryDownloadEnabled) { continue; } - if (game.Provider == Provider.Origin && !settings.OriginSettings.IntegrationEnabled) + if (game.Provider == Provider.Origin && !settings.GOGSettings.IntegrationEnabled) + { + continue; + } + else if (game.Provider == Provider.Origin && !game.IsInstalled && !settings.GOGSettings.LibraryDownloadEnabled) { continue; } @@ -157,7 +222,12 @@ public void DeleteGame(IGame game) dbGames.Delete(game.Id); } - games.Remove(game); + var existingGame = games.FirstOrDefault(a => a.ProviderId == game.ProviderId && a.Provider == game.Provider); + + if (existingGame != null) + { + games.Remove(existingGame); + } } public void AddImage(string id, string name, byte[] data) @@ -232,7 +302,7 @@ public void DeleteImageSafe(string id, IGame game) Database.FileStorage.Delete(id); } - public void UpdateGame(IGame game) + public void UpdateGameInDatabase(IGame game) { CheckDbState(); @@ -247,392 +317,162 @@ public void UpdateGame(IGame game) } } + public void UnloadNotInstalledGames(Provider provider) + { + var notInstalledGames = games.Where(a => a.Provider == provider && !a.IsInstalled).ToList(); + + foreach (var game in notInstalledGames) + { + games.Remove(game); + } + } + public void UpdateGameWithMetadata(IGame game) { + GameMetadata metadata; + switch (game.Provider) { case Provider.Steam: - UpdateSteamGameWithMetadata(game); + metadata = steamLibrary.UpdateGameWithMetadata(game); break; case Provider.GOG: - UpdateGogGameWithMetadata(game); + metadata = gogLibrary.UpdateGameWithMetadata(game); break; case Provider.Origin: - UpdateOriginGameWithMetadata(game); + metadata = originLibrary.UpdateGameWithMetadata(game); break; case Provider.Custom: - break; + return; default: - break; - } - } - - #region Origin - public void UpdateOriginGameWithMetadata(IGame game) - { - var metadata = Origin.DownloadGameMetadata(game.ProviderId); - game.Name = metadata.StoreDetails.i18n.displayName.Replace("™", ""); - game.CommunityHubUrl = metadata.StoreDetails.i18n.gameForumURL; - game.StoreUrl = "https://www.origin.com/store" + metadata.StoreDetails.offerPath; - game.WikiUrl = @"http://pcgamingwiki.com/w/index.php?search=" + game.Name; - game.Description = metadata.StoreDetails.i18n.longDescription; - game.Developers = new List() { metadata.StoreDetails.developerFacetKey }; - game.Publishers = new List() { metadata.StoreDetails.publisherFacetKey }; - game.ReleaseDate = metadata.StoreDetails.platforms.First(a => a.platform == "PCWIN").releaseDate; - - if (!string.IsNullOrEmpty(metadata.StoreDetails.i18n.gameManualURL)) - { - game.OtherTasks = new ObservableCollection() - { - new GameTask() - { - IsBuiltIn = true, - Type = GameTaskType.URL, - Path = metadata.StoreDetails.i18n.gameManualURL, - Name = "Manual" - } - }; - } - - var image = string.Format("images/origin/{0}/{1}", game.ProviderId.Replace(":", ""), metadata.Image.Name); - AddImage(image, metadata.Image.Name, metadata.Image.Data); - game.Image = image; - - // There's not icon available on Origin servers so we will load one from EXE - if (game.IsInstalled && string.IsNullOrEmpty(game.Icon)) - { - var exeIcon = IconExtension.ExtractIconFromExe(game.PlayTask.Path, true); - if (exeIcon != null) - { - var iconName = Guid.NewGuid() + ".png"; - var iconId = string.Format("images/origin/{0}/{1}", game.ProviderId.Replace(":", ""), iconName); - AddImage(iconId, iconName, exeIcon.ToByteArray(System.Drawing.Imaging.ImageFormat.Png)); - game.Icon = iconId; - } + return; } - game.IsProviderDataUpdated = true; - UpdateGame(game); - } - - public void UpdateOriginInstalledGames() - { - var importedGames = Origin.GetInstalledGames(true); - - foreach (var game in importedGames) + if (metadata.Icon != null) { - var existingGame = Games.FirstOrDefault(a => a.ProviderId == game.ProviderId); - - if (existingGame == null) - { - AddGame(game); - } - else - { - existingGame.PlayTask = game.PlayTask; - existingGame.InstallDirectory = game.InstallDirectory; - UpdateGame(existingGame); - } + AddImage(metadata.Icon.Path, metadata.Icon.Name, metadata.Icon.Data); + game.Icon = metadata.Icon.Path; } - foreach (var game in Games.Where(a => a.Provider == Provider.Origin)) + if (metadata.Image != null) { - if (importedGames.FirstOrDefault(a => a.ProviderId == game.ProviderId) == null) - { - game.PlayTask = null; - game.InstallDirectory = string.Empty; - UpdateGame(game); - } + AddImage(metadata.Image.Path, metadata.Image.Name, metadata.Image.Data); + game.Image = metadata.Image.Path; } - } - public void UpdateOriginLibrary() - { - var importedGames = Origin.GetOwnedGames(); - foreach (var game in importedGames.Where(a => a.offerType == "basegame")) - { - var existingGame = Games.FirstOrDefault(a => a.ProviderId == game.offerId); - if (existingGame == null) - { - AddGame(new Game() - { - Provider = Provider.Origin, - ProviderId = game.offerId, - Name = game.offerId - }); - } - } + UpdateGameInDatabase(game); } - #endregion Origin - #region GOG - public void UpdateGogGameWithMetadata(IGame game) + public void UpdateInstalledGames(Provider provider) { - var metadata = Gog.DownloadGameMetadata(game.ProviderId, game.StoreUrl); - game.Name = metadata.GameDetails.title; - game.CommunityHubUrl = metadata.GameDetails.links.forum; - game.StoreUrl = string.IsNullOrEmpty(game.StoreUrl) ? metadata.GameDetails.links.product_card : game.StoreUrl; - game.WikiUrl = @"http://pcgamingwiki.com/w/index.php?search=" + metadata.GameDetails.title; - game.Description = metadata.GameDetails.description.full; - - if (metadata.StoreDetails != null) - { - game.Genres = metadata.StoreDetails.genres.Select(a => a.name).ToList(); - game.Developers = new List() { metadata.StoreDetails.developer.name }; - game.Publishers = new List() { metadata.StoreDetails.publisher.name }; - - if (game.ReleaseDate == null) - { - Int64 intDate = Convert.ToInt64(metadata.StoreDetails.releaseDate) * 1000; - game.ReleaseDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(intDate).ToUniversalTime(); - } - } - - var icon = string.Format("images/gog/{0}/{1}", game.ProviderId, metadata.Icon.Name); - AddImage(icon, metadata.Icon.Name, metadata.Icon.Data); - game.Icon = icon; - - using (var imageStream = new MemoryStream()) - { - using (var tempStream = new MemoryStream(metadata.Image.Data)) - { - using (var backStream = Application.GetResourceStream(new Uri("pack://application:,,,/Playnite;component/Resources/Images/gog_cover_background.png")).Stream) - { - CoverCreator.CreateCover(backStream, tempStream, imageStream); - imageStream.Seek(0, SeekOrigin.Begin); - } - } - - var image = string.Format("images/gog/{0}/{1}", game.ProviderId, metadata.Image.Name); - AddImage(image, metadata.Image.Name, imageStream.ToArray()); - game.Image = image; - } + List installedGames = null; - if (!string.IsNullOrEmpty(metadata.BackgroundImage)) + switch (provider) { - game.BackgroundImage = metadata.BackgroundImage; + case Provider.Custom: + return; + case Provider.GOG: + installedGames = gogLibrary.GetInstalledGames(); + break; + case Provider.Origin: + installedGames = originLibrary.GetInstalledGames(true); + break; + case Provider.Steam: + installedGames = steamLibrary.GetInstalledGames(); + break; + default: + return; } - game.IsProviderDataUpdated = true; - UpdateGame(game); - } - - public void UpdateGogInstalledGames() - { - var importedGames = Gog.GetInstalledGames(); - - foreach (var game in importedGames) + foreach (var newGame in installedGames) { - var existingGame = Games.FirstOrDefault(a => a.ProviderId == game.ProviderId); + var existingGame = Games.FirstOrDefault(a => a.ProviderId == newGame.ProviderId); if (existingGame == null) { - AddGame(game); + AddGame(newGame); } else { - existingGame.PlayTask = game.PlayTask; - existingGame.OtherTasks = game.OtherTasks; - existingGame.InstallDirectory = game.InstallDirectory; - UpdateGame(existingGame); - } - } - - foreach (var game in Games.Where(a => a.Provider == Provider.GOG)) - { - if (importedGames.FirstOrDefault(a => a.ProviderId == game.ProviderId) == null) - { - game.PlayTask = null; - game.OtherTasks = null; - game.InstallDirectory = string.Empty; - UpdateGame(game); - } - } - } + existingGame.PlayTask = newGame.PlayTask; + existingGame.InstallDirectory = newGame.InstallDirectory; - public void UpdateGogLibrary() - { - var importedGames = Gog.GetOwnedGames(); - foreach (var game in importedGames) - { - var existingGame = Games.FirstOrDefault(a => a.ProviderId == game.id.ToString()); - if (existingGame == null) - { - // User library has more accurate data (like release date and url), - // so we will update them during import and not impor them again during metadata update - AddGame(new Game() + if (newGame.OtherTasks != null) { - Provider = Provider.GOG, - ProviderId = game.id.ToString(), - Name = game.title, - ReleaseDate = game.releaseDate.date, - StoreUrl = @"https://www.gog.com" + game.url - }); - } - } - } - #endregion GOG - - #region Steam - - public void UpdateSteamGameWithMetadata(IGame game) - { - var metadata = Steam.DownloadGameMetadata(int.Parse(game.ProviderId)); - game.Name = metadata.ProductDetails["common"]["name"].Value; - game.CommunityHubUrl = @"https://steamcommunity.com/app/" + game.ProviderId; - game.StoreUrl = @"http://store.steampowered.com/app/" + game.ProviderId; - game.WikiUrl = @"http://pcgamingwiki.com/api/appid.php?appid=" + game.ProviderId; - - if (metadata.StoreDetails != null) - { - game.Description = metadata.StoreDetails.detailed_description; - game.Genres = metadata.StoreDetails.genres?.Select(a => a.description).ToList(); - game.Developers = metadata.StoreDetails.developers; - game.Publishers = metadata.StoreDetails.publishers; - game.ReleaseDate = metadata.StoreDetails.release_date.date; - } + existingGame.OtherTasks = new ObservableCollection(existingGame.OtherTasks.Where(a => !a.IsBuiltIn)); + foreach (var task in newGame.OtherTasks.Reverse()) + { + existingGame.OtherTasks.Insert(0, task); + } - var tasks = new ObservableCollection(); - var launchList = metadata.ProductDetails["config"]["launch"].Children; - foreach (var task in launchList.Skip(1)) - { - var properties = task["config"]; - if (properties.Name != null) - { - if (properties["oslist"].Name != null) - { - if (properties["oslist"].Value != "windows") + if (provider == Provider.Steam) { - continue; + foreach (var task in existingGame.OtherTasks.Where(a => a.Type == GameTaskType.File && a.IsBuiltIn)) + { + task.WorkingDir = newGame.InstallDirectory; + } } } - } - // Ignore action without name - shoudn't be visible to end user - if (task["description"].Name != null) - { - var newTask = new GameTask() - { - Name = task["description"].Value, - Arguments = task["arguments"].Value ?? string.Empty, - Path = task["executable"].Value, - IsBuiltIn = true, - WorkingDir = game.InstallDirectory - }; - - tasks.Add(newTask); + UpdateGameInDatabase(existingGame); } } - var manual = metadata.ProductDetails["extended"]["gamemanualurl"]; - if (manual.Name != null) + // No longer installed games must be updated + foreach (var game in Games.Where(a => a.Provider == provider)) { - tasks.Add((new GameTask() + if (installedGames.FirstOrDefault(a => a.ProviderId == game.ProviderId) == null) { - Name = "Manual", - Type = GameTaskType.URL, - Path = manual.Value, - IsBuiltIn = true - })); - } - - game.OtherTasks = tasks; - - if (metadata.Icon != null) - { - var icon = string.Format("images/steam/{0}/{1}", game.ProviderId, metadata.Icon.Name); - AddImage(icon, metadata.Icon.Name, metadata.Icon.Data); - game.Icon = icon; - } - - if (metadata.Image != null) - { - //var image = string.Format("images/steam/{0}/{1}", game.ProviderId, metadata.Image.Name); - //AddImage(image, metadata.Image.Name, metadata.Image.Data); - //game.Image = image; - - using (var imageStream = new MemoryStream()) - { - using (var tempStream = new MemoryStream(metadata.Image.Data)) + game.PlayTask = null; + game.InstallDirectory = string.Empty; + if (game.OtherTasks != null) { - using (var backStream = Application.GetResourceStream(new Uri("pack://application:,,,/Playnite;component/Resources/Images/steam_cover_background.png")).Stream) - { - CoverCreator.CreateCover(backStream, tempStream, imageStream); - imageStream.Seek(0, SeekOrigin.Begin); - } + game.OtherTasks = new ObservableCollection(game.OtherTasks.Where(a => !a.IsBuiltIn)); } - var image = string.Format("images/steam/{0}/{1}", game.ProviderId, metadata.Image.Name); - AddImage(image, metadata.Image.Name, imageStream.ToArray()); - game.Image = image; + UpdateGameInDatabase(game); } } - - if (!string.IsNullOrEmpty(metadata.BackgroundImage)) - { - game.BackgroundImage = metadata.BackgroundImage; - } - - game.IsProviderDataUpdated = true; - UpdateGame(game); } - public void UpdateSteamInstalledGames() + public void UpdateOwnedGames(Provider provider) { - var importedGames = Steam.GetInstalledGames(); - foreach (var game in importedGames) - { - var existingGame = Games.FirstOrDefault(a => a.ProviderId == game.ProviderId); + List importedGames = null; - if (existingGame == null) - { - AddGame(game); - } - else - { - existingGame.PlayTask = game.PlayTask; - existingGame.InstallDirectory = game.InstallDirectory; - - if (existingGame.OtherTasks != null) - { - foreach (var task in existingGame.OtherTasks.Where(a => a.Type == GameTaskType.File && a.IsBuiltIn)) - { - task.WorkingDir = game.InstallDirectory; - } - } - - UpdateGame(existingGame); - } + switch (provider) + { + case Provider.Custom: + return; + case Provider.GOG: + importedGames = gogLibrary.GetLibraryGames(); + break; + case Provider.Origin: + importedGames = originLibrary.GetLibraryGames(); + break; + case Provider.Steam: + importedGames = steamLibrary.GetLibraryGames(SteamUserName); + break; + default: + return; } - foreach (var game in Games.Where(a => a.Provider == Provider.Steam)) + foreach (var game in importedGames) { - if (importedGames.FirstOrDefault(a => a.ProviderId == game.ProviderId) == null) + var gameNotPresent = Games.FirstOrDefault(a => a.ProviderId == game.ProviderId && a.Provider == provider) == null; + if (gameNotPresent) { - game.PlayTask = null; - game.InstallDirectory = string.Empty; - UpdateGame(game); + AddGame(game); } } - } - public void UpdateSteamLibrary(string userName) - { - var importedGames = Steam.GetOwnedGames(userName); - foreach (var game in importedGames) + // Delete games that are no longer in library + foreach (IGame dbGame in dbGames.FindAll().Where(a => a.Provider == provider)) { - var existingGame = Games.FirstOrDefault(a => a.ProviderId == game.appid.ToString()); - if (existingGame == null) + if (importedGames.FirstOrDefault(a => a.ProviderId == dbGame.ProviderId) == null) { - AddGame(new Game() - { - Provider = Provider.Steam, - ProviderId = game.appid.ToString(), - Name = game.name - }); + DeleteGame(dbGame); } } } - #endregion Steam } } diff --git a/source/Playnite/Playnite.csproj b/source/Playnite/Playnite.csproj index 3d755608d..4878294fb 100644 --- a/source/Playnite/Playnite.csproj +++ b/source/Playnite/Playnite.csproj @@ -141,23 +141,27 @@ - + + LoginWindow.xaml + - + + - + + diff --git a/source/Playnite/Providers/GOG/GOG.cs b/source/Playnite/Providers/GOG/GogLibrary.cs similarity index 57% rename from source/Playnite/Providers/GOG/GOG.cs rename to source/Playnite/Providers/GOG/GogLibrary.cs index 17f320259..f54c70de2 100644 --- a/source/Playnite/Providers/GOG/GOG.cs +++ b/source/Playnite/Providers/GOG/GogLibrary.cs @@ -1,41 +1,35 @@ -using System; +using Newtonsoft.Json; +using NLog; +using Playnite.Database; +using Playnite.Models; +using Playnite.Providers; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.ComponentModel; using System.Data.SQLite; -using System.Diagnostics; using System.IO; using System.Linq; -using System.Text; -using Newtonsoft.Json; -using NLog; -using Playnite.Models; +using System.Windows; namespace Playnite.Providers.GOG { - public class Gog + public class GogLibrary : IGogLibrary { - private static Logger logger = LogManager.GetCurrentClassLogger(); + private Logger logger = LogManager.GetCurrentClassLogger(); - public static List UserLibraryCache - { - get; - set; - } - - public static void CacheGogDatabases(string targetPath, string dbfile) + public void CacheGogDatabases(string targetPath, string dbfile) { FileSystem.CreateFolder(targetPath); var source = Path.Combine(GogSettings.DBStoragePath, dbfile); File.Copy(source, Path.Combine(targetPath, dbfile), true); } - public static List GetInstalledGames() + public List GetInstalledGames() { var targetIndexPath = Path.Combine(Paths.TempPath, "index.db"); CacheGogDatabases(Paths.TempPath, "index.db"); - var games = new List(); + var games = new List(); var db = new SQLiteConnection(@"Data Source=" + targetIndexPath); db.Open(); @@ -116,7 +110,7 @@ public static List GetInstalledGames() return games; } - public static List GetOwnedGames() + public List GetLibraryGames() { var api = new WebApiClient(); if (api.GetLoginRequired()) @@ -124,10 +118,24 @@ public static List GetInstalledGames() throw new Exception("User is not logged in."); } - return api.GetOwnedGames(); + var games = new List(); + + foreach (var game in api.GetOwnedGames()) + { + games.Add(new Game() + { + Provider = Provider.GOG, + ProviderId = game.id.ToString(), + Name = game.title, + ReleaseDate = game.releaseDate.date, + StoreUrl = @"https://www.gog.com" + game.url + }); + } + + return games; } - public static GogGameMetadata DownloadGameMetadata(string id, string storeUrl = null) + public GogGameMetadata DownloadGameMetadata(string id, string storeUrl = null) { var metadata = new GogGameMetadata(); var gameDetail = WebApiClient.GetGameDetails(id); @@ -145,42 +153,75 @@ public static GogGameMetadata DownloadGameMetadata(string id, string storeUrl = } var icon = Web.DownloadData("http:" + gameDetail.images.icon); + var iconName = Path.GetFileName(new Uri(gameDetail.images.icon).AbsolutePath); var image = Web.DownloadData("http:" + gameDetail.images.logo2x); + var imageName = Path.GetFileName(new Uri(gameDetail.images.logo2x).AbsolutePath); - metadata.Icon = new GogGameMetadata.ImageData() - { - Name = Path.GetFileName(new Uri(gameDetail.images.icon).AbsolutePath), - Data = icon - }; + metadata.Icon = new FileDefinition( + string.Format("images/gog/{0}/{1}", id, iconName), + iconName, + icon + ); - metadata.Image = new GogGameMetadata.ImageData() - { - Name = Path.GetFileName(new Uri(gameDetail.images.logo2x).AbsolutePath), - Data = image - }; + metadata.Image = new FileDefinition( + string.Format("images/gog/{0}/{1}", id, imageName), + imageName, + image + ); metadata.BackgroundImage = "http:" + gameDetail.images.background; } return metadata; } - } - public class GogGameMetadata - { - public class ImageData + public GogGameMetadata UpdateGameWithMetadata(IGame game) { - public string Name + var metadata = DownloadGameMetadata(game.ProviderId, game.StoreUrl); + game.Name = metadata.GameDetails.title; + game.CommunityHubUrl = metadata.GameDetails.links.forum; + game.StoreUrl = string.IsNullOrEmpty(game.StoreUrl) ? metadata.GameDetails.links.product_card : game.StoreUrl; + game.WikiUrl = @"http://pcgamingwiki.com/w/index.php?search=" + metadata.GameDetails.title; + game.Description = metadata.GameDetails.description.full; + + if (metadata.StoreDetails != null) + { + game.Genres = metadata.StoreDetails.genres.Select(a => a.name).ToList(); + game.Developers = new List() { metadata.StoreDetails.developer.name }; + game.Publishers = new List() { metadata.StoreDetails.publisher.name }; + + if (game.ReleaseDate == null) + { + Int64 intDate = Convert.ToInt64(metadata.StoreDetails.releaseDate) * 1000; + game.ReleaseDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(intDate).ToUniversalTime(); + } + } + + using (var imageStream = new MemoryStream()) { - get;set; + using (var tempStream = new MemoryStream(metadata.Image.Data)) + { + using (var backStream = Application.GetResourceStream(new Uri("pack://application:,,,/Playnite;component/Resources/Images/gog_cover_background.png")).Stream) + { + CoverCreator.CreateCover(backStream, tempStream, imageStream); + imageStream.Seek(0, SeekOrigin.Begin); + metadata.Image.Data = imageStream.ToArray(); + } + } } - public byte[] Data + if (!string.IsNullOrEmpty(metadata.BackgroundImage)) { - get; set; + game.BackgroundImage = metadata.BackgroundImage; } + + game.IsProviderDataUpdated = true; + return metadata; } + } + public class GogGameMetadata : GameMetadata + { public ProductApiDetail GameDetails { get;set; @@ -190,20 +231,5 @@ public StorePageResult.ProductDetails StoreDetails { get;set; } - - public ImageData Icon - { - get;set; - } - - public ImageData Image - { - get;set; - } - - public string BackgroundImage - { - get; set; - } } } diff --git a/source/Playnite/Providers/GOG/IGogLibrary.cs b/source/Playnite/Providers/GOG/IGogLibrary.cs new file mode 100644 index 000000000..92dab1be6 --- /dev/null +++ b/source/Playnite/Providers/GOG/IGogLibrary.cs @@ -0,0 +1,23 @@ +using Playnite.Database; +using Playnite.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Playnite.Providers.GOG +{ + public interface IGogLibrary + { + List GetInstalledGames(); + + List GetLibraryGames(); + + GogGameMetadata DownloadGameMetadata(string id, string storeUrl = null); + + GogGameMetadata UpdateGameWithMetadata(IGame game); + + void CacheGogDatabases(string targetPath, string dbfile); + } +} diff --git a/source/Playnite/Providers/GameMetadata.cs b/source/Playnite/Providers/GameMetadata.cs new file mode 100644 index 000000000..20714371d --- /dev/null +++ b/source/Playnite/Providers/GameMetadata.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Playnite.Database; + +namespace Playnite.Providers +{ + public class GameMetadata + { + public FileDefinition Icon + { + get; set; + } + + public FileDefinition Image + { + get; set; + } + + public string BackgroundImage + { + get; set; + } + } +} diff --git a/source/Playnite/Providers/Origin/IOriginLibrary.cs b/source/Playnite/Providers/Origin/IOriginLibrary.cs new file mode 100644 index 000000000..2fdf42ec1 --- /dev/null +++ b/source/Playnite/Providers/Origin/IOriginLibrary.cs @@ -0,0 +1,25 @@ +using Playnite.Database; +using Playnite.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Playnite.Providers.Origin +{ + public interface IOriginLibrary + { + string GetPathFromPlatformPath(string path); + + System.Collections.Specialized.NameValueCollection ParseOriginManifest(string path); + + List GetInstalledGames(bool useDataCache = false); + + List GetLibraryGames(); + + OriginGameMetadata DownloadGameMetadata(string id); + + OriginGameMetadata UpdateGameWithMetadata(IGame game); + } +} diff --git a/source/Playnite/Providers/Origin/Origin.cs b/source/Playnite/Providers/Origin/OriginLibrary.cs similarity index 67% rename from source/Playnite/Providers/Origin/Origin.cs rename to source/Playnite/Providers/Origin/OriginLibrary.cs index 14b099dd7..3437696c2 100644 --- a/source/Playnite/Providers/Origin/Origin.cs +++ b/source/Playnite/Providers/Origin/OriginLibrary.cs @@ -11,14 +11,16 @@ using Playnite.Providers.Steam; using Microsoft.Win32; using Newtonsoft.Json; +using System.Collections.ObjectModel; +using Playnite.Database; namespace Playnite.Providers.Origin { - public class Origin + public class OriginLibrary : IOriginLibrary { - private static Logger logger = LogManager.GetCurrentClassLogger(); + private Logger logger = LogManager.GetCurrentClassLogger(); - public static string GetPathFromPlatformPath(string path) + public string GetPathFromPlatformPath(string path) { if (!path.StartsWith("[")) { @@ -67,18 +69,17 @@ public static string GetPathFromPlatformPath(string path) return Path.Combine(keyValue.ToString(), executable); } - public static System.Collections.Specialized.NameValueCollection ParseOriginManifest(string path) + public System.Collections.Specialized.NameValueCollection ParseOriginManifest(string path) { var text = File.ReadAllText(path); var data = HttpUtility.UrlDecode(text); return HttpUtility.ParseQueryString(data); } - - public static List GetInstalledGames(bool useDataCache = false) + public List GetInstalledGames(bool useDataCache = false) { var contentPath = Path.Combine(OriginPaths.DataPath, "LocalContent"); - var games = new List(); + var games = new List(); if (Directory.Exists(contentPath)) { @@ -176,7 +177,7 @@ public static List GetInstalledGames(bool useDataCache = false) return games; } - public static List GetOwnedGames() + public List GetLibraryGames() { var api = new WebApiClient(); if (api.GetLoginRequired()) @@ -196,11 +197,22 @@ public static List GetInstalledGames(bool useDataCache = false) throw new Exception("Access error: " + info.error); } - var games = api.GetOwnedGames(info.pid.pidId, token); + var games = new List(); + + foreach (var game in api.GetOwnedGames(info.pid.pidId, token).Where(a => a.offerType == "basegame")) + { + games.Add(new Game() + { + Provider = Provider.Origin, + ProviderId = game.offerId, + Name = game.offerId + }); + } + return games; } - public static OriginGameMetadata DownloadGameMetadata(string id) + public OriginGameMetadata DownloadGameMetadata(string id) { var data = new OriginGameMetadata() { @@ -210,37 +222,66 @@ public static OriginGameMetadata DownloadGameMetadata(string id) var imageUrl = data.StoreDetails.imageServer + data.StoreDetails.i18n.packArtLarge; var imageData = Web.DownloadData(imageUrl); var imageName = Guid.NewGuid() + Path.GetExtension(new Uri(imageUrl).AbsolutePath); - data.Image = new OriginGameMetadata.ImageData() - { - Data = imageData, - Name = imageName - }; + + data.Image = new FileDefinition( + string.Format("images/origin/{0}/{1}", id.Replace(":", ""), imageName), + imageName, + imageData + ); return data; } - } - public class OriginGameMetadata - { - public class ImageData + public OriginGameMetadata UpdateGameWithMetadata(IGame game) { - public string Name + var metadata = DownloadGameMetadata(game.ProviderId); + game.Name = metadata.StoreDetails.i18n.displayName.Replace("™", ""); + game.CommunityHubUrl = metadata.StoreDetails.i18n.gameForumURL; + game.StoreUrl = "https://www.origin.com/store" + metadata.StoreDetails.offerPath; + game.WikiUrl = @"http://pcgamingwiki.com/w/index.php?search=" + game.Name; + game.Description = metadata.StoreDetails.i18n.longDescription; + game.Developers = new List() { metadata.StoreDetails.developerFacetKey }; + game.Publishers = new List() { metadata.StoreDetails.publisherFacetKey }; + game.ReleaseDate = metadata.StoreDetails.platforms.First(a => a.platform == "PCWIN").releaseDate; + + if (!string.IsNullOrEmpty(metadata.StoreDetails.i18n.gameManualURL)) { - get; set; + game.OtherTasks = new ObservableCollection() + { + new GameTask() + { + IsBuiltIn = true, + Type = GameTaskType.URL, + Path = metadata.StoreDetails.i18n.gameManualURL, + Name = "Manual" + } + }; } - public byte[] Data + // There's not icon available on Origin servers so we will load one from EXE + if (game.IsInstalled && string.IsNullOrEmpty(game.Icon)) { - get; set; + var exeIcon = IconExtension.ExtractIconFromExe(game.PlayTask.Path, true); + if (exeIcon != null) + { + var iconName = Guid.NewGuid() + ".png"; + + metadata.Icon = new FileDefinition( + string.Format("images/origin/{0}/{1}", game.ProviderId.Replace(":", ""), iconName), + iconName, + exeIcon.ToByteArray(System.Drawing.Imaging.ImageFormat.Png) + ); + } } - } - public GameStoreDataResponse StoreDetails - { - get; set; + game.IsProviderDataUpdated = true; + return metadata; } + } - public ImageData Image + public class OriginGameMetadata : GameMetadata + { + public GameStoreDataResponse StoreDetails { get; set; } diff --git a/source/Playnite/Providers/Steam/ISteamLibrary.cs b/source/Playnite/Providers/Steam/ISteamLibrary.cs new file mode 100644 index 000000000..50df618f6 --- /dev/null +++ b/source/Playnite/Providers/Steam/ISteamLibrary.cs @@ -0,0 +1,25 @@ +using Playnite.Database; +using Playnite.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Playnite.Providers.Steam +{ + public interface ISteamLibrary + { + IGame GetInstalledGamesFromFile(string path); + + List GetInstalledGamesFromFolder(string path); + + List GetInstalledGames(); + + List GetLibraryGames(string userName); + + SteamGameMetadata DownloadGameMetadata(int id); + + SteamGameMetadata UpdateGameWithMetadata(IGame game); + } +} diff --git a/source/Playnite/Providers/Steam/Steam.cs b/source/Playnite/Providers/Steam/SteamLibrary.cs similarity index 51% rename from source/Playnite/Providers/Steam/Steam.cs rename to source/Playnite/Providers/Steam/SteamLibrary.cs index 41103c888..e784f70be 100644 --- a/source/Playnite/Providers/Steam/Steam.cs +++ b/source/Playnite/Providers/Steam/SteamLibrary.cs @@ -16,14 +16,16 @@ using Playnite.Providers.Steam; using SteamKit2; using Playnite.Services; +using Playnite.Database; +using System.Windows; namespace Playnite.Providers.Steam { - public class Steam + public class SteamLibrary : ISteamLibrary { - private static Logger logger = LogManager.GetCurrentClassLogger(); + private Logger logger = LogManager.GetCurrentClassLogger(); - public static Game GetInstalledGamesFromFile(string path) + public IGame GetInstalledGamesFromFile(string path) { var kv = new KeyValue(); kv.ReadFileAsText(path); @@ -60,9 +62,9 @@ public static Game GetInstalledGamesFromFile(string path) return game; } - public static List GetInstalledGamesFromFolder(string path) + public List GetInstalledGamesFromFolder(string path) { - var games = new List(); + var games = new List(); var appsFolder = Path.Combine(path, "steamapps"); foreach (var file in Directory.GetFiles(appsFolder, @"appmanifest*")) @@ -74,29 +76,41 @@ public static List GetInstalledGamesFromFolder(string path) return games; } - public static List GetInstalledGames() + public List GetInstalledGames() { - var games = new List(); + var games = new List(); foreach (var folder in SteamSettings.GameDatabases) { - games.AddRange(Steam.GetInstalledGamesFromFolder(folder)); + games.AddRange(GetInstalledGamesFromFolder(folder)); } return games; } - public static List GetOwnedGames(string userName) + public List GetLibraryGames(string userName) { if (string.IsNullOrEmpty(userName)) { throw new Exception("Steam user name cannot be empty."); + } + + var games = new List(); + + foreach (var game in (new ServicesClient()).GetSteamLibrary(userName)) + { + games.Add(new Game() + { + Provider = Provider.Steam, + ProviderId = game.appid.ToString(), + Name = game.name + }); } - - return (new ServicesClient()).GetSteamLibrary(userName); + + return games; } - public static SteamGameMetadata DownloadGameMetadata(int id) + public SteamGameMetadata DownloadGameMetadata(int id) { var metadata = new SteamGameMetadata(); var productInfo = SteamApiClient.GetProductInfo(id).GetAwaiter().GetResult(); @@ -150,12 +164,14 @@ public static SteamGameMetadata DownloadGameMetadata(int id) // There might be no icon assigned to game if (!string.IsNullOrEmpty(iconUrl)) { + var iconName = Path.GetFileName(new Uri(iconUrl).AbsolutePath); var iconData = Web.DownloadData(iconUrl); - metadata.Icon = new SteamGameMetadata.ImageData() - { - Name = Path.GetFileName(new Uri(iconUrl).AbsolutePath), - Data = iconData - }; + metadata.Icon = new FileDefinition( + + string.Format("images/steam/{0}/{1}", id.ToString(), iconName), + iconName, + iconData + ); } @@ -189,11 +205,12 @@ public static SteamGameMetadata DownloadGameMetadata(int id) if (imageData != null) { - metadata.Image = new SteamGameMetadata.ImageData() - { - Name = Path.GetFileName(new Uri(imageUrl).AbsolutePath), - Data = imageData - }; + var imageName = Path.GetFileName(new Uri(imageUrl).AbsolutePath); + metadata.Image = new FileDefinition( + string.Format("images/steam/{0}/{1}", id.ToString(), imageName), + imageName, + imageData + ); } // Background Image @@ -201,44 +218,104 @@ public static SteamGameMetadata DownloadGameMetadata(int id) return metadata; } - } - public class SteamGameMetadata - { - public class ImageData + public SteamGameMetadata UpdateGameWithMetadata(IGame game) { - public string Name + var metadata = DownloadGameMetadata(int.Parse(game.ProviderId)); + game.Name = metadata.ProductDetails["common"]["name"].Value; + game.CommunityHubUrl = @"https://steamcommunity.com/app/" + game.ProviderId; + game.StoreUrl = @"http://store.steampowered.com/app/" + game.ProviderId; + game.WikiUrl = @"http://pcgamingwiki.com/api/appid.php?appid=" + game.ProviderId; + + if (metadata.StoreDetails != null) { - get; set; + game.Description = metadata.StoreDetails.detailed_description; + game.Genres = metadata.StoreDetails.genres?.Select(a => a.description).ToList(); + game.Developers = metadata.StoreDetails.developers; + game.Publishers = metadata.StoreDetails.publishers; + game.ReleaseDate = metadata.StoreDetails.release_date.date; } - public byte[] Data + var tasks = new ObservableCollection(); + var launchList = metadata.ProductDetails["config"]["launch"].Children; + foreach (var task in launchList.Skip(1)) { - get; set; + var properties = task["config"]; + if (properties.Name != null) + { + if (properties["oslist"].Name != null) + { + if (properties["oslist"].Value != "windows") + { + continue; + } + } + } + + // Ignore action without name - shoudn't be visible to end user + if (task["description"].Name != null) + { + var newTask = new GameTask() + { + Name = task["description"].Value, + Arguments = task["arguments"].Value ?? string.Empty, + Path = task["executable"].Value, + IsBuiltIn = true, + WorkingDir = game.InstallDirectory + }; + + tasks.Add(newTask); + } } - } - public KeyValue ProductDetails - { - get;set; - } + var manual = metadata.ProductDetails["extended"]["gamemanualurl"]; + if (manual.Name != null) + { + tasks.Add((new GameTask() + { + Name = "Manual", + Type = GameTaskType.URL, + Path = manual.Value, + IsBuiltIn = true + })); + } - public StoreAppDetailsResult.AppDetails StoreDetails - { - get; set; - } + game.OtherTasks = tasks; - public ImageData Icon - { - get; set; + if (metadata.Image != null) + { + using (var imageStream = new MemoryStream()) + { + using (var tempStream = new MemoryStream(metadata.Image.Data)) + { + using (var backStream = Application.GetResourceStream(new Uri("pack://application:,,,/Playnite;component/Resources/Images/steam_cover_background.png")).Stream) + { + CoverCreator.CreateCover(backStream, tempStream, imageStream); + imageStream.Seek(0, SeekOrigin.Begin); + metadata.Image.Data = imageStream.ToArray(); + } + } + } + } + + if (!string.IsNullOrEmpty(metadata.BackgroundImage)) + { + game.BackgroundImage = metadata.BackgroundImage; + } + + game.IsProviderDataUpdated = true; + return metadata; } + } - public ImageData Image + public class SteamGameMetadata : GameMetadata + { + public KeyValue ProductDetails { - get; set; + get;set; } - public string BackgroundImage + public StoreAppDetailsResult.AppDetails StoreDetails { get; set; } diff --git a/source/PlayniteTests/Database/GameDatabaseTests.cs b/source/PlayniteTests/Database/GameDatabaseTests.cs index fef531feb..8dbf10195 100644 --- a/source/PlayniteTests/Database/GameDatabaseTests.cs +++ b/source/PlayniteTests/Database/GameDatabaseTests.cs @@ -4,25 +4,29 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; using Playnite.Database; using Playnite.Models; using Playnite; +using Moq; +using Playnite.Providers.GOG; +using Playnite.Providers.Steam; +using Playnite.Providers.Origin; +using NUnit.Framework; namespace PlayniteTests.Database { - [TestClass()] + [TestFixture] public class GameDatabaseTests { - [ClassInitialize] - public static void ClassInit(TestContext context) + [OneTimeSetUp] + public void Init() { // Some test are reading resources, which cannot be access until pack:// namespace is initialized // http://stackoverflow.com/questions/6005398/uriformatexception-invalid-uri-invalid-port-specified string s = System.IO.Packaging.PackUriHelper.UriSchemePack; } - [TestMethod] + [Test] public void ListUpdateTest() { var path = Path.Combine(Playnite.PlayniteTests.TempPath, "updatedb.db"); @@ -39,7 +43,7 @@ public void ListUpdateTest() db.AddGame(new Game() { - ProviderId = "testid", + ProviderId = "testid2", Name = "Test Game 2" }); @@ -51,12 +55,12 @@ public void ListUpdateTest() Assert.AreEqual(2, db.Games.Count); db.AddGame(new Game() { - ProviderId = "testid", + ProviderId = "testid3", Name = "Test Game 3" }); db.Games[2].Name = "Changed Name"; - db.UpdateGame(db.Games[2]); + db.UpdateGameInDatabase(db.Games[2]); } using (db.OpenDatabase(path, true)) @@ -72,7 +76,7 @@ public void ListUpdateTest() } } - [TestMethod] + [Test] public void DeleteImageSafeTest() { var path = Path.Combine(Playnite.PlayniteTests.TempPath, "deleteimagetest.db"); @@ -93,7 +97,7 @@ public void DeleteImageSafeTest() db.AddGame(new Game() { - ProviderId = "testid", + ProviderId = "testid2", Name = "Test Game 2", Icon = "testimage" }); @@ -103,13 +107,13 @@ public void DeleteImageSafeTest() // Removes image db.Games[1].Icon = string.Empty; - db.UpdateGame(db.Games[1]); + db.UpdateGameInDatabase(db.Games[1]); db.DeleteImageSafe("testimage", db.Games[0]); Assert.AreEqual(0, db.Database.FileStorage.FindAll().Count()); } } - [TestMethod] + [Test] public void DeleteGameImageCleanupTest() { var path = Path.Combine(Playnite.PlayniteTests.TempPath, "deleteimagecleanuptest.db"); @@ -137,9 +141,217 @@ public void DeleteGameImageCleanupTest() } } + [Test] + public void UnloadNotInstalledGamesTest() + { + var path = Path.Combine(Playnite.PlayniteTests.TempPath, "unloadntoinstalled.db"); + FileSystem.DeleteFile(path); + + var db = new GameDatabase(); + using (db.OpenDatabase(path, true)) + { + db.AddGame(new Game() + { + ProviderId = "testid", + Name = "Test Game", + PlayTask = new GameTask() + }); + + db.AddGame(new Game() + { + ProviderId = "testid2", + Name = "Test Game 2", + PlayTask = new GameTask() + }); + + Assert.AreEqual(2, db.Games.Count); + + db.Games[0].PlayTask = null; + db.UpdateGameInDatabase(db.Games[0]); + db.UnloadNotInstalledGames(Provider.Custom); + + Assert.AreEqual(1, db.Games.Count); + } + + using (db.OpenDatabase(path, true)) + { + Assert.AreEqual(2, db.Games.Count); + } + } + + [TestCase(Provider.GOG)] + [TestCase(Provider.Steam)] + [TestCase(Provider.Origin)] + public void UpdateOwnedGamesTest(Provider provider) + { + var path = Path.Combine(Playnite.PlayniteTests.TempPath, "ownedgames.db"); + FileSystem.DeleteFile(path); + + var libraryGames = new List() + { + new Game() + { + ProviderId = "testid", + Name = "Test Game", + Provider = provider + }, + new Game() + { + ProviderId = "testid2", + Name = "Test Game 2", + Provider = provider + } + }; + + var gogLibrary = new Mock(); + var steamLibrary = new Mock(); + var originLibrary = new Mock(); + gogLibrary.Setup(oc => oc.GetLibraryGames()).Returns(libraryGames); + steamLibrary.Setup(oc => oc.GetLibraryGames(string.Empty)).Returns(libraryGames); + originLibrary.Setup(oc => oc.GetLibraryGames()).Returns(libraryGames); + + var db = new GameDatabase(gogLibrary.Object, steamLibrary.Object, originLibrary.Object); + using (db.OpenDatabase(path, true)) + { + // Games are properly imported + db.UpdateOwnedGames(provider); + Assert.AreEqual(2, db.Games.Count); + + libraryGames.Add(new Game() + { + ProviderId = "testid3", + Name = "Test Game 3", + Provider = provider + }); + + // New library game is added to DB + db.UpdateOwnedGames(provider); + Assert.AreEqual(3, db.Games.Count); + + // Game removed from library is removed from DB + libraryGames.RemoveAt(0); + db.UpdateOwnedGames(provider); + Assert.AreEqual(2, db.Games.Count); + } + } + + [TestCase(Provider.GOG)] + [TestCase(Provider.Steam)] + [TestCase(Provider.Origin)] + public void UpdateInstalledGamesTest(Provider provider) + { + var path = Path.Combine(Playnite.PlayniteTests.TempPath, "installedgames.db"); + FileSystem.DeleteFile(path); + + var installedGames = CreateGameList(provider); + + var gogLibrary = new Mock(); + var steamLibrary = new Mock(); + var originLibrary = new Mock(); + gogLibrary.Setup(oc => oc.GetInstalledGames()).Returns(installedGames); + steamLibrary.Setup(oc => oc.GetInstalledGames()).Returns(installedGames); + originLibrary.Setup(oc => oc.GetInstalledGames(false)).Returns(installedGames); + originLibrary.Setup(oc => oc.GetInstalledGames(true)).Returns(installedGames); + + var db = new GameDatabase(gogLibrary.Object, steamLibrary.Object, originLibrary.Object); + using (db.OpenDatabase(path, true)) + { + // Games are imported + db.UpdateInstalledGames(provider); + Assert.AreEqual(2, db.Games.Count); + Assert.IsTrue(db.Games[0].IsInstalled); + + // Game is no longer install and DB reflects that + installedGames.Clear(); + installedGames.AddRange(CreateGameList(provider)); + installedGames.RemoveAt(0); + db.UpdateInstalledGames(provider); + Assert.IsFalse(db.Games[0].IsInstalled); + + // User tasks are not affected by import + installedGames.Clear(); + installedGames.AddRange(CreateGameList(provider)); + + db.Games[0].OtherTasks = new System.Collections.ObjectModel.ObservableCollection() + { + new GameTask() + { + IsBuiltIn = false, + Name = "User Task" + } + }; + + db.UpdateGameInDatabase(db.Games[0]); + db.UpdateInstalledGames(provider); + Assert.AreEqual(3, db.Games[0].OtherTasks.Count); + + installedGames.Clear(); + installedGames.AddRange(CreateGameList(provider)); + installedGames.RemoveAt(0); + db.UpdateInstalledGames(provider); + Assert.AreEqual(1, db.Games[0].OtherTasks.Count); + Assert.AreEqual("User Task", db.Games[0].OtherTasks[0].Name); + } + } + + private List CreateGameList(Provider provider) + { + return new List() + { + new Game() + { + ProviderId = "testid", + Name = "Test Game", + Provider = provider, + PlayTask = new GameTask() + { + IsBuiltIn = true, + Type = GameTaskType.File + }, + OtherTasks = new System.Collections.ObjectModel.ObservableCollection() + { + new GameTask() + { + IsBuiltIn = true, + Name = "Task 1" + }, + new GameTask() + { + IsBuiltIn = true, + Name = "Task 2" + } + } + }, + new Game() + { + ProviderId = "testid2", + Name = "Test Game 2", + Provider = provider, + PlayTask = new GameTask() + { + IsBuiltIn = true, + Type = GameTaskType.File + }, + OtherTasks = new System.Collections.ObjectModel.ObservableCollection() + { + new GameTask() + { + IsBuiltIn = true, + Name = "Task 1" + }, + new GameTask() + { + IsBuiltIn = true, + Name = "Task 2" + } + } + } + }; + } + #region GOG - [TestMethod] - public void UpdateGogInstalledGames_CleanImportTest() + [Test] + public void UpdateGogInstalledGamesCleanImportTest() { var path = Path.Combine(Playnite.PlayniteTests.TempPath, "goginstalledimportclean.db"); FileSystem.DeleteFile(path); @@ -147,13 +359,13 @@ public void UpdateGogInstalledGames_CleanImportTest() var db = new GameDatabase(); using (db.OpenDatabase(path, true)) { - db.UpdateGogInstalledGames(); + db.UpdateInstalledGames(Provider.GOG); Assert.AreNotEqual(0, db.Games.Count); } } - [TestMethod] - public void UpdateGogInstalledGames_UpdateImportTest() + [Test] + public void UpdateGogInstalledGamesUpdateImportTest() { var path = Path.Combine(Playnite.PlayniteTests.TempPath, "goginstalledimportupdate.db"); FileSystem.DeleteFile(path); @@ -161,11 +373,11 @@ public void UpdateGogInstalledGames_UpdateImportTest() var db = new GameDatabase(); using (db.OpenDatabase(path, true)) { - db.UpdateGogInstalledGames(); + db.UpdateInstalledGames(Provider.GOG); var game = db.Games[0]; game.PlayTask = null; game.InstallDirectory = @"c:\nonsense\directory\"; - db.UpdateGame(game); + db.UpdateGameInDatabase(game); } using (db.OpenDatabase(path, true)) @@ -175,7 +387,7 @@ public void UpdateGogInstalledGames_UpdateImportTest() Assert.AreEqual(@"c:\nonsense\directory\", game.InstallDirectory); var gameCount = db.Games.Count; - db.UpdateGogInstalledGames(); + db.UpdateInstalledGames(Provider.GOG); Assert.AreEqual(gameCount, db.Games.Count); game = db.Games[0]; @@ -184,7 +396,7 @@ public void UpdateGogInstalledGames_UpdateImportTest() } } - [TestMethod] + [Test] public void UpdateGogGameWithMetadataTest() { var game = new Game() @@ -218,11 +430,12 @@ public void UpdateGogGameWithMetadataTest() Assert.AreEqual(2, files.Count()); } } + #endregion GOG #region Steam - [TestMethod] - public void UpdateSteamInstalledGames_CleanImportTest() + [Test] + public void UpdateSteamInstalledGamesCleanImportTest() { var path = Path.Combine(Playnite.PlayniteTests.TempPath, "steaminstalledimportclean.db"); FileSystem.DeleteFile(path); @@ -230,13 +443,13 @@ public void UpdateSteamInstalledGames_CleanImportTest() var db = new GameDatabase(); using (db.OpenDatabase(path, true)) { - db.UpdateSteamInstalledGames(); + db.UpdateInstalledGames(Provider.Steam); Assert.AreNotEqual(0, db.Games.Count); } } - [TestMethod] - public void UpdateSteamInstalledGames_UpdateImportTest() + [Test] + public void UpdateSteamInstalledGamesUpdateImportTest() { var path = Path.Combine(Playnite.PlayniteTests.TempPath, "steaminstalledimportupdate.db"); FileSystem.DeleteFile(path); @@ -244,11 +457,11 @@ public void UpdateSteamInstalledGames_UpdateImportTest() var db = new GameDatabase(); using (db.OpenDatabase(path, true)) { - db.UpdateSteamInstalledGames(); + db.UpdateInstalledGames(Provider.Steam); var game = db.Games[0]; game.PlayTask = null; game.InstallDirectory = @"c:\nonsense\directory\"; - db.UpdateGame(game); + db.UpdateGameInDatabase(game); } using (db.OpenDatabase(path, true)) @@ -258,7 +471,7 @@ public void UpdateSteamInstalledGames_UpdateImportTest() Assert.AreEqual(@"c:\nonsense\directory\", game.InstallDirectory); var gameCount = db.Games.Count; - db.UpdateSteamInstalledGames(); + db.UpdateInstalledGames(Provider.Steam); Assert.AreEqual(gameCount, db.Games.Count); game = db.Games[0]; @@ -267,7 +480,7 @@ public void UpdateSteamInstalledGames_UpdateImportTest() } } - [TestMethod] + [Test] public void UpdateSteamGameWithMetadataTest() { var game = new Game() @@ -304,8 +517,8 @@ public void UpdateSteamGameWithMetadataTest() #endregion Steam #region Origin - [TestMethod] - public void UpdateOriginInstalledGames_CleanImportTest() + [Test] + public void UpdateOriginInstalledGamesCleanImportTest() { var path = Path.Combine(Playnite.PlayniteTests.TempPath, "origininstalledimportclean.db"); FileSystem.DeleteFile(path); @@ -313,12 +526,44 @@ public void UpdateOriginInstalledGames_CleanImportTest() var db = new GameDatabase(); using (db.OpenDatabase(path, true)) { - db.UpdateOriginInstalledGames(); + db.UpdateInstalledGames(Provider.Origin); Assert.AreNotEqual(0, db.Games.Count); } } - [TestMethod] + [Test] + public void UpdateOriginInstalledGamesUpdateImportTest() + { + var path = Path.Combine(Playnite.PlayniteTests.TempPath, "origininstalledimportupdate.db"); + FileSystem.DeleteFile(path); + + var db = new GameDatabase(); + using (db.OpenDatabase(path, true)) + { + db.UpdateInstalledGames(Provider.Origin); + var game = db.Games[0]; + game.PlayTask = null; + game.InstallDirectory = @"c:\nonsense\directory\"; + db.UpdateGameInDatabase(game); + } + + using (db.OpenDatabase(path, true)) + { + var game = db.Games[0]; + Assert.IsNull(game.PlayTask); + Assert.AreEqual(@"c:\nonsense\directory\", game.InstallDirectory); + var gameCount = db.Games.Count; + + db.UpdateInstalledGames(Provider.Origin); + Assert.AreEqual(gameCount, db.Games.Count); + + game = db.Games[0]; + Assert.IsNotNull(game.PlayTask); + Assert.AreNotEqual(@"c:\nonsense\directory\", game.InstallDirectory); + } + } + + [Test] public void UpdateOriginGameWithMetadataTest() { var game = new Game() diff --git a/source/PlayniteTests/GamesStatsTests.cs b/source/PlayniteTests/GamesStatsTests.cs index 31ea4fb76..266de9cbe 100644 --- a/source/PlayniteTests/GamesStatsTests.cs +++ b/source/PlayniteTests/GamesStatsTests.cs @@ -4,17 +4,17 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; using Playnite; using Playnite.Models; namespace PlayniteTests { - [TestClass] + [TestFixture] public class GamesStatsTests { - [TestMethod] + [Test] public void BasicTest() { var stats = new GamesStats(); diff --git a/source/PlayniteTests/MetaProviders/WikipediaTests.cs b/source/PlayniteTests/MetaProviders/WikipediaTests.cs index 0eecc2508..c3e8cba8d 100644 --- a/source/PlayniteTests/MetaProviders/WikipediaTests.cs +++ b/source/PlayniteTests/MetaProviders/WikipediaTests.cs @@ -3,13 +3,13 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; using Playnite.MetaProviders; using Playnite.Models; namespace PlayniteTests.MetaProviders { - [TestClass()] + [TestFixture] public class WikipediaTests { private void ValidateGameDate(Game game) @@ -25,7 +25,7 @@ private void ValidateBoxArt(Game game) Assert.IsTrue(!string.IsNullOrEmpty(game.Image)); } - [TestMethod()] + [Test] public void ParseGamePage_MetadataParsingTest() { var wiki = new Wikipedia(); diff --git a/source/PlayniteTests/PlayniteTests.csproj b/source/PlayniteTests/PlayniteTests.csproj index 4f57c1566..d90208eaa 100644 --- a/source/PlayniteTests/PlayniteTests.csproj +++ b/source/PlayniteTests/PlayniteTests.csproj @@ -58,6 +58,9 @@ true + + ..\packages\Castle.Core.4.0.0\lib\net45\Castle.Core.dll + ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll True @@ -70,6 +73,12 @@ ..\packages\LiteDB.3.0.1\lib\net35\LiteDB.dll True + + ..\packages\Moq.4.7.8\lib\net45\Moq.dll + + + ..\packages\NUnit.3.6.1\lib\net45\nunit.framework.dll + ..\packages\protobuf-net.2.0.0.668\lib\net40\protobuf-net.dll True @@ -100,11 +109,7 @@ - - - - - + @@ -112,13 +117,13 @@ - + - + - + diff --git a/source/PlayniteTests/ProgramsTests.cs b/source/PlayniteTests/ProgramsTests.cs index 95913dd49..d09edaf03 100644 --- a/source/PlayniteTests/ProgramsTests.cs +++ b/source/PlayniteTests/ProgramsTests.cs @@ -3,15 +3,15 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; using Playnite; namespace PlayniteTests { - [TestClass()] + [TestFixture] public class ProgramsTests { - [TestMethod()] + [Test] public void GetPrograms_StandardTest() { var apps = Programs.GetPrograms(); diff --git a/source/PlayniteTests/Providers/GOG/GogTests.cs b/source/PlayniteTests/Providers/GOG/GogLibraryTests.cs similarity index 78% rename from source/PlayniteTests/Providers/GOG/GogTests.cs rename to source/PlayniteTests/Providers/GOG/GogLibraryTests.cs index 43cb2819e..fae5c33bd 100644 --- a/source/PlayniteTests/Providers/GOG/GogTests.cs +++ b/source/PlayniteTests/Providers/GOG/GogLibraryTests.cs @@ -1,4 +1,4 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; using Playnite.Providers.GOG; using System; using System.Collections.Generic; @@ -9,14 +9,15 @@ namespace Playnite.Providers.GOG.Tests { - [TestClass()] - public class GogTests + [TestFixture] + public class GogLibraryTests { - [TestMethod()] + [Test()] [Description("Basic verification testing that installed games can be fetched from local client.")] public void GetInstalledGames_Basic() { - var games = Gog.GetInstalledGames(); + var gogLib = new GogLibrary(); + var games = gogLib.GetInstalledGames(); Assert.AreNotEqual(0, games.Count); CollectionAssert.AllItemsAreUnique(games); @@ -31,11 +32,13 @@ public void GetInstalledGames_Basic() } } - [TestMethod()] + [Test] public void DownloadGameMetadataTest() { + var gogLib = new GogLibrary(); + // Existing store page - contains all data - var existingStore = Gog.DownloadGameMetadata("1207658645"); + var existingStore = gogLib.DownloadGameMetadata("1207658645"); Assert.IsNotNull(existingStore.GameDetails); Assert.IsNotNull(existingStore.StoreDetails); Assert.IsNotNull(existingStore.Icon.Data); @@ -43,7 +46,7 @@ public void DownloadGameMetadataTest() Assert.IsNotNull(existingStore.BackgroundImage); // Game with missing store link in api data - var customStore = Gog.DownloadGameMetadata("1207662223", "https://www.gog.com/game/commandos_2_3"); + var customStore = gogLib.DownloadGameMetadata("1207662223", "https://www.gog.com/game/commandos_2_3"); Assert.IsNotNull(customStore.GameDetails); Assert.IsNotNull(customStore.StoreDetails); Assert.IsNotNull(customStore.Icon.Data); @@ -51,7 +54,7 @@ public void DownloadGameMetadataTest() Assert.IsNotNull(customStore.BackgroundImage); // Existing game not present on store - var nonStore = Gog.DownloadGameMetadata("2"); + var nonStore = gogLib.DownloadGameMetadata("2"); Assert.IsNotNull(nonStore.GameDetails); Assert.IsNull(nonStore.StoreDetails); Assert.IsNotNull(nonStore.Icon.Data); diff --git a/source/PlayniteTests/Providers/GOG/WebApiClientTests.cs b/source/PlayniteTests/Providers/GOG/WebApiClientTests.cs index ed6bf10c1..f6babddbf 100644 --- a/source/PlayniteTests/Providers/GOG/WebApiClientTests.cs +++ b/source/PlayniteTests/Providers/GOG/WebApiClientTests.cs @@ -3,15 +3,15 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; using Playnite.Providers.GOG; namespace PlayniteTests.Providers.GOG { - [TestClass()] + [TestFixture] public class WebApiClientTests { - [TestMethod()] + [Test] public void GetGameDetailsTest() { var existingDetails = WebApiClient.GetGameDetails("2"); @@ -21,7 +21,7 @@ public void GetGameDetailsTest() Assert.IsNull(nonexistingDetails); } - [TestMethod()] + [Test] public void GetGameStoreDataTest() { var existingStore = WebApiClient.GetGameStoreData(@"https://www.gog.com/game/vampire_the_masquerade_bloodlines"); diff --git a/source/PlayniteTests/Providers/Origin/OriginTests.cs b/source/PlayniteTests/Providers/Origin/OriginLibraryTests.cs similarity index 54% rename from source/PlayniteTests/Providers/Origin/OriginTests.cs rename to source/PlayniteTests/Providers/Origin/OriginLibraryTests.cs index e590e4406..48b2090a4 100644 --- a/source/PlayniteTests/Providers/Origin/OriginTests.cs +++ b/source/PlayniteTests/Providers/Origin/OriginLibraryTests.cs @@ -3,22 +3,26 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; using System.IO; +using Playnite.Providers.Origin; +using Playnite.Models; +using Playnite; -namespace Playnite.Providers.Origin.Tests +namespace PlayniteTests.Providers.Origin { - [TestClass()] - public class OriginTests + [TestFixture] + public class OriginLibraryTests { - [TestMethod()] + [Test] public void GetInstalledGamesTest() { - var games = Origin.GetInstalledGames(false); + var originLib = new OriginLibrary(); + var games = originLib.GetInstalledGames(false); Assert.AreNotEqual(0, games.Count); var game = games[0]; - Assert.AreEqual(Models.Provider.Origin, game.Provider); + Assert.AreEqual(Provider.Origin, game.Provider); Assert.IsTrue(!string.IsNullOrEmpty(game.Name)); Assert.IsTrue(!string.IsNullOrEmpty(game.ProviderId)); Assert.IsTrue(Directory.Exists(game.InstallDirectory)); @@ -26,39 +30,42 @@ public void GetInstalledGamesTest() foreach (var g in games) { - if (g.PlayTask.Type == Models.GameTaskType.File) + if (g.PlayTask.Type == GameTaskType.File) { Assert.IsTrue(File.Exists(g.PlayTask.Path)); } } } - [TestMethod()] + [Test] public void GetInstalledGamesCacheTest() { + var originLib = new OriginLibrary(); OriginPaths.CachePath = Path.Combine(Playnite.PlayniteTests.TempPath, "origincache"); FileSystem.CreateFolder(OriginPaths.CachePath, true); - var games = Origin.GetInstalledGames(true); + var games = originLib.GetInstalledGames(true); var cacheFiles = Directory.GetFiles(OriginPaths.CachePath, "*.json"); Assert.IsTrue(cacheFiles.Count() > 0); } - [TestMethod()] + [Test] public void DownloadGameMetadataTest() { - var existingStore = Origin.DownloadGameMetadata("OFB-EAST:60108"); + var originLib = new OriginLibrary(); + var existingStore = originLib.DownloadGameMetadata("OFB-EAST:60108"); Assert.IsNotNull(existingStore.StoreDetails); Assert.IsNotNull(existingStore.Image.Data); } - [TestMethod()] + [Test] public void GetPathFromPlatformPathTest() { - var path = Origin.GetPathFromPlatformPath(@"[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\PowerShell\\3\\PowerShellEngine\\ApplicationBase]\\powershell.exe"); + var originLib = new OriginLibrary(); + var path = originLib.GetPathFromPlatformPath(@"[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\PowerShell\\3\\PowerShellEngine\\ApplicationBase]\\powershell.exe"); Assert.IsTrue(File.Exists(path)); - path = Origin.GetPathFromPlatformPath(@"[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\PowerShell\\3\\PowerShellEngine\\ApplicatioBase]\\powershell.exe"); + path = originLib.GetPathFromPlatformPath(@"[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\PowerShell\\3\\PowerShellEngine\\ApplicatioBase]\\powershell.exe"); Assert.IsTrue(string.IsNullOrEmpty(path)); } } diff --git a/source/PlayniteTests/Providers/Origin/WebApiClientTests.cs b/source/PlayniteTests/Providers/Origin/WebApiClientTests.cs index ce189dcb6..a0d129478 100644 --- a/source/PlayniteTests/Providers/Origin/WebApiClientTests.cs +++ b/source/PlayniteTests/Providers/Origin/WebApiClientTests.cs @@ -3,15 +3,15 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; using Playnite.Providers.Origin; namespace PlayniteTests.Providers.Origin { - [TestClass()] + [TestFixture] public class WebApiClientTests { - [TestMethod()] + [Test] public void GetGameLocalDataTest() { var ids = new string[] { "DR:104624900", "OFB-EAST:52018" }; @@ -26,7 +26,7 @@ public void GetGameLocalDataTest() } } - [TestMethod()] + [Test] public void GetGameStoreDataTest() { var ids = new string[] { "DR:104624900", "OFB-EAST:52018" }; diff --git a/source/PlayniteTests/Providers/Steam/SteamApiClientTests.cs b/source/PlayniteTests/Providers/Steam/SteamApiClientTests.cs index e7be251eb..136446699 100644 --- a/source/PlayniteTests/Providers/Steam/SteamApiClientTests.cs +++ b/source/PlayniteTests/Providers/Steam/SteamApiClientTests.cs @@ -3,15 +3,15 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; using Playnite.Providers.Steam; namespace PlayniteTests.Providers.Steam { - [TestClass] + [TestFixture] public class SteamApiClientTests { - [TestMethod] + [Test] public void GetProductInfoTest() { var data = SteamApiClient.GetProductInfo(214490).GetAwaiter().GetResult(); diff --git a/source/PlayniteTests/Providers/Steam/SteamTests.cs b/source/PlayniteTests/Providers/Steam/SteamLibraryTests.cs similarity index 71% rename from source/PlayniteTests/Providers/Steam/SteamTests.cs rename to source/PlayniteTests/Providers/Steam/SteamLibraryTests.cs index a6e8a1adf..1797ff07c 100644 --- a/source/PlayniteTests/Providers/Steam/SteamTests.cs +++ b/source/PlayniteTests/Providers/Steam/SteamLibraryTests.cs @@ -1,4 +1,4 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; using Playnite.Providers.GOG; using System; using System.Collections.Generic; @@ -6,17 +6,19 @@ using System.Text; using System.Threading.Tasks; using System.IO; +using Playnite.Providers.Steam; -namespace Playnite.Providers.Steam.Tests +namespace PlayniteTests.Providers.Steam { - [TestClass()] - public class SteamTests + [TestFixture] + public class SteamLibraryTests { - [TestMethod()] + [Test] [Description("Basic verification testing that installed games can be fetched from local client.")] public void GetInstalledGames_Basic() { - var games = Steam.GetInstalledGames(); + var steamLib = new SteamLibrary(); + var games = steamLib.GetInstalledGames(); Assert.AreNotEqual(0, games.Count); CollectionAssert.AllItemsAreUnique(games); @@ -27,15 +29,17 @@ public void GetInstalledGames_Basic() Assert.IsFalse(string.IsNullOrEmpty(game.InstallDirectory)); Assert.IsTrue(Directory.Exists(game.InstallDirectory)); Assert.IsNotNull(game.PlayTask); - Assert.IsTrue(game.PlayTask.Type == Models.GameTaskType.URL); + Assert.IsTrue(game.PlayTask.Type == Playnite.Models.GameTaskType.URL); } } - [TestMethod()] + [Test] public void DownloadGameMetadataTest() { + var steamLib = new SteamLibrary(); + // Existing store - var existing = Steam.DownloadGameMetadata(107410); + var existing = steamLib.DownloadGameMetadata(107410); Assert.IsNotNull(existing.ProductDetails); Assert.IsNotNull(existing.StoreDetails); Assert.IsNotNull(existing.Icon.Data); @@ -43,7 +47,7 @@ public void DownloadGameMetadataTest() Assert.IsNotNull(existing.BackgroundImage); // NonExisting store - var nonExisting = Steam.DownloadGameMetadata(201280); + var nonExisting = steamLib.DownloadGameMetadata(201280); Assert.IsNotNull(nonExisting.ProductDetails); Assert.IsNull(nonExisting.StoreDetails); Assert.IsNotNull(nonExisting.Icon.Data); diff --git a/source/PlayniteTests/Providers/Steam/WebApiClientTests.cs b/source/PlayniteTests/Providers/Steam/WebApiClientTests.cs index 68eb53cd0..0aa276944 100644 --- a/source/PlayniteTests/Providers/Steam/WebApiClientTests.cs +++ b/source/PlayniteTests/Providers/Steam/WebApiClientTests.cs @@ -3,15 +3,15 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; using Playnite.Providers.Steam; namespace PlayniteTests.Providers.Steam { - [TestClass()] + [TestFixture] public class WebApiClientTests { - [TestMethod] + [Test] public void GetStoreAppDetailTest() { var data = WebApiClient.GetStoreAppDetail(214490); diff --git a/source/PlayniteTests/SettingsTests.cs b/source/PlayniteTests/SettingsTests.cs index 65f63b5c0..bae3b6d2c 100644 --- a/source/PlayniteTests/SettingsTests.cs +++ b/source/PlayniteTests/SettingsTests.cs @@ -4,27 +4,27 @@ using System.Text; using System.Threading.Tasks; using System.IO; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; using Playnite; namespace PlayniteTests { - [TestClass()] + [TestFixture] public class SettingsTests { - [ClassInitialize()] - public static void ClassInit(TestContext context) + [OneTimeSetUp] + public void Init() { FileSystem.DeleteFile(Paths.UninstallerPath); } - [ClassCleanup()] - public static void ClassCleanup() + [OneTimeTearDown] + public void Cleanup() { FileSystem.DeleteFile(Paths.UninstallerPath); } - [TestMethod()] + [Test] public void PortablePathsTest() { Assert.IsTrue(Settings.IsPortable); diff --git a/source/PlayniteTests/packages.config b/source/PlayniteTests/packages.config index 2ce7cf50c..1a708f9cf 100644 --- a/source/PlayniteTests/packages.config +++ b/source/PlayniteTests/packages.config @@ -1,7 +1,10 @@  + + + diff --git a/source/PlayniteUI/Controls/GamePopupMenu.xaml.cs b/source/PlayniteUI/Controls/GamePopupMenu.xaml.cs index 5fddef9ad..accd41527 100644 --- a/source/PlayniteUI/Controls/GamePopupMenu.xaml.cs +++ b/source/PlayniteUI/Controls/GamePopupMenu.xaml.cs @@ -171,7 +171,7 @@ private void ListPopupHide_MouseUp(object sender, MouseButtonEventArgs e) { var game = (IGame)(sender as FrameworkElement).DataContext; game.Hidden = !game.Hidden; - GameDatabase.Instance.UpdateGame(game); + GameDatabase.Instance.UpdateGameInDatabase(game); IsOpen = false; } } diff --git a/source/PlayniteUI/Controls/GamePopupMenuMulti.xaml.cs b/source/PlayniteUI/Controls/GamePopupMenuMulti.xaml.cs index 714eac1f3..bfd697c0b 100644 --- a/source/PlayniteUI/Controls/GamePopupMenuMulti.xaml.cs +++ b/source/PlayniteUI/Controls/GamePopupMenuMulti.xaml.cs @@ -100,7 +100,7 @@ private void Hide_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) foreach (var game in (List)DataContext) { game.Hidden = true; - GameDatabase.Instance.UpdateGame(game); + GameDatabase.Instance.UpdateGameInDatabase(game); } IsOpen = false; @@ -111,7 +111,7 @@ private void UnHide_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) foreach (var game in (List)DataContext) { game.Hidden = false; - GameDatabase.Instance.UpdateGame(game); + GameDatabase.Instance.UpdateGameInDatabase(game); } IsOpen = false; diff --git a/source/PlayniteUI/Controls/GamesImagesView.xaml.cs b/source/PlayniteUI/Controls/GamesImagesView.xaml.cs index 0ffaede52..03a77f08c 100644 --- a/source/PlayniteUI/Controls/GamesImagesView.xaml.cs +++ b/source/PlayniteUI/Controls/GamesImagesView.xaml.cs @@ -57,11 +57,25 @@ private void GamesGridView_CollectionChanged(object sender, System.Collections.S return; } - if (e.OldItems.Contains(GameDetails.DataContext)) + // Can be called from another thread if games are being loaded + GameDetails.Dispatcher.Invoke(() => { - GameDetails.DataContext = null; - CloseDetailBorder_MouseLeftButtonDown(this, null); - } + if (GameDetails.DataContext == null) + { + return; + } + + var game = (IGame)GameDetails.DataContext; + foreach (IGame removedGame in e.OldItems) + { + if (game.Id == removedGame.Id) + { + GameDetails.DataContext = null; + CloseDetailBorder_MouseLeftButtonDown(this, null); + return; + } + } + }); } private void CloseDetailBorder_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) diff --git a/source/PlayniteUI/Controls/GamesListView.xaml.cs b/source/PlayniteUI/Controls/GamesListView.xaml.cs index b1b1d1858..11920bb33 100644 --- a/source/PlayniteUI/Controls/GamesListView.xaml.cs +++ b/source/PlayniteUI/Controls/GamesListView.xaml.cs @@ -55,10 +55,24 @@ private void GamesGridView_CollectionChanged(object sender, System.Collections.S return; } - if (e.OldItems.Contains(GameDetails.DataContext)) + // Can be called from another thread if games are being loaded + GameDetails.Dispatcher.Invoke(() => { - GameDetails.DataContext = null; - } + if (GameDetails.DataContext == null) + { + return; + } + + var game = (IGame)GameDetails.DataContext; + foreach (IGame removedGame in e.OldItems) + { + if (game.Id == removedGame.Id) + { + GameDetails.DataContext = null; + return; + } + } + }); } private void GamesListList_SelectionChanged(object sender, SelectionChangedEventArgs e) diff --git a/source/PlayniteUI/GamesEditor.cs b/source/PlayniteUI/GamesEditor.cs index f1eab8593..a66be0780 100644 --- a/source/PlayniteUI/GamesEditor.cs +++ b/source/PlayniteUI/GamesEditor.cs @@ -86,7 +86,7 @@ public void PlayGame(IGame game) } finally { - GameDatabase.Instance.UpdateGame(game); + GameDatabase.Instance.UpdateGameInDatabase(game); } } @@ -103,7 +103,7 @@ public void InstallGame(IGame game) } finally { - GameDatabase.Instance.UpdateGame(game); + GameDatabase.Instance.UpdateGameInDatabase(game); } } @@ -119,7 +119,7 @@ public void UnInstallGame(IGame game) } finally { - GameDatabase.Instance.UpdateGame(game); + GameDatabase.Instance.UpdateGameInDatabase(game); } } diff --git a/source/PlayniteUI/Windows/CategoryConfigWindow.xaml.cs b/source/PlayniteUI/Windows/CategoryConfigWindow.xaml.cs index 22aa43004..5421dbeb8 100644 --- a/source/PlayniteUI/Windows/CategoryConfigWindow.xaml.cs +++ b/source/PlayniteUI/Windows/CategoryConfigWindow.xaml.cs @@ -138,7 +138,7 @@ private void ButtonOK_Click(object sender, RoutedEventArgs e) if (AutoUpdateGame) { - GameDatabase.Instance.UpdateGame(game); + GameDatabase.Instance.UpdateGameInDatabase(game); } } } @@ -157,7 +157,7 @@ private void ButtonOK_Click(object sender, RoutedEventArgs e) if (AutoUpdateGame) { - GameDatabase.Instance.UpdateGame(Game); + GameDatabase.Instance.UpdateGameInDatabase(Game); } } diff --git a/source/PlayniteUI/Windows/GameEditWindow.xaml.cs b/source/PlayniteUI/Windows/GameEditWindow.xaml.cs index ffa461b7c..b99955377 100644 --- a/source/PlayniteUI/Windows/GameEditWindow.xaml.cs +++ b/source/PlayniteUI/Windows/GameEditWindow.xaml.cs @@ -619,12 +619,12 @@ private void ButtonOK_Click(object sender, RoutedEventArgs e) { foreach (var game in Games) { - GameDatabase.Instance.UpdateGame(game); + GameDatabase.Instance.UpdateGameInDatabase(game); } } else { - GameDatabase.Instance.UpdateGame(Game); + GameDatabase.Instance.UpdateGameInDatabase(Game); } DialogResult = true; diff --git a/source/PlayniteUI/Windows/MainWindow.xaml.cs b/source/PlayniteUI/Windows/MainWindow.xaml.cs index 2eb23fd68..45a5796cd 100644 --- a/source/PlayniteUI/Windows/MainWindow.xaml.cs +++ b/source/PlayniteUI/Windows/MainWindow.xaml.cs @@ -213,9 +213,11 @@ private async void LoadGames() return; } + var database = GameDatabase.Instance; + try { - GameDatabase.Instance.OpenDatabase(Config.DatabasePath); + database.OpenDatabase(Config.DatabasePath); } catch (Exception exc) { @@ -226,12 +228,12 @@ private async void LoadGames() return; } - BindingOperations.EnableCollectionSynchronization(GameDatabase.Instance.Games, gamesLock); - GameDatabase.Instance.LoadGamesFromDb(Config); - ListGamesView.ItemsSource = GameDatabase.Instance.Games; - ImagesGamesView.ItemsSource = GameDatabase.Instance.Games; - GridGamesView.ItemsSource = GameDatabase.Instance.Games; - MainCollectionView = (ListCollectionView)CollectionViewSource.GetDefaultView(GameDatabase.Instance.Games); + BindingOperations.EnableCollectionSynchronization(database.Games, gamesLock); + database.LoadGamesFromDb(Config); + ListGamesView.ItemsSource = database.Games; + ImagesGamesView.ItemsSource = database.Games; + GridGamesView.ItemsSource = database.Games; + MainCollectionView = (ListCollectionView)CollectionViewSource.GetDefaultView(database.Games); Config_PropertyChanged(this, null); @@ -246,8 +248,13 @@ private async void LoadGames() { if (Config.GOGSettings.IntegrationEnabled) { - GameDatabase.Instance.UpdateGogInstalledGames(); + database.UpdateInstalledGames(Provider.GOG); NotificationBar.RemoveMessage(NotificationCodes.GOGLInstalledImportError); + + if (!Config.GOGSettings.LibraryDownloadEnabled) + { + database.UnloadNotInstalledGames(Provider.GOG); + } } } catch (Exception e) @@ -263,8 +270,13 @@ private async void LoadGames() { if (Config.SteamSettings.IntegrationEnabled) { - GameDatabase.Instance.UpdateSteamInstalledGames(); + database.UpdateInstalledGames(Provider.Steam); NotificationBar.RemoveMessage(NotificationCodes.SteamInstalledImportError); + + if (!Config.SteamSettings.LibraryDownloadEnabled) + { + database.UnloadNotInstalledGames(Provider.Steam); + } } } catch (Exception e) @@ -280,8 +292,13 @@ private async void LoadGames() { if (Config.OriginSettings.IntegrationEnabled) { - GameDatabase.Instance.UpdateOriginInstalledGames(); + database.UpdateInstalledGames(Provider.Origin); NotificationBar.RemoveMessage(NotificationCodes.OriginInstalledImportError); + + if (!Config.OriginSettings.LibraryDownloadEnabled) + { + database.UnloadNotInstalledGames(Provider.Origin); + } } } catch (Exception e) @@ -299,7 +316,7 @@ private async void LoadGames() { if (Config.GOGSettings.IntegrationEnabled && Config.GOGSettings.LibraryDownloadEnabled) { - GameDatabase.Instance.UpdateGogLibrary(); + database.UpdateOwnedGames(Provider.GOG); NotificationBar.RemoveMessage(NotificationCodes.GOGLibDownloadError); } } @@ -318,7 +335,8 @@ private async void LoadGames() { if (Config.SteamSettings.IntegrationEnabled && Config.SteamSettings.LibraryDownloadEnabled) { - GameDatabase.Instance.UpdateSteamLibrary(Config.SteamSettings.AccountName); + database.SteamUserName = Config.SteamSettings.AccountName; + database.UpdateOwnedGames(Provider.Steam); NotificationBar.RemoveMessage(NotificationCodes.SteamLibDownloadError); } } @@ -337,7 +355,7 @@ private async void LoadGames() { if (Config.OriginSettings.IntegrationEnabled && Config.OriginSettings.LibraryDownloadEnabled) { - GameDatabase.Instance.UpdateOriginLibrary(); + database.UpdateOwnedGames(Provider.Origin); NotificationBar.RemoveMessage(NotificationCodes.OriginLibDownloadError); } } @@ -350,12 +368,12 @@ private async void LoadGames() })); } - gamesStats.SetGames(GameDatabase.Instance.Games); + gamesStats.SetGames(database.Games); ProgressControl.Text = "Downloading images and game details..."; ProgressControl.ProgressMin = 0; - ProgressControl.ProgressMax = GameDatabase.Instance.Games.Count == 0 ? 0 : GameDatabase.Instance.Games.Count - 1; + ProgressControl.ProgressMax = database.Games.Count == 0 ? 0 : database.Games.Count - 1; - for (int i = 0; i < GameDatabase.Instance.Games.Count; i++) + for (int i = 0; i < database.Games.Count; i++) { if (GamesLoaderHandler.CancelToken.Token.IsCancellationRequested) { @@ -364,7 +382,7 @@ private async void LoadGames() ProgressControl.ProgressValue = i; - var game = GameDatabase.Instance.Games[i]; + var game = database.Games[i]; if (game.Provider == Provider.Custom || game.IsProviderDataUpdated == true) { continue; @@ -372,7 +390,7 @@ private async void LoadGames() try { - GameDatabase.Instance.UpdateGameWithMetadata(game); + database.UpdateGameWithMetadata(game); } catch (Exception e) { @@ -632,7 +650,7 @@ private void AddNewGamePopup_MouseUp(object sender, MouseButtonEventArgs e) if (GamesEditor.Instance.EditGame(newGame) == true) { - GameDatabase.Instance.UpdateGame(newGame); + GameDatabase.Instance.UpdateGameInDatabase(newGame); switch (Settings.Instance.GamesViewType) { case ViewType.List: diff --git a/source/PlayniteUITests/Converters/ImageUrlToImageSourceConverterTests.cs b/source/PlayniteUITests/Converters/ImageUrlToImageSourceConverterTests.cs index f80cb0ad3..91c448472 100644 --- a/source/PlayniteUITests/Converters/ImageUrlToImageSourceConverterTests.cs +++ b/source/PlayniteUITests/Converters/ImageUrlToImageSourceConverterTests.cs @@ -1,4 +1,4 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; using System; using System.Collections.Generic; using System.Linq; @@ -13,16 +13,16 @@ namespace PlayniteUITests.Converters { - [TestClass] + [TestFixture] public class ImageUrlToImageSourceConverterTests { - [ClassInitialize] - public static void ClassInit(TestContext context) + [OneTimeSetUp] + public void Init() { FileSystem.DeleteFolder(Paths.ImagesCachePath); } - [TestMethod] + [Test] public void ConvertTest() { var convererter = new ImageUrlToImageSourceConverter(); @@ -37,12 +37,11 @@ public void ConvertTest() Assert.AreEqual(DependencyProperty.UnsetValue, convererter.Convert(@"https://steamcdn-a.akamaihd.net/steam/apps/108710/dasdasd.jpg", typeof(string), null, CultureInfo.InvariantCulture)); } - [TestMethod] - [ExpectedException(typeof(WebException))] + [Test] public void ConvertInvalidTest() { var convererter = new ImageUrlToImageSourceConverter(); - convererter.Convert(@"http://totaly.made.up.url/that/doesnt/exists.jpg", typeof(string), null, CultureInfo.InvariantCulture); + Assert.Throws(() => convererter.Convert(@"http://totaly.made.up.url/that/doesnt/exists.jpg", typeof(string), null, CultureInfo.InvariantCulture)); } } } diff --git a/source/PlayniteUITests/GamesEditorTests.cs b/source/PlayniteUITests/GamesEditorTests.cs index 3f78a19ac..340409915 100644 --- a/source/PlayniteUITests/GamesEditorTests.cs +++ b/source/PlayniteUITests/GamesEditorTests.cs @@ -1,16 +1,16 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using NUnit.Framework; using Playnite.Models; using PlayniteUI; namespace PlayniteUITests { - [TestClass] + [TestFixture] public class GamesEditorTests { - [TestMethod] + [Test] public void GetMultiGameEditObject_StandardTest() { // All common diff --git a/source/PlayniteUITests/PlayniteUITests.csproj b/source/PlayniteUITests/PlayniteUITests.csproj index 05addf4b1..ac2c129dd 100644 --- a/source/PlayniteUITests/PlayniteUITests.csproj +++ b/source/PlayniteUITests/PlayniteUITests.csproj @@ -58,6 +58,9 @@ ..\packages\LiteDB.3.0.1\lib\net35\LiteDB.dll True + + ..\packages\NUnit.3.6.1\lib\net45\nunit.framework.dll + @@ -67,11 +70,7 @@ - - - - - + diff --git a/source/PlayniteUITests/packages.config b/source/PlayniteUITests/packages.config index 3f7111701..6bf8618d4 100644 --- a/source/PlayniteUITests/packages.config +++ b/source/PlayniteUITests/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file From 44b1e20b581e9bb0690449b67c9a5d45ee612917 Mon Sep 17 00:00:00 2001 From: Josef Nemec Date: Fri, 31 Mar 2017 13:59:23 +0200 Subject: [PATCH 4/8] Added menu option to report new issue on github. --- source/PlayniteUI/Windows/MainWindow.xaml | 1 + source/PlayniteUI/Windows/MainWindow.xaml.cs | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/source/PlayniteUI/Windows/MainWindow.xaml b/source/PlayniteUI/Windows/MainWindow.xaml index 0f915684e..dd34790b1 100644 --- a/source/PlayniteUI/Windows/MainWindow.xaml +++ b/source/PlayniteUI/Windows/MainWindow.xaml @@ -59,6 +59,7 @@ + diff --git a/source/PlayniteUI/Windows/MainWindow.xaml.cs b/source/PlayniteUI/Windows/MainWindow.xaml.cs index 45a5796cd..ff284a713 100644 --- a/source/PlayniteUI/Windows/MainWindow.xaml.cs +++ b/source/PlayniteUI/Windows/MainWindow.xaml.cs @@ -721,6 +721,12 @@ private void AboutPopup_Click(object sender, RoutedEventArgs e) aboutWindow.ShowDialog(); } + private void IssuePopup_MouseUp(object sender, MouseButtonEventArgs e) + { + System.Diagnostics.Process.Start(@"https://github.com/JosefNemec/Playnite/issues/new"); + PopupMenu.IsOpen = false; + } + private void Window_LocationChanged(object sender, EventArgs e) { if (IsLoaded) From 9b74ddeb2ed3b541ace9a769d209133244bf163d Mon Sep 17 00:00:00 2001 From: Josef Nemec Date: Mon, 3 Apr 2017 18:53:04 +0200 Subject: [PATCH 5/8] Added handling of game (un)installation when running via 3rd party #6 --- source/Playnite/Database/GameDatabase.cs | 24 +-- source/Playnite/Diagnostic.cs | 2 +- source/Playnite/Models/Game.cs | 81 +++++++++- .../{Providers => Models}/GameMetadata.cs | 2 +- source/Playnite/Playnite.csproj | 10 +- .../Providers/GOG/GogGameStateMonitor.cs | 89 +++++++++++ .../Providers/GameInstalledEventArgs.cs | 28 ++++ .../Playnite/Providers/IGameStateMonitor.cs | 19 +++ .../Providers/Origin/IOriginLibrary.cs | 4 + .../Origin/OriginGameStateMonitor.cs | 138 ++++++++++++++++++ .../Providers/Origin/OriginLibrary.cs | 94 ++++++------ .../Playnite/Providers/Steam/ISteamLibrary.cs | 4 +- .../Providers/Steam/SteamGameStateMonitor.cs | 109 ++++++++++++++ .../Playnite/Providers/Steam/SteamLibrary.cs | 24 ++- .../Playnite/Providers/Steam/SteamSettings.cs | 45 +----- source/Playnite/packages.config | 1 + source/PlayniteUI/Controls/GameDetails.xaml | 17 ++- .../PlayniteUI/Controls/GameDetails.xaml.cs | 8 + source/PlayniteUI/Controls/GamePopupMenu.xaml | 20 +-- 19 files changed, 600 insertions(+), 119 deletions(-) rename source/Playnite/{Providers => Models}/GameMetadata.cs (88%) create mode 100644 source/Playnite/Providers/GOG/GogGameStateMonitor.cs create mode 100644 source/Playnite/Providers/GameInstalledEventArgs.cs create mode 100644 source/Playnite/Providers/IGameStateMonitor.cs create mode 100644 source/Playnite/Providers/Origin/OriginGameStateMonitor.cs create mode 100644 source/Playnite/Providers/Steam/SteamGameStateMonitor.cs diff --git a/source/Playnite/Database/GameDatabase.cs b/source/Playnite/Database/GameDatabase.cs index 9908a61de..93468dad4 100644 --- a/source/Playnite/Database/GameDatabase.cs +++ b/source/Playnite/Database/GameDatabase.cs @@ -152,20 +152,20 @@ public void LoadGamesFromDb(Settings settings) continue; } - if (game.Provider == Provider.GOG && !settings.OriginSettings.IntegrationEnabled) + if (game.Provider == Provider.GOG && !settings.GOGSettings.IntegrationEnabled) { continue; } - else if (game.Provider == Provider.GOG && !game.IsInstalled && !settings.OriginSettings.LibraryDownloadEnabled) + else if (game.Provider == Provider.GOG && !game.IsInstalled && !settings.GOGSettings.LibraryDownloadEnabled) { continue; } - if (game.Provider == Provider.Origin && !settings.GOGSettings.IntegrationEnabled) + if (game.Provider == Provider.Origin && !settings.OriginSettings.IntegrationEnabled) { continue; } - else if (game.Provider == Provider.Origin && !game.IsInstalled && !settings.GOGSettings.LibraryDownloadEnabled) + else if (game.Provider == Provider.Origin && !game.IsInstalled && !settings.OriginSettings.LibraryDownloadEnabled) { continue; } @@ -306,11 +306,6 @@ public void UpdateGameInDatabase(IGame game) { CheckDbState(); - if (!Games.Contains(game)) - { - throw new Exception(string.Format("Trying to update game which is not loaded, id:{0}, provider:{1}", game.ProviderId, game.Provider)); - } - lock (fileLock) { dbGames.Update(game); @@ -385,8 +380,8 @@ public void UpdateInstalledGames(Provider provider) } foreach (var newGame in installedGames) - { - var existingGame = Games.FirstOrDefault(a => a.ProviderId == newGame.ProviderId); + { + var existingGame = dbGames.FindAll().FirstOrDefault(a => a.ProviderId == newGame.ProviderId && a.Provider == provider); if (existingGame == null) { @@ -415,6 +410,13 @@ public void UpdateInstalledGames(Provider provider) } UpdateGameInDatabase(existingGame); + + // Game may have been not installed prviously and may not be loaded currently + var loaded = Games.FirstOrDefault(a => a.ProviderId == existingGame.ProviderId && a.Provider == existingGame.Provider) != null; + if (!loaded) + { + Games.Add(existingGame); + } } } diff --git a/source/Playnite/Diagnostic.cs b/source/Playnite/Diagnostic.cs index 66c150938..fc6426864 100644 --- a/source/Playnite/Diagnostic.cs +++ b/source/Playnite/Diagnostic.cs @@ -77,7 +77,7 @@ public static void CreateDiagPackage(string path) if (SteamSettings.IsInstalled) { - foreach (var folder in SteamSettings.GameDatabases) + foreach (var folder in (new SteamLibrary()).GetLibraryFolders()) { var appsFolder = Path.Combine(folder, "steamapps"); addFolderToZip(archive, "Steam", appsFolder, "appmanifest*"); diff --git a/source/Playnite/Models/Game.cs b/source/Playnite/Models/Game.cs index d63c98c79..0d0eb9150 100644 --- a/source/Playnite/Models/Game.cs +++ b/source/Playnite/Models/Game.cs @@ -13,11 +13,14 @@ using Playnite.Providers.GOG; using Playnite.Providers.Origin; using Playnite.Providers.Steam; +using Playnite.Providers; namespace Playnite.Models { public class Game : IGame { + private IGameStateMonitor stateMonitor; + private string backgroundImage; public string BackgroundImage { @@ -443,6 +446,22 @@ public bool IsProviderDataUpdated } } + private bool isSetupInProgress = false; + [BsonIgnore] + public bool IsSetupInProgress + { + get + { + return isSetupInProgress; + } + + set + { + isSetupInProgress = value; + OnPropertyChanged("IsSetupInProgress"); + } + } + public event PropertyChangedEventHandler PropertyChanged; public Game() @@ -467,12 +486,15 @@ public void InstallGame() { case Provider.Steam: Process.Start(@"steam://install/" + ProviderId); + RegisterStateMonitor(new SteamGameStateMonitor(ProviderId, new SteamLibrary())); break; case Provider.GOG: Process.Start(@"goggalaxy://openGameView/" + ProviderId); + RegisterStateMonitor(new GogGameStateMonitor(ProviderId, InstallDirectory, new GogLibrary())); break; case Provider.Origin: Process.Start(string.Format(@"origin2://game/launch?offerIds={0}&autoDownload=true", ProviderId)); + RegisterStateMonitor(new OriginGameStateMonitor(ProviderId, new OriginLibrary())); break; case Provider.Custom: break; @@ -505,6 +527,7 @@ public void UninstallGame() { case Provider.Steam: Process.Start("steam://uninstall/" + ProviderId); + RegisterStateMonitor(new SteamGameStateMonitor(ProviderId, new SteamLibrary())); break; case Provider.GOG: var uninstaller = Path.Combine(InstallDirectory, "unins000.exe"); @@ -513,10 +536,12 @@ public void UninstallGame() throw new FileNotFoundException("Uninstaller not found."); } - Process.Start(uninstaller).WaitForExit(); + Process.Start(uninstaller); + RegisterStateMonitor(new GogGameStateMonitor(ProviderId, InstallDirectory, new GogLibrary())); break; case Provider.Origin: Process.Start("appwiz.cpl"); + RegisterStateMonitor(new OriginGameStateMonitor(ProviderId, new OriginLibrary())); break; case Provider.Custom: break; @@ -525,6 +550,60 @@ public void UninstallGame() } } + public void RegisterStateMonitor(IGameStateMonitor monitor) + { + if (stateMonitor != null) + { + stateMonitor.Dispose(); + } + + stateMonitor = monitor; + stateMonitor.GameInstalled += StateMonitor_GameInstalled; + stateMonitor.GameUninstalled += StateMonitor_GameUninstalled; + stateMonitor.StartMonitoring(); + IsSetupInProgress = true; + } + + public void UnregisetrStateMonitor() + { + if (stateMonitor != null) + { + stateMonitor.StopMonitoring(); + stateMonitor.Dispose(); + } + + IsSetupInProgress = false; + } + + private void StateMonitor_GameUninstalled(object sender, EventArgs e) + { + IsSetupInProgress = false; + PlayTask = null; + InstallDirectory = string.Empty; + + if (OtherTasks != null) + { + OtherTasks = new ObservableCollection(OtherTasks.Where(a => !a.IsBuiltIn)); + } + } + + private void StateMonitor_GameInstalled(object sender, GameInstalledEventArgs e) + { + IsSetupInProgress = false; + var game = e.NewGame; + PlayTask = game.PlayTask; + InstallDirectory = game.InstallDirectory; + + if (game.OtherTasks != null) + { + OtherTasks = new ObservableCollection(OtherTasks.Where(a => !a.IsBuiltIn)); + foreach (var task in game.OtherTasks.Reverse()) + { + OtherTasks.Insert(0, task); + } + } + } + public override string ToString() { return Name; diff --git a/source/Playnite/Providers/GameMetadata.cs b/source/Playnite/Models/GameMetadata.cs similarity index 88% rename from source/Playnite/Providers/GameMetadata.cs rename to source/Playnite/Models/GameMetadata.cs index 20714371d..ebed56f24 100644 --- a/source/Playnite/Providers/GameMetadata.cs +++ b/source/Playnite/Models/GameMetadata.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Playnite.Database; -namespace Playnite.Providers +namespace Playnite.Models { public class GameMetadata { diff --git a/source/Playnite/Playnite.csproj b/source/Playnite/Playnite.csproj index 4878294fb..fa0b04f08 100644 --- a/source/Playnite/Playnite.csproj +++ b/source/Playnite/Playnite.csproj @@ -79,6 +79,9 @@ ..\packages\NLog.4.4.3\lib\net45\NLog.dll True + + ..\packages\Polly.5.0.6\lib\net45\Polly.dll + @@ -141,7 +144,9 @@ - + + + @@ -154,13 +159,16 @@ + + + diff --git a/source/Playnite/Providers/GOG/GogGameStateMonitor.cs b/source/Playnite/Providers/GOG/GogGameStateMonitor.cs new file mode 100644 index 000000000..74ba65167 --- /dev/null +++ b/source/Playnite/Providers/GOG/GogGameStateMonitor.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NLog; + +namespace Playnite.Providers.GOG +{ + public class GogGameStateMonitor : IGameStateMonitor + { + private static Logger logger = LogManager.GetCurrentClassLogger(); + + public event EventHandler GameUninstalled; + + public event GameInstalledEventHandler GameInstalled + { + add + { + } + remove + { + } + } + + private IGogLibrary library; + + private FileSystemWatcher watcher; + + private string id; + + private string installDirectory; + + public GogGameStateMonitor(string id, string installDirectory, IGogLibrary originLibrary) + { + library = originLibrary; + this.id = id; + this.installDirectory = installDirectory; + } + + public void StartMonitoring() + { + logger.Info("Starting monitoring of GOG app " + id); + Dispose(); + + var infoFile = string.Format("goggame-{0}.info", id); + if (File.Exists(Path.Combine(installDirectory, infoFile))) + { + logger.Info("GOG app {0} is currently not installed, starting install monitor.", id); + watcher = new FileSystemWatcher() + { + NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName, + Path = installDirectory, + Filter = Path.GetFileName(infoFile) + }; + + watcher.Deleted += Watcher_Deleted; + watcher.EnableRaisingEvents = true; + } + else + { + // Installation detection not implemented for several technical limitations + // Maily because of shared access to GOG's local database + logger.Warn("GOG app {0} is currently not installed, NOT starting install monitor", id); + } + } + + private void Watcher_Deleted(object sender, FileSystemEventArgs e) + { + logger.Info("GOG app {0} uninstalled.", id); + GameUninstalled?.Invoke(this, null); + } + + public void StopMonitoring() + { + Dispose(); + } + + public void Dispose() + { + if (watcher != null) + { + watcher.EnableRaisingEvents = false; + watcher.Dispose(); + } + } + } +} diff --git a/source/Playnite/Providers/GameInstalledEventArgs.cs b/source/Playnite/Providers/GameInstalledEventArgs.cs new file mode 100644 index 000000000..50dc3cef2 --- /dev/null +++ b/source/Playnite/Providers/GameInstalledEventArgs.cs @@ -0,0 +1,28 @@ +using Playnite.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Playnite.Providers +{ + public delegate void GameInstalledEventHandler(object sender, GameInstalledEventArgs args); + + public class GameInstalledEventArgs : EventArgs + { + public IGame NewGame + { + get; set; + } + + public GameInstalledEventArgs() + { + } + + public GameInstalledEventArgs(IGame game) + { + NewGame = game; + } + } +} diff --git a/source/Playnite/Providers/IGameStateMonitor.cs b/source/Playnite/Providers/IGameStateMonitor.cs new file mode 100644 index 000000000..e12d46fd2 --- /dev/null +++ b/source/Playnite/Providers/IGameStateMonitor.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Playnite.Providers +{ + public interface IGameStateMonitor : IDisposable + { + event EventHandler GameUninstalled; + + event GameInstalledEventHandler GameInstalled; + + void StartMonitoring(); + + void StopMonitoring(); + } +} diff --git a/source/Playnite/Providers/Origin/IOriginLibrary.cs b/source/Playnite/Providers/Origin/IOriginLibrary.cs index 2fdf42ec1..648fdbb48 100644 --- a/source/Playnite/Providers/Origin/IOriginLibrary.cs +++ b/source/Playnite/Providers/Origin/IOriginLibrary.cs @@ -14,6 +14,10 @@ public interface IOriginLibrary System.Collections.Specialized.NameValueCollection ParseOriginManifest(string path); + GameLocalDataResponse GetLocalManifest(string id, string packageName = null, bool useDataCache = false); + + GameTask GetGamePlayTask(GameLocalDataResponse manifest); + List GetInstalledGames(bool useDataCache = false); List GetLibraryGames(); diff --git a/source/Playnite/Providers/Origin/OriginGameStateMonitor.cs b/source/Playnite/Providers/Origin/OriginGameStateMonitor.cs new file mode 100644 index 000000000..c22dc05a0 --- /dev/null +++ b/source/Playnite/Providers/Origin/OriginGameStateMonitor.cs @@ -0,0 +1,138 @@ +using Playnite.Models; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NLog; + +namespace Playnite.Providers.Origin +{ + public class OriginGameStateMonitor : IGameStateMonitor + { + private static Logger logger = LogManager.GetCurrentClassLogger(); + + public event EventHandler GameUninstalled; + + public event GameInstalledEventHandler GameInstalled; + + private IOriginLibrary library; + + private GameLocalDataResponse manifest; + + private FileSystemWatcher watcher; + + private GameLocalDataResponse.Publishing.Software platform; + + private string executablePath; + + private CancellationTokenSource installWaitToken; + + private string id; + + public OriginGameStateMonitor(string id, IOriginLibrary originLibrary) + { + this.id = id; + library = originLibrary; + manifest = library.GetLocalManifest(id, null, true); + platform = manifest.publishing.softwareList.software.FirstOrDefault(a => a.softwarePlatform == "PCWIN"); + } + + private void Watcher_Created(object sender, FileSystemEventArgs e) + { + logger.Info("Origin app {0} installed.", id); + GameInstalled?.Invoke(this, new GameInstalledEventArgs(new Game() + { + PlayTask = library.GetGamePlayTask(manifest), + InstallDirectory = Path.GetDirectoryName(executablePath) + })); + } + + private void Watcher_Deleted(object sender, FileSystemEventArgs e) + { + logger.Info("Origin app {0} uninstalled.", id); + GameUninstalled?.Invoke(this, null); + } + + public void StartMonitoring() + { + logger.Info("Starting monitoring of Origin app " + id); + Dispose(); + + executablePath = library.GetPathFromPlatformPath(platform.fulfillmentAttributes.installCheckOverride); + + // Game is not installed + if (string.IsNullOrEmpty(executablePath)) + { + logger.Info("Origin app {0} is currently not installed, starting install monitor.", id); + installWaitToken = new CancellationTokenSource(); + + Task.Factory.StartNew(() => + { + while (!installWaitToken.Token.IsCancellationRequested) + { + executablePath = library.GetPathFromPlatformPath(platform.fulfillmentAttributes.installCheckOverride); + if (!string.IsNullOrEmpty(executablePath)) + { + if (File.Exists(executablePath)) + { + Watcher_Created(this, null); + return; + } + else + { + watcher = new FileSystemWatcher() + { + NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName, + Path = Path.GetDirectoryName(executablePath), + Filter = Path.GetFileName(executablePath) + }; + + watcher.Created += Watcher_Created; + watcher.EnableRaisingEvents = true; + return; + } + } + + Thread.Sleep(2000); + } + }, installWaitToken.Token); + } + else + { + logger.Info("Origin app {0} is currently installed, starting uninstall monitor.", id); + watcher = new FileSystemWatcher() + { + NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName, + Path = Path.GetDirectoryName(executablePath), + Filter = Path.GetFileName(executablePath) + }; + + watcher.Deleted += Watcher_Deleted; + watcher.EnableRaisingEvents = true; + } + } + + public void StopMonitoring() + { + logger.Info("Stopping monitoring of Origin app " + id); + Dispose(); + } + + public void Dispose() + { + if (installWaitToken != null) + { + installWaitToken.Cancel(false); + } + + if (watcher != null) + { + watcher.EnableRaisingEvents = false; + watcher.Dispose(); + } + } + } +} diff --git a/source/Playnite/Providers/Origin/OriginLibrary.cs b/source/Playnite/Providers/Origin/OriginLibrary.cs index 3437696c2..2c96922b3 100644 --- a/source/Playnite/Providers/Origin/OriginLibrary.cs +++ b/source/Playnite/Providers/Origin/OriginLibrary.cs @@ -76,6 +76,57 @@ public System.Collections.Specialized.NameValueCollection ParseOriginManifest(st return HttpUtility.ParseQueryString(data); } + public GameLocalDataResponse GetLocalManifest(string id, string packageName = null, bool useDataCache = false) + { + var package = packageName; + + if (string.IsNullOrEmpty(package)) + { + package = id.Replace(":", ""); + } + + var cacheFile = Path.Combine(OriginPaths.CachePath, Path.GetFileNameWithoutExtension(package) + ".json"); + if (useDataCache == true && File.Exists(cacheFile)) + { + return JsonConvert.DeserializeObject(File.ReadAllText(cacheFile, Encoding.UTF8)); + } + else if (useDataCache == true && !File.Exists(cacheFile)) + { + FileSystem.CreateFolder(OriginPaths.CachePath); + var data = WebApiClient.GetGameLocalData(id); + File.WriteAllText(cacheFile, JsonConvert.SerializeObject(data), Encoding.UTF8); + return data; + } + else + { + return WebApiClient.GetGameLocalData(id); + } + } + + public GameTask GetGamePlayTask(GameLocalDataResponse manifest) + { + var platform = manifest.publishing.softwareList.software.FirstOrDefault(a => a.softwarePlatform == "PCWIN"); + var playTask = new GameTask() + { + IsBuiltIn = true, + IsPrimary = true + }; + + if (platform.fulfillmentAttributes.executePathOverride.Contains(@"://")) + { + playTask.Type = GameTaskType.URL; + playTask.Path = platform.fulfillmentAttributes.executePathOverride; + } + else + { + var executePath = GetPathFromPlatformPath(platform.fulfillmentAttributes.executePathOverride); + playTask.WorkingDir = Path.GetDirectoryName(GetPathFromPlatformPath(platform.fulfillmentAttributes.installCheckOverride)); + playTask.Path = executePath; + } + + return playTask; + } + public List GetInstalledGames(bool useDataCache = false) { var contentPath = Path.Combine(OriginPaths.DataPath, "LocalContent"); @@ -107,22 +158,7 @@ public List GetInstalledGames(bool useDataCache = false) ProviderId = gameId }; - GameLocalDataResponse localData; - var cacheFile = Path.Combine(OriginPaths.CachePath, Path.GetFileNameWithoutExtension(package) + ".json"); - if (useDataCache == true && File.Exists(cacheFile)) - { - localData = JsonConvert.DeserializeObject(File.ReadAllText(cacheFile, Encoding.UTF8)); - } - else if (useDataCache == true && !File.Exists(cacheFile)) - { - FileSystem.CreateFolder(OriginPaths.CachePath); - localData = WebApiClient.GetGameLocalData(gameId); - File.WriteAllText(cacheFile, JsonConvert.SerializeObject(localData), Encoding.UTF8); - } - else - { - localData = WebApiClient.GetGameLocalData(gameId); - } + GameLocalDataResponse localData = GetLocalManifest(gameId, package, useDataCache); if (localData.offerType != "Base Game") { @@ -145,31 +181,7 @@ public List GetInstalledGames(bool useDataCache = false) } newGame.InstallDirectory = Path.GetDirectoryName(installPath); - - var playTask = new GameTask() - { - IsBuiltIn = true, - IsPrimary = true - }; - - if (platform.fulfillmentAttributes.executePathOverride.Contains(@"://")) - { - playTask.Type = GameTaskType.URL; - playTask.Path = platform.fulfillmentAttributes.executePathOverride; - } - else - { - var executePath = GetPathFromPlatformPath(platform.fulfillmentAttributes.executePathOverride); - if (string.IsNullOrEmpty(executePath)) - { - continue; - } - - playTask.WorkingDir = newGame.InstallDirectory; - playTask.Path = executePath; - } - - newGame.PlayTask = playTask; + newGame.PlayTask = GetGamePlayTask(localData); games.Add(newGame); } } diff --git a/source/Playnite/Providers/Steam/ISteamLibrary.cs b/source/Playnite/Providers/Steam/ISteamLibrary.cs index 50df618f6..ee84a9a02 100644 --- a/source/Playnite/Providers/Steam/ISteamLibrary.cs +++ b/source/Playnite/Providers/Steam/ISteamLibrary.cs @@ -10,7 +10,7 @@ namespace Playnite.Providers.Steam { public interface ISteamLibrary { - IGame GetInstalledGamesFromFile(string path); + IGame GetInstalledGameFromFile(string path); List GetInstalledGamesFromFolder(string path); @@ -21,5 +21,7 @@ public interface ISteamLibrary SteamGameMetadata DownloadGameMetadata(int id); SteamGameMetadata UpdateGameWithMetadata(IGame game); + + List GetLibraryFolders(); } } diff --git a/source/Playnite/Providers/Steam/SteamGameStateMonitor.cs b/source/Playnite/Providers/Steam/SteamGameStateMonitor.cs new file mode 100644 index 000000000..4d2e31168 --- /dev/null +++ b/source/Playnite/Providers/Steam/SteamGameStateMonitor.cs @@ -0,0 +1,109 @@ +using NLog; +using Polly; +using SteamKit2; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Playnite.Providers.Steam +{ + public class SteamGameStateMonitor : IGameStateMonitor + { + private static Logger logger = LogManager.GetCurrentClassLogger(); + + public event EventHandler GameUninstalled; + + public event GameInstalledEventHandler GameInstalled; + + private List watchers; + + private ISteamLibrary library; + + private string oldGameState = string.Empty; + + private string id; + + public SteamGameStateMonitor(string id, ISteamLibrary steamLibrary) + { + this.id = id; + library = steamLibrary; + watchers = new List(); + + foreach (var folder in library.GetLibraryFolders()) + { + var watcher = new FileSystemWatcher() + { + NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName, + Path = Path.Combine(folder, "steamapps"), + Filter = string.Format("appmanifest_{0}.acf", id) + }; + + watcher.Deleted += Watcher_Deleted; + watcher.Changed += Watcher_Changed; + watchers.Add(watcher); + } + } + + private void Watcher_Changed(object sender, FileSystemEventArgs e) + { + var kv = new KeyValue(); + + Policy.Handle().WaitAndRetry(4, r => TimeSpan.FromSeconds(1)).Execute(() => + { + kv.ReadFileAsText(e.FullPath); + }); + + var state = kv["StateFlags"].Value; + if (state == "4" && oldGameState != state) + { + oldGameState = state; + var newGame = library.GetInstalledGameFromFile(e.FullPath); + logger.Info("Steam app {0} installed.", id); + GameInstalled?.Invoke(this, new GameInstalledEventArgs(newGame)); + } + } + + private void Watcher_Deleted(object sender, FileSystemEventArgs e) + { + logger.Info("Steam app {0} uninstalled.", id); + GameUninstalled?.Invoke(this, null); + } + + public void StartMonitoring() + { + logger.Info("Starting monitoring of Steam app " + id); + + foreach (var watcher in watchers) + { + watcher.EnableRaisingEvents = true; + } + } + + public void StopMonitoring() + { + logger.Info("Stopping monitoring of Steam app " + id); + + foreach (var watcher in watchers) + { + watcher.EnableRaisingEvents = false; + } + } + + public void Dispose() + { + if (watchers == null) + { + return; + } + + foreach (var watcher in watchers) + { + watcher.EnableRaisingEvents = false; + watcher.Dispose(); + } + } + } +} diff --git a/source/Playnite/Providers/Steam/SteamLibrary.cs b/source/Playnite/Providers/Steam/SteamLibrary.cs index e784f70be..d9d2c8e99 100644 --- a/source/Playnite/Providers/Steam/SteamLibrary.cs +++ b/source/Playnite/Providers/Steam/SteamLibrary.cs @@ -25,7 +25,7 @@ public class SteamLibrary : ISteamLibrary { private Logger logger = LogManager.GetCurrentClassLogger(); - public IGame GetInstalledGamesFromFile(string path) + public IGame GetInstalledGameFromFile(string path) { var kv = new KeyValue(); kv.ReadFileAsText(path); @@ -69,7 +69,7 @@ public List GetInstalledGamesFromFolder(string path) foreach (var file in Directory.GetFiles(appsFolder, @"appmanifest*")) { - var game = GetInstalledGamesFromFile(Path.Combine(appsFolder, file)); + var game = GetInstalledGameFromFile(Path.Combine(appsFolder, file)); games.Add(game); } @@ -80,7 +80,7 @@ public List GetInstalledGames() { var games = new List(); - foreach (var folder in SteamSettings.GameDatabases) + foreach (var folder in GetLibraryFolders()) { games.AddRange(GetInstalledGamesFromFolder(folder)); } @@ -88,6 +88,24 @@ public List GetInstalledGames() return games; } + public List GetLibraryFolders() + { + var dbs = new List() { SteamSettings.InstallationPath }; + var configPath = Path.Combine(SteamSettings.InstallationPath, "steamapps", "libraryfolders.vdf"); + var kv = new KeyValue(); + kv.ReadFileAsText(configPath); + + foreach (var child in kv.Children) + { + if (int.TryParse(child.Name, out int test)) + { + dbs.Add(child.Value); + } + } + + return dbs; + } + public List GetLibraryGames(string userName) { if (string.IsNullOrEmpty(userName)) diff --git a/source/Playnite/Providers/Steam/SteamSettings.cs b/source/Playnite/Providers/Steam/SteamSettings.cs index fb7b1bf62..04fe7e872 100644 --- a/source/Playnite/Providers/Steam/SteamSettings.cs +++ b/source/Playnite/Providers/Steam/SteamSettings.cs @@ -29,7 +29,7 @@ public static string InstallationPath { if (key != null) { - return key.GetValue("SteamPath").ToString(); + return key.GetValue("SteamPath").ToString().Replace('/', '\\'); } } @@ -37,49 +37,6 @@ public static string InstallationPath } } - public static List GameDatabases - { - get - { - var dbs = new List() { InstallationPath }; - var configPath = Path.Combine(InstallationPath, "steamapps", "libraryfolders.vdf"); - var kv = new KeyValue(); - kv.ReadFileAsText(configPath); - - foreach (var child in kv.Children) - { - if (int.TryParse(child.Name, out int test)) - { - dbs.Add(child.Value); - } - } - - return dbs; - } - } - - /// - /// By default is set to C:\ProgramData\Playnite\steam - /// - public static string DataCachePath - { - get - { - return Path.Combine(Paths.DataCachePath, "steam"); - } - } - - /// - /// By default is set to C:\ProgramData\Playnite\steam\library.json - /// - public static string LibraryCachePath - { - get - { - return Path.Combine(DataCachePath, "library.json"); - } - } - private string accountName = string.Empty; public string AccountName { diff --git a/source/Playnite/packages.config b/source/Playnite/packages.config index 03984d248..9f516cceb 100644 --- a/source/Playnite/packages.config +++ b/source/Playnite/packages.config @@ -10,6 +10,7 @@ + diff --git a/source/PlayniteUI/Controls/GameDetails.xaml b/source/PlayniteUI/Controls/GameDetails.xaml index 4bdc6f87f..580e12718 100644 --- a/source/PlayniteUI/Controls/GameDetails.xaml +++ b/source/PlayniteUI/Controls/GameDetails.xaml @@ -55,12 +55,17 @@ -