public void RebuildGameGrid() { Application.Current?.Dispatcher.Invoke(() => { using (var tb = new TimedBlock($"RebuildGameGrid()")) { var newFilteredGameIds = GameLibrary.GameLibrary.ListGames(titleFilter, filter, order, isOrderAscending); var newFilteredGames = newFilteredGameIds.Select(gameId => allGames.Single(game => game.ViewModel.GameId == gameId)).ToArray(); var xCount = (int)((windowSize.Width - Const.GridBorder) / (Const.GameControlSize.Width + Const.GridBorder)); var border = (int)(windowSize.Width - xCount * Const.GameControlSize.Width) / (xCount + 1); //Log.WriteLine($"RebuildGameGrid(width={ windowSize.Width }, border={ border }, ") for (var i = 0; i < newFilteredGames.Length; i++) { var newX = border + (int)(Const.GameControlSize.Width + border) * (i % xCount); var newY = (int)(border - Const.GridBorder) + (int)(Const.GameControlSize.Height + border) * (i / xCount); newFilteredGames[i].ViewModel.SetDestination(newX, newY); } FilteredGameIds = newFilteredGameIds; FilteredGames = newFilteredGames; foreach (var gameControl in newFilteredGames) { gameControl.MoveToDestination(256); } } }); }
/// <summary> /// update play time from steam library /// </summary> /// <param name="steamApiKey"></param> /// <param name="steamId"></param> static void UpdatePlayTimeFromSteamLibrary() { using (var tb = new TimedBlock("UpdatePlayTimeFromSteamLibrary()")) { try { var playTimes = SteamApi.ListPlayTimes(Settings.Default.SteamApiKey, Settings.Default.UserSteamId); foreach (var playTime in playTimes) { var steamAppId = playTime.Key; var game = Games.Values .SingleOrDefault(g => g.Data .SingleOrDefault(d => d is SteamDbInfoData && (d as SteamDbInfoData).AppId == steamAppId) != null); if (game == null) { continue; } var steamData = game.CustomData <SteamDbInfoData>(); if (steamData != null) { steamData.PlayTimeForEver = playTime.Value; } } } catch (Exception ex) { Log.WriteLine(ex.ToString()); } } }
public static void PerformTaskOnGAThread(string blockName, Action taskBlock, long delayInSeconds) { if (endThread) { return; } lock (Instance.threadLock) { DateTime time = DateTime.Now; time = time.AddSeconds(delayInSeconds); TimedBlock timedBlock = new TimedBlock(time, taskBlock, blockName); Instance.AddTimedBlock(timedBlock); threadDeadline = time.AddSeconds(10); #if WINDOWS_WSA || WINDOWS_UWP if (IsThreadFinished()) { StartThread(); } #else if (IsThreadFinished()) { if (Instance.thread != null) { Instance.thread.Join(); } StartThread(); } #endif } }
public static void PerformTaskOnGAThread(string blockName, Action taskBlock, long delayInSeconds) { lock (Instance.threadLock) { DateTime time = DateTime.Now; time = time.AddSeconds(delayInSeconds); TimedBlock timedBlock = new TimedBlock(time, taskBlock, blockName); Instance.id2TimedBlockMap.Add(timedBlock.id, timedBlock); Instance.AddTimedBlock(timedBlock); } }
static void OnDespawned(TimedBlock instance) { Assert.That(instance._exclusiveTimer.IsRunning); instance._exclusiveTimer.Pause(); foreach (var timer in instance._pausedTimers) { Assert.That(!timer.IsRunning); timer.Resume(); } instance._pausedTimers.Clear(); }
public static long ScheduleTimer(double interval, string blockName, Action callback) { lock (Instance.threadLock) { DateTime time = DateTime.Now; time = time.AddSeconds(interval); TimedBlock timedBlock = new TimedBlock(time, callback, blockName); Instance.id2TimedBlockMap.Add(timedBlock.id, timedBlock); Instance.AddTimedBlock(timedBlock); return(timedBlock.id); } }
public LauncherWindowViewModel2() { GameLibrary.GameLibrary.Load(); using (var tb = new TimedBlock($"LauncherWindowViewModel2({ GameLibrary.GameLibrary.Games.Count })#CreateAllGameControls")) { // all controls must be created on STA thread, so better create controls for all games now allGames = GameLibrary.GameLibrary.Games .Select(g => { var gc = new GameControl(g.Key, RebuildGameGrid, ProgressBarStartStop) { Width = Const.GameControlSize.Width, Height = Const.GameControlSize.Height, }; gc.ViewModel.X = (int)Const.GridBorder + Rng.Next(0, (int)(windowSize.Width - Const.MinWindowSize.Width)); gc.ViewModel.Y = (int)Const.GridBorder + Rng.Next(0, (int)(windowSize.Height - Const.MinWindowSize.Height)); return(gc); }) .ToArray(); } RebuildGameGrid(); ProgressBarStartStop(true); // update games GameLibrary.GameLibrary.UpdateAll( (gameId) => GameUpdated(gameId), () => { try { ProgressBarStartStop(false); Application.Current?.Dispatcher.Invoke(() => { RebuildGameGrid(); }); } catch (Exception ex) { Log.WriteLine(ex.ToString()); } }); }
private void RebuildGameGrid() { Application.Current?.Dispatcher.Invoke(() => { using (var tb = new TimedBlock($"RebuildGameGrid()")) { FilteredGameIds = GameLibrary.GameLibrary.ListGames(titleFilter, filter, order, isOrderAscending); var _filteredGameControls = new List <GameControl>(); foreach (var gameId in filteredGameIds) { _filteredGameControls.Add(allGameControls.Single(g => g.ViewModel.GameId == gameId)); } FilteredGameControls = _filteredGameControls; } }); }
/// <summary> /// update games with missing images / informations /// </summary> /// <param name="gameUpdated"></param> public static void UpdateAll(Action <string> gameUpdated, Action updateAllFinished) { try { // update only installed games with no data var gamesToUpdate = Games .Where(g => !g.Value.Removed && g.Value.Data.Length == 0) .Select(g => g.Key); Threading.ThreadAndForget(() => { using (var tb = new TimedBlock($"Update({ gamesToUpdate.Count() })")) { try { //foreach (var gameId in gamesToUpdate) Parallel.ForEach(gamesToUpdate, gameId => { if (UpdateGame(gameId)) { gameUpdated(gameId); } } ); UpdatePlayTimeFromSteamLibrary(); Save(); updateAllFinished(); } catch (Exception ex) { Log.WriteLine(ex.ToString()); } } }); } catch (Exception ex) { Log.WriteLine(ex.ToString()); } }
static void OnSpawned( IEnumerable <TimerInfo> timers, TimerInfo exclusiveTimer, TimedBlock instance) { Assert.That(instance._pausedTimers.IsEmpty()); instance._exclusiveTimer = exclusiveTimer; foreach (var timer in timers) { if (exclusiveTimer == timer) { Assert.That(!timer.IsRunning); timer.Resume(); } else if (timer.IsRunning) { timer.Pause(); instance._pausedTimers.Add(timer); } } }
static void OnSpawned( TimerInfo exclusiveTimer, TimedBlock instance) { Assert.That(instance._pausedTimers.Count == 0); instance._exclusiveTimer = exclusiveTimer; foreach (var timer in _timers.Values) { if (exclusiveTimer == timer) { Assert.That(!timer.IsRunning); timer.Resume(); } else if (timer.IsRunning) { timer.Pause(); instance._pausedTimers.Add(timer); } } }
/// <summary> /// save current library and create backup of old library file /// </summary> public static void Save() { using (var tb = new TimedBlock($"Save({ GameLibraryFile })")) { try { // backup old library if (File.Exists(GameLibraryFile)) { var newBackup = Utils.Compress(File.ReadAllText(GameLibraryFile)); File.WriteAllBytes(Path.Combine(BackupDirectory, GamesLibraryFileName + $"{ BackupExtension }{ DateTime.Now.ToString(TimestampFormat) }"), newBackup); // keep only last week backup files var oldBackupFiles = Directory.GetFiles(BackupDirectory, $"*{ BackupExtension }*"); foreach (var backupToDelete in BackupsOlderThan(7)) { Log.WriteLine($"DeleteBackup({ backupToDelete })"); File.Delete(backupToDelete); } } // save current library File.WriteAllText(GameLibraryFile, Utils.GetFormattedXml(Utils.Serialize(Games))); var stats = new string[] { $"{ nameof(IgdbComData) }={ Games.Count(g => g.Value.Data.Any(d => d is IgdbComData)) }", $"{ nameof(SteamDbInfoData) }={ Games.Count(g => g.Value.Data.Any(d => d is SteamDbInfoData)) }", $"{ nameof(SteamCryoTankNetData) }={ Games.Count(g => g.Value.Data.Any(d => d is SteamCryoTankNetData)) }", $"{ nameof(SalenautsComData) }={ Games.Count(g => g.Value.Data.Any(d => d is SalenautsComData)) }", $"{ nameof(UserData) }={ Games.Count(g => g.Value.Data.Any(d => d is UserData)) }", }; Log.WriteLine($"GameDataStats(All={ Games.Count }, { string.Join(", ", stats) })"); } catch (Exception ex) { Log.WriteLine(ex.ToString()); } } }
/// <summary> /// load existing game images to cache /// </summary> /// <returns></returns> static bool LoadGameImages() { using (var tb = new TimedBlock($"LoadGameImages({ Games.Count })")) { try { GameImageCache.Clear(); Parallel.ForEach(Games.Keys, gameId => { UpdateGameImageCache(gameId, Games[gameId].Image); }); return(true); } catch (Exception ex) { Log.WriteLine(ex.ToString()); } } return(false); }
private void AddTimedBlock(TimedBlock timedBlock) { this.blocks.Enqueue(timedBlock.deadline.Ticks, timedBlock); }
/// <summary> /// Attempt to reuse the pip graph from a previous run /// </summary> private GraphReuseResult AttemptToReuseGraph( LoggingContext outerLoggingContext, int maxDegreeOfParallelism, GraphFingerprint graphFingerprint, IReadOnlyDictionary <string, string> properties, CacheInitializationTask cacheInitializationTask, JournalState journalState, EngineState engineState) { Contract.Ensures(Contract.Result <GraphReuseResult>() != null); GraphCacheCheckStatistics cacheGraphStats = CheckGraphCacheReuse( outerLoggingContext, maxDegreeOfParallelism, graphFingerprint, properties, cacheInitializationTask, journalState, out var serializer, out var inputChanges); // There are 3 cases in which we should reload the graph // - we have a graph cache hit // - the build is configured to reload the graph no matter what // - graph patching is enabled and the reason for cache miss was 'SpecFileChanges' var shouldReload = cacheGraphStats.WasHit || Configuration.Cache.CachedGraphPathToLoad.IsValid || PartialReloadCondition(Configuration.FrontEnd, cacheGraphStats); if (!shouldReload) { return(GraphReuseResult.CreateForNoReuse(inputChanges)); } bool fullReload = !PartialReloadCondition(Configuration.FrontEnd, cacheGraphStats); // Now we actually reload the graph var reloadStats = default(GraphCacheReloadStatistics); using (var tb = TimedBlock <EmptyStruct, GraphCacheReloadStatistics> .Start( outerLoggingContext, Statistics.GraphCacheReload, (context, emptyStruct) => { if (fullReload) { Logger.Log.ReloadingPipGraphStart(context); } else { Logger.Log.PartiallyReloadingEngineState(context); } }, default(EmptyStruct), (context, graphCacheCheckStatistics) => { Logger.Log.PartiallyReloadingEngineStateComplete(context, graphCacheCheckStatistics); m_enginePerformanceInfo.GraphReloadDurationMs = graphCacheCheckStatistics.ElapsedMilliseconds; }, () => reloadStats)) { var reuseResult = fullReload ? ReloadEngineSchedule( serializer, cacheInitializationTask, journalState, tb.LoggingContext, engineState, inputChanges, graphFingerprint?.ExactFingerprint.BuildEngineHash.ToString()) : ReloadPipGraphOnly(serializer, tb.LoggingContext, engineState, inputChanges); // Set telemetry statistics reloadStats.SerializedFileSizeBytes = serializer.BytesDeserialized; reloadStats.Success = !reuseResult.IsNoReuse; return(reuseResult); } }
/// <summary> /// Check if pip graph can be reused. /// /// There are 3 opportunities to determine a graph match. The applicability of each depends on the distributed build roles. /// (1) from engine cache, /// (2) from content cache, and /// (3) from master node (if running on a worker node in a distributed build) /// </summary> private GraphCacheCheckStatistics CheckGraphCacheReuse( LoggingContext outerLoggingContext, int maxDegreeOfParallelism, GraphFingerprint graphFingerprint, IReadOnlyDictionary <string, string> properties, CacheInitializationTask cacheInitializationTask, JournalState journalState, out EngineSerializer serializer, out InputTracker.InputChanges inputChanges) { serializer = CreateEngineSerializer(outerLoggingContext); inputChanges = null; var cacheGraphStats = default(GraphCacheCheckStatistics); using (var timeBlock = TimedBlock <EmptyStruct, GraphCacheCheckStatistics> .Start( outerLoggingContext, Statistics.GraphCacheReuseCheck, (context, emptyStruct) => Logger.Log.CheckingForPipGraphReuseStart(context), default(EmptyStruct), (loggingContext, stats) => { Logger.Log.CheckingForPipGraphReuseComplete(loggingContext, stats); // On misses we want to give the user a message for why there was a miss if (!stats.WasHit) { Contract.Assume(stats.MissReason != GraphCacheMissReason.NoMiss); Logger.Log.GraphNotReusedDueToChangedInput(loggingContext, stats.MissMessageForConsole, stats.MissDescription); } m_enginePerformanceInfo.GraphCacheCheckDurationMs = stats.ElapsedMilliseconds; m_enginePerformanceInfo.GraphCacheCheckJournalEnabled = stats.JournalEnabled; }, () => cacheGraphStats)) { var loggingContext = timeBlock.LoggingContext; var effectiveEnvironmentVariables = FrontEndEngineImplementation.PopulateFromEnvironmentAndApplyOverrides(properties); var availableMounts = MountsTable.CreateAndRegister(loggingContext, Context, Configuration, m_initialCommandLineConfiguration.Startup.Properties); if (!AddConfigurationMountsAndCompleteInitialization(loggingContext, availableMounts)) { return(cacheGraphStats); } cacheGraphStats.JournalEnabled = journalState.IsEnabled; // ************************************************************ // 1. Engine cache check: // ************************************************************ // * Single machine builds // Distributed builds rely on the graph being available via the cache for it to be shared between master // and workers. So even if the master could have had a hit from the engine cache, it must be ignored // since the workers would not be able to retrieve it. if (!HasExplicitlyLoadedGraph(Configuration.Cache) && !Configuration.Schedule.ForceUseEngineInfoFromCache && Configuration.Distribution.BuildRole == DistributedBuildRoles.None) { Contract.Assume( graphFingerprint != null, "When looking up a cached graph on a distributed master or single-machine build, a graph fingerprint must be computed"); InputTracker.MatchResult engineCacheMatchResult = CheckIfAvailableInputsToGraphMatchPreviousRun( loggingContext, serializer, graphFingerprint: graphFingerprint, availableEnvironmentVariables: effectiveEnvironmentVariables, availableMounts: availableMounts, journalState: journalState, maxDegreeOfParallelism: maxDegreeOfParallelism); cacheGraphStats.ObjectDirectoryHit = engineCacheMatchResult.Matches; cacheGraphStats.ObjectDirectoryMissReason = engineCacheMatchResult.MissType; cacheGraphStats.MissReason = engineCacheMatchResult.MissType; cacheGraphStats.MissDescription = engineCacheMatchResult.FirstMissIdentifier; cacheGraphStats.WasHit = engineCacheMatchResult.Matches; cacheGraphStats.InputFilesChecked = engineCacheMatchResult.FilesChecked; // Checking the engine cache may have used a FileChangeTracker and provided information about // files/ContentHash pairs that were unchanged from the previous run. Hold onto this information as it may // be useful when eventually parsing files. // The FileChangeTracker is now up to date. All changed files have been removed from it. It can be reused // for a future build with the same fingerprint, though it may be tracking extra files, for example if // a spec was removed since the previous build. inputChanges = engineCacheMatchResult.InputChanges; } var shouldTryContentCache = !cacheGraphStats.WasHit && Configuration.Distribution.BuildRole != DistributedBuildRoles.Worker && Configuration.Cache.AllowFetchingCachedGraphFromContentCache && !HasExplicitlyLoadedGraph(Configuration.Cache) && (!Configuration.FrontEnd.UseSpecPublicFacadeAndAstWhenAvailable.HasValue || !Configuration.FrontEnd.UseSpecPublicFacadeAndAstWhenAvailable.Value); // ************************************************************ // 2. Content cache check: // ************************************************************ // * Single machine builds that missed earlier // * Distributed masters // This is the only valid place for the master to get a hit since it must be in the cache for the // workers to get it. if (shouldTryContentCache) { // Since an in-place match did not succeed, we need a readied cache to try again. // TODO: This logs an error if it fails. We are assuming some later thing will ensure that, if failed, // the engine fails overall. Possible <CacheInitializer> possibleCacheInitializerForFallback = cacheInitializationTask.GetAwaiter().GetResult(); if (possibleCacheInitializerForFallback.Succeeded) { CacheInitializer cacheInitializerForFallback = possibleCacheInitializerForFallback.Result; using (EngineCache cacheForFallback = cacheInitializerForFallback.CreateCacheForContext()) { var cacheGraphProvider = new CachedGraphProvider( loggingContext, Context, cacheForFallback, FileContentTable, maxDegreeOfParallelism); var cachedGraphDescriptor = cacheGraphProvider.TryGetPipGraphCacheDescriptorAsync(graphFingerprint, effectiveEnvironmentVariables, availableMounts.MountsByName).Result; if (cachedGraphDescriptor == null) { // There was no matching fingerprint in the cache. Record the status for logging before returning. cacheGraphStats.CacheMissReason = GraphCacheMissReason.FingerprintChanged; SetMissReasonIfUnset(ref cacheGraphStats, cacheGraphStats.CacheMissReason); return(cacheGraphStats); } var fetchEngineScheduleContent = EngineSchedule.TryFetchFromCacheAsync( loggingContext, Context, cacheForFallback, cachedGraphDescriptor, serializer, FileContentTable, m_tempCleaner).Result; if (!fetchEngineScheduleContent) { cacheGraphStats.CacheMissReason = GraphCacheMissReason.NoPreviousRunToCheck; SetMissReasonIfUnset(ref cacheGraphStats, cacheGraphStats.CacheMissReason); return(cacheGraphStats); } // If a distributed master, take note of the graph fingerprint if (Configuration.Distribution.BuildRole == DistributedBuildRoles.Master) { Contract.Assert(cachedGraphDescriptor != null); m_masterService.CachedGraphDescriptor = cachedGraphDescriptor; } Logger.Log.FetchedSerializedGraphFromCache(outerLoggingContext); cacheGraphStats.CacheMissReason = GraphCacheMissReason.NoMiss; cacheGraphStats.MissReason = cacheGraphStats.CacheMissReason; cacheGraphStats.WasHit = true; } } else { cacheGraphStats.CacheMissReason = GraphCacheMissReason.CacheFailure; SetMissReasonIfUnset(ref cacheGraphStats, cacheGraphStats.CacheMissReason); return(cacheGraphStats); } } // ************************************************************ // 3. Query distributed master // ************************************************************ // * Distributed workers only if (Configuration.Distribution.BuildRole == DistributedBuildRoles.Worker) { Contract.Assume( graphFingerprint == null, "Distributed workers should request a graph fingerprint from the master (not compute one locally)"); Possible <CacheInitializer> possibleCacheInitializerForWorker = cacheInitializationTask.GetAwaiter().GetResult(); Contract.Assume(possibleCacheInitializerForWorker.Succeeded, "Workers must have a valid cache"); CacheInitializer cacheInitializerForWorker = possibleCacheInitializerForWorker.Result; using (EngineCache cacheForWorker = cacheInitializerForWorker.CreateCacheForContext()) { PipGraphCacheDescriptor schedulerStateDescriptor; if (!m_workerService.TryGetBuildScheduleDescriptor(out schedulerStateDescriptor) || !EngineSchedule.TryFetchFromCacheAsync( outerLoggingContext, Context, cacheForWorker, schedulerStateDescriptor, serializer, FileContentTable, m_tempCleaner).Result) { cacheGraphStats.CacheMissReason = GraphCacheMissReason.NoFingerprintFromMaster; cacheGraphStats.MissReason = cacheGraphStats.CacheMissReason; return(cacheGraphStats); } AsyncOut <AbsolutePath> symlinkFileLocation = new AsyncOut <AbsolutePath>(); if (!SymlinkDefinitionFileProvider.TryFetchWorkerSymlinkFileAsync( outerLoggingContext, Context.PathTable, cacheForWorker, Configuration.Layout, m_workerService, symlinkFileLocation).Result) { cacheGraphStats.CacheMissReason = GraphCacheMissReason.NoFingerprintFromMaster; cacheGraphStats.MissReason = cacheGraphStats.CacheMissReason; return(cacheGraphStats); } m_workerSymlinkDefinitionFile = symlinkFileLocation.Value; // Success. Populate the stats cacheGraphStats.WasHit = true; cacheGraphStats.WorkerHit = true; cacheGraphStats.MissReason = GraphCacheMissReason.NoMiss; cacheGraphStats.CacheMissReason = GraphCacheMissReason.NoMiss; } } } return(cacheGraphStats); }
/// <summary> /// load game library from xml and add new games /// </summary> /// <returns>games to update (without images)</returns> static bool LoadGames(string libraryToLoad = null) { using (var tb = new TimedBlock($"LoadGames({ libraryToLoad })")) { try { Games.Clear(); // deserialize library DeserializeLibrary(libraryToLoad); if (!string.IsNullOrEmpty(GamesDirectory)) { // add new games from current games folder foreach (var gameShortcut in Directory.GetFiles(GamesDirectory, "*.*", SearchOption.AllDirectories)) { var fileInfo = new FileInfo(gameShortcut); if (!SupportedGameExtensions.Contains(fileInfo.Extension.ToLower())) { continue; } var gameTitle = fileInfo.Name.Replace(fileInfo.Extension, string.Empty); var gameId = GetGameId(gameTitle); if (!Games.ContainsKey(gameId)) { var newGameInfo = new GameInfo { Title = gameTitle, Shortcut = gameShortcut, Added = DateTime.Now, }; // add new game Games.AddOrUpdate(gameId, newGameInfo, (key, oldGameInfo) => newGameInfo); } // update game shortcut everytime Games[gameId].Shortcut = gameShortcut; } } // mark games with not-existing shortcuts as removed foreach (var game in Games.Where(g => g.Value.Shortcut != null && !File.Exists(g.Value.Shortcut))) { game.Value.Shortcut = null; } return(true); } catch (Exception ex) { Log.WriteLine(ex.ToString()); } } return(false); }
/// <summary> /// fix differences in old vs new library /// </summary> /// <param name="oldLibrary"></param> /// <param name="newLibrary"></param> /// <returns></returns> static ConcurrentDictionary <string, GameInfo> FixLibrary(string oldLibrary, ConcurrentDictionary <string, GameInfo> newLibrary) { using (var tb = new TimedBlock($"FixLibrary({ (string.IsNullOrEmpty(oldLibrary) ? "null" : oldLibrary) }, { newLibrary.Count })")) { try { var oldLibraryXml = new XmlDocument(); if (!string.IsNullOrEmpty(oldLibrary)) { oldLibraryXml.Load(oldLibrary); } var nsmgr = new XmlNamespaceManager(oldLibraryXml.NameTable); nsmgr.AddNamespace("a", "http://schemas.microsoft.com/2003/10/Serialization/Arrays"); nsmgr.AddNamespace("i", "http://www.w3.org/2001/XMLSchema-instance"); nsmgr.AddNamespace("d3p1", "http://schemas.datacontract.org/2004/07/GameLibrary"); nsmgr.AddNamespace("d4p1", "http://schemas.datacontract.org/2004/07/GameLibrary.GameDataProviders"); foreach (var newGame in newLibrary) { if (newGame.Value.LastDataUpdate == null) { newGame.Value.LastDataUpdate = new ConcurrentDictionary <string, DateTime>(); } else { // remove last update values of missing data var lastUpdateKeys = newGame.Value.LastDataUpdate.Keys.ToArray(); foreach (var lastUpdateKey in lastUpdateKeys) { if (!newGame.Value.Data.Any(d => d.DataType == lastUpdateKey)) { newGame.Value.LastDataUpdate.TryRemove(lastUpdateKey, out DateTime value); } } } // merge old library if (!string.IsNullOrEmpty(oldLibrary)) { var oldGame = oldLibraryXml.DocumentElement .SelectSingleNode($"/a:ArrayOfKeyValueOfstringGameInfo7FtjRveh/a:KeyValueOfstringGameInfo7FtjRveh[a:Key='{ newGame.Key }']/a:Value", nsmgr); if (oldGame == null) { Log.WriteLine($"deserializedGame { newGame.Value.Title } not found in old library"); continue; } var steamDbData = newGame.Value.CustomData <SteamDbInfoData>(); if (steamDbData != null) { if (Int32.TryParse(oldGame.SelectSingleNode($"d3p1:Data/d4p1:GameData[@i:type='d4p1:SteamDbInfoData']/d4p1:MetacriticScore", nsmgr)?.InnerText ?? string.Empty, out int rating)) { steamDbData.Rating = rating; } steamDbData.Summary = steamDbData.Summary ?? oldGame.SelectSingleNode($"d3p1:Data/d4p1:GameData[@i:type='d4p1:SteamDbInfoData']/d4p1:Description", nsmgr)?.InnerText; if (steamDbData.Summary == string.Empty) { steamDbData.Summary = null; } steamDbData.AppId = steamDbData.AppId ?? oldGame.SelectSingleNode($"d3p1:Data/d4p1:GameData[@i:type='d4p1:SteamDbInfoData']/d4p1:AppId", nsmgr)?.InnerText; steamDbData.SourceGameTitle = steamDbData.SourceGameTitle ?? oldGame.SelectSingleNode($"d3p1:Data/d4p1:GameData[@i:type='d4p1:SteamDbInfoData']/d4p1:SourceGameTitle", nsmgr)?.InnerText; if (steamDbData.SourceGameTitle == string.Empty) { steamDbData.SourceGameTitle = null; } if (bool.TryParse(oldGame.SelectSingleNode($"d3p1:Data/d4p1:GameData[@i:type='d4p1:SteamDbInfoData']/d4p1:GamepadFriendly", nsmgr)?.InnerText ?? string.Empty, out bool gf)) { steamDbData.GamepadFriendly = gf; } } var salenautsComData = newGame.Value.CustomData <SalenautsComData>(); if (salenautsComData != null) { salenautsComData.SourceGameTitle = salenautsComData.SourceGameTitle ?? oldGame.SelectSingleNode($"d3p1:Data/d4p1:GameData[@i:type='d4p1:SalenautsComData']/d4p1:SourceGameTitle", nsmgr)?.InnerText; if (salenautsComData.SourceGameTitle == string.Empty) { salenautsComData.SourceGameTitle = null; } } var userData = newGame.Value.CustomData <UserData>(); if (userData != null) { userData.SourceGameTitle = userData.SourceGameTitle ?? oldGame.SelectSingleNode($"d3p1:Data/d4p1:GameData[@i:type='d4p1:UserData']/d4p1:SourceGameTitle", nsmgr)?.InnerText; if (userData.SourceGameTitle == string.Empty) { userData.SourceGameTitle = null; } if (userData.SourceGameTitle == null) { userData.SourceGameTitle = newGame.Value.Title; } if (bool.TryParse(oldGame.SelectSingleNode($"d3p1:Data/d4p1:GameData[@i:type='d4p1:UserData']/d4p1:GamepadFriendly", nsmgr)?.InnerText ?? string.Empty, out bool gf)) { userData.GamepadFriendly = gf; } } } } return(newLibrary); } catch (Exception ex) { Log.WriteLine(ex.ToString()); return(null); } } }