public GameResultItemViewModel(Game game, VpdbRelease release, VpdbVersion version, VpdbTableFile tableFile, ICommand closeCommand) { Game = game; Version = version; Release = release; TableFile = tableFile; SelectResult.Subscribe(_ => { GameManager.LinkRelease(Game, release, tableFile.Reference.Id); MessageManager.LogReleaseLinked(game, release, tableFile.Reference.Id); closeCommand.Execute(null); }); }
public GameItemViewModel(Game game, IDependencyResolver resolver) { Game = game; _logger = resolver.GetService<ILogger>(); _vpdbClient = resolver.GetService<IVpdbClient>(); _gameManager = resolver.GetService<IGameManager>(); _messageManager = resolver.GetService<IMessageManager>(); var threadManager = resolver.GetService<IThreadManager>(); // release identify IdentifyRelease = ReactiveCommand.CreateAsyncObservable(_ => _vpdbClient.Api.GetReleasesBySize(Game.FileSize, MatchThreshold).SubscribeOn(threadManager.WorkerScheduler)); IdentifyRelease.Select(releases => releases .Select(release => new {release, release.Versions}) .SelectMany(x => x.Versions.Select(version => new {x.release, version, version.Files})) .SelectMany(x => x.Files.Select(file => new GameResultItemViewModel(game, x.release, x.version, file, CloseResults))) ).Subscribe(x => { var releases = x as GameResultItemViewModel[] ?? x.ToArray(); var numMatches = 0; _logger.Info("Found {0} releases for game to identify.", releases.Length); GameResultItemViewModel match = null; foreach (var vm in releases) { if (game.Filename == vm.TableFile.Reference.Name && game.FileSize == vm.TableFile.Reference.Bytes) { numMatches++; match = vm; } } _logger.Info("Found {0} identical match(es).", numMatches); // if file name and file size are identical, directly match. if (numMatches == 1 && match != null) { _logger.Info("File name and size are equal to local release, linking."); _gameManager.LinkRelease(match.Game, match.Release, match.TableFile.Reference.Id); _messageManager.LogReleaseLinked(match.Game, match.Release, match.TableFile.Reference.Id); } else { _logger.Info("View model updated with identified releases."); IdentifiedReleases = releases; HasExecuted = true; } }, exception => _vpdbClient.HandleApiError(exception, "identifying a game by file size")); //SyncToggled // .Where(_ => Game.IsSynced && Game.HasRelease) // .Subscribe(_ => { GameManager.Sync(Game); }); // handle errors IdentifyRelease.ThrownExceptions.Subscribe(e => { _logger.Error(e, "Error matching game."); }); // spinner IdentifyRelease.IsExecuting.ToProperty(this, vm => vm.IsExecuting, out _isExecuting); // result switch IdentifyRelease.Select(r => r.Count > 0).Subscribe(hasResults => { HasResults = hasResults; }); // close button CloseResults.Subscribe(_ => { HasExecuted = false; }); // identify button visibility this.WhenAny( vm => vm.HasExecuted, vm => vm.Game.HasRelease, vm => vm.IsExecuting, (hasExecuted, hasRelease, isExecuting) => !hasExecuted.Value && !hasRelease.Value && !isExecuting.Value ).ToProperty(this, vm => vm.ShowIdentifyButton, out _showIdentifyButton); }
public IGameManager Sync(Game game) { _downloadManager.DownloadRelease(game.ReleaseId, game.FileId); return this; }
/// <summary> /// Applies table script changes from a previous version to an updated version. /// </summary> /// <param name="game">Game where to apply changes to</param> /// <param name="baseFileName">File name of the previous version</param> /// <param name="fileToPatchId">File ID of the updated version</param> private void PatchGame(Game game, string baseFileName, string fileToPatchId) { // todo create log message when something goes wrong. var baseFileId = game.PreviousFileId; _logger.Info("Patching file {0} with changes from file {1}", fileToPatchId, baseFileId); // get table scripts for original files var baseFile = _databaseManager.GetTableFile(game.ReleaseId, baseFileId).Reference; var fileToPatch = _databaseManager.GetTableFile(game.ReleaseId, fileToPatchId).Reference; var originalBaseScript = (string)baseFile?.Metadata["table_script"]; var originalScriptToPatch = (string)fileToPatch?.Metadata["table_script"]; if (originalBaseScript == null || originalScriptToPatch == null) { _logger.Warn("Got no script for file {0}, aborting.", originalBaseScript == null ? baseFileId : fileToPatchId); return; } // get script from local (potentially modified) table file var localBaseFilePath = Path.Combine(game.Platform.TablePath, baseFileName); var localBaseScript = _visualPinballManager.GetTableScript(localBaseFilePath); if (localBaseScript == null) { _logger.Warn("Error reading table script from {0}.", localBaseFilePath); return; } if (localBaseScript == originalBaseScript) { _logger.Info("No changes between old local and remote version, so nothing to patch. We're done patching."); return; } _logger.Info("Script changes between old local and old remote table detected, so let's merge those changes!"); // sanity check: compare extracted script from vpdb with our own var localFilePathToPatch = Path.Combine(game.Platform.TablePath, game.Filename); var localScriptToPatch = _visualPinballManager.GetTableScript(localFilePathToPatch); if (localScriptToPatch != originalScriptToPatch) { _logger.Error("Script in metadata ({0} bytes) is not identical to what we've extracted from the download ({1} bytes).", originalScriptToPatch.Length, localScriptToPatch.Length); return; } // we need line arrays for the merge tool var originalBaseScriptLines = originalBaseScript.Split('\n'); var originalScriptToPatchLines = originalScriptToPatch.Split('\n'); var localBaseScriptLines = localBaseScript.Split('\n'); // do the three-way merge var result = Diff.Diff3Merge(localBaseScriptLines, originalBaseScriptLines, originalScriptToPatchLines, true); var patchedScriptLines = new List<string>(); var failed = 0; var succeeded = 0; foreach (var okBlock in result.Select(block => block as Diff.MergeOkResultBlock)) { if (okBlock != null) { succeeded++; patchedScriptLines.AddRange(okBlock.ContentLines); } else { failed++; } } if (failed > 0) { _logger.Warn("Merge failed ({0} block(s) ok, {1} block(s) conflicted. Needs manual resolving", succeeded, failed); return; } var patchedScript = string.Join("\n", patchedScriptLines); _logger.Info("Successfully merged changes - {0} block(s) applied.", succeeded); // save script to table try { _visualPinballManager.SetTableScript(localFilePathToPatch, patchedScript); } catch (Exception e) { _logger.Error(e, "Error writing patched script back to table file."); return; } game.PatchedTableScript = patchedScript; _logger.Info("Successfully wrote back script to table file."); }
public void LinkRelease(Game game, VpdbRelease release, string fileId) { // update in case we didn't catch the last version. _vpdbClient.Api.GetRelease(release.Id).Subscribe(updatedRelease => { _logger.Info("Linking {0} to {1} ({2})", game, release, fileId); _databaseManager.AddOrUpdateRelease(release); game.ReleaseId = release.Id; game.FileId = fileId; _databaseManager.Save(); }, exception => _vpdbClient.HandleApiError(exception, "retrieving release details during linking")); }