public IPinballXManager RenameGame(string oldFileName, AggregatedGame game) { var xmlPath = Path.Combine(game.System.DatabasePath, game.XmlGame.DatabaseFile); var newFilename = Path.GetFileNameWithoutExtension(game.FileName); if (_settingsManager.Settings.ReformatXml) { // read xml var menu = _marshallManager.UnmarshallXml(xmlPath); // get game var xmlGame = menu.Games.FirstOrDefault(g => g.FileName == oldFileName); if (xmlGame == null) { throw new InvalidOperationException($"Cannot find game with name {oldFileName} in {xmlPath}."); } // update game xmlGame.FileName = newFilename; // save xml _marshallManager.MarshallXml(menu, xmlPath); } else { var xml = _file.ReadAllText(xmlPath); xml = xml.Replace($"name=\"{oldFileName}\"", $"name=\"{newFilename}\""); _file.WriteAllText(xmlPath, xml); _logger.Info("Replaced name \"{0}\" with \"{1}\" in {2}.", oldFileName, newFilename, xmlPath); } return(this); }
public void MapGame(AggregatedGame game, VpdbRelease release, string fileId) { // update in case we didn't catch the last version. _vpdbManager.GetRelease(release.Id).Subscribe(updatedRelease => { _logger.Info("Mapping {0} to {1} ({2})", game, release, fileId); GetOrCreateMapping(game).Map(release, fileId); }, exception => _vpdbClient.HandleApiError(exception, "retrieving release details during linking")); }
public Message LogReleaseLinked(AggregatedGame game, VpdbRelease release, string fileId) { var msg = new Message(MessageType.ReleaseLinked, MessageLevel.Info, new Dictionary <string, string> { { DataGameName, game.FileId }, { DataRelease, release.Id }, { DataFile, fileId } }); return(Log(msg)); }
public void UnHideGame(AggregatedGame game) { _logger.Info("Unhiding game {0}", game.FileId); if (!game.HasMapping) { _logger.Error("Cannot unhide game without mapping."); return; } game.Mapping.IsHidden = false; }
/* * public IDownloadManager DownloadRelease(string releaseId, string currentFileId = null) * { * // ignore if already launched * if (_currentlyDownloading.Contains(releaseId)) { * _logger.Warn("Skipping already queued release {0}.", releaseId); * return this; * } * _currentlyDownloading.Add(releaseId); * * // retrieve release details * _logger.Info("Retrieving details for release {0} before downloading..", releaseId); * _vpdbManager.GetRelease(releaseId).ObserveOn(Scheduler.Default).Subscribe(release => { * * // match file based on settings * var file = FindLatestFile(release, currentFileId); * * // check if match * if (file == null) { * _logger.Info("Nothing matched current flavor configuration, skipping."); * _currentlyDownloading.Remove(releaseId); * return; * } * * // download * DownloadRelease(release, file); * * }, exception => _vpdbManager.HandleApiError(exception, "retrieving release details during download")); * * return this; * }*/ public IDownloadManager DownloadRelease(AggregatedGame game) { var release = game.MappedRelease; var tableFile = game.MappedTableFile; // also fetch game data for media & co _vpdbManager.GetGame(release.Game.Id, true).Subscribe(g => { var version = _databaseManager.GetVersion(release.Id, tableFile.Reference.Id); _logger.Info($"Checking what to download for {g.DisplayName} - {release.Name} v{version?.Name} ({tableFile.Reference.Id})"); var gameName = release.Game.DisplayName; var system = _pinballXManager.FindSystem(tableFile); var platform = tableFile.Compatibility[0].Platform; var fileTypes = new List <FileType>(); // check if backglass image needs to be downloaded var backglassImagePath = Path.Combine(system.MediaPath, Job.MediaBackglassImages); if (!FileBaseExists(backglassImagePath, gameName)) { fileTypes.Add(FileType.BackglassImage); _jobManager.AddJob(new Job(release, g.Backglass, FileType.BackglassImage, platform)); } // check if wheel image needs to be downloaded var wheelImagePath = Path.Combine(system.MediaPath, Job.MediaWheelImages); if (!FileBaseExists(wheelImagePath, gameName)) { fileTypes.Add(FileType.WheelImage); _jobManager.AddJob(new Job(release, g.Logo, FileType.WheelImage, platform)); } // queue table shot var tableImage = Path.Combine(system.MediaPath, Job.MediaTableImages); if (!FileBaseExists(tableImage, gameName)) { fileTypes.Add(FileType.TableImage); _jobManager.AddJob(new Job(release, tableFile.PlayfieldImage, FileType.TableImage, platform)); } // todo check for ROM to be downloaded, path at HKEY_CURRENT_USER\SOFTWARE\Freeware\Visual PinMame\globals // todo also queue all remaining non-table files of the release. // queue for download var job = new Job(release, tableFile, FileType.TableFile, platform); fileTypes.Add(FileType.TableFile); _logger.Info("Found {0} file type(s) to download: {1}", fileTypes.Count, string.Join(", ", fileTypes)); _jobManager.AddJob(job); game.SetJob(job); _logger.Info("Created new job for {0} - {1} v{2} ({3}): {4}", job.Release.Game.DisplayName, job.Release.Name, job.Version.Name, job.File.Id, tableFile.ToString()); _currentlyDownloading.Remove(release.Id); }, exception => _vpdbManager.HandleApiError(exception, "retrieving game details during download")); return(this); }
private Mapping GetOrCreateMapping(AggregatedGame game) { if (game.HasMapping) { return(game.Mapping); } var system = FindSystem(game); var mapping = game.Mapping ?? new Mapping(game, system); game.Update(mapping); system.Mappings.Add(mapping); return(mapping); }
public GameResultItemViewModel(AggregatedGame game, VpdbRelease release, VpdbVersion version, VpdbTableFile tableFile, ICommand closeCommand) { Game = game; Version = version; Release = release; TableFile = tableFile; SelectResult = ReactiveCommand.Create(() => { GameManager.MapGame(game, release, tableFile.Reference.Id); //MessageManager.LogReleaseLinked(game, release, tableFile.Reference.Id); closeCommand.Execute(null); }); }
public void HideGame(AggregatedGame game) { _logger.Info("Hiding game {0}", game.FileId); if (game.FilePath == null) { _logger.Error("Cannot hide game without local file."); return; } var mapping = GetOrCreateMapping(game); mapping.IsHidden = true; }
private PinballXSystem FindSystem(AggregatedGame game) { if (game.HasSystem) { return(game.System); } var tablePath = PathHelper.NormalizePath(Path.GetDirectoryName(game.FilePath)); var system = _pinballXManager.Systems.FirstOrDefault(s => s.TablePath == tablePath); if (system == null) { throw new Exception($"Got game at {game.FilePath} but no systems that match path ({string.Join(", ", _pinballXManager.Systems.Select(s => s.TablePath))})."); } return(system); }
public void AddGame(AggregatedGame game) { if (game.HasXmlGame) { throw new InvalidOperationException("Game already in XML database."); } if (game.MappedRelease == null || game.MappedTableFile == null) { throw new InvalidOperationException("Cannot add game without release mapping."); } var xmlGame = new PinballXGame { FileName = Path.GetFileNameWithoutExtension(game.FileDisplayName), Description = $"{game.MappedRelease.Game.Title} ({game.MappedRelease.Game.Manufacturer} {game.MappedRelease.Game.Year})", Manufacturer = game.MappedRelease.Game.Manufacturer, Year = game.MappedRelease.Game.Year.ToString(), System = game.System }; _pinballXManager.AddGame(xmlGame); }
/// <summary> /// Returns true if a given game should be displayed or false otherwise. /// /// Visibility is determined in function of platform filters and enabled /// platforms. /// </summary> /// <param name="game">Game</param> /// <returns>True if visible, false otherwise</returns> private bool IsGameVisible(AggregatedGame game) { if (game.System != null) { if (_systemFilter.Contains(game.System.Name)) { return(false); } if (game.HasXmlGame && _databaseFileFilter[game.System].Contains(game.XmlGame.DatabaseFile)) { return(false); } if (game.HasXmlGame && _executableFilter[game.System].Contains(game.XmlGame.AlternateExe ?? "")) { return(false); } } if (StatusFilter == DataStatus.FilesNotInDatabase && game.HasXmlGame) { return(false); } if (StatusFilter == DataStatus.GamesNotOnDisk && game.HasLocalFile) { return(false); } if (StatusFilter == DataStatus.UnmappedFiles && game.HasMapping) { return(false); } if (!ShowHidden && (game.HasMapping && game.Mapping.IsHidden)) { return(false); } if (!ShowDisabled && game.HasXmlGame && game.XmlGame.Enabled != null && !"true".Equals(game.XmlGame.Enabled, StringComparison.InvariantCultureIgnoreCase)) { return(false); } return(true); }
/// <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(AggregatedGame 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 IGameManager Sync(AggregatedGame game) { _downloadManager.DownloadRelease(game); return(this); }
public void DownloadGame(AggregatedGame game) { _downloadManager.DownloadRelease(game); }
public GameItemViewModel(AggregatedGame game, IDependencyResolver resolver) { Game = game; _logger = resolver.GetService <ILogger>(); _vpdbClient = resolver.GetService <IVpdbClient>(); _gameManager = resolver.GetService <IGameManager>(); _pinballXManager = resolver.GetService <IPinballXManager>(); _messageManager = resolver.GetService <IMessageManager>(); var threadManager = resolver.GetService <IThreadManager>(); game.WhenAnyValue(g => g.IsDownloading).ToProperty(this, vm => vm.IsDownloading, out _isDownloading); game.WhenAnyValue(g => g.IsQueued).ToProperty(this, vm => vm.IsQueued, out _isQueued); // release identify IdentifyRelease = ReactiveCommand.CreateFromObservable(() => _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.MapGame(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; ShowResults = true; } }, exception => _vpdbClient.HandleApiError(exception, "identifying a game by file size")); //var canSync = this.WhenAnyValue(x => x.Game.IsSynced, x => x.Game.HasRelease, (isSynced, hasRelease) => isSynced && hasRelease); //var canSync = this.WhenAnyValue(x => x.Game.Mapping.IsSynced, x => x.Game.MappedRelease, (isSynced, rls) => isSynced && rls != null); //SyncToggled = ReactiveCommand.Create(() => { _gameManager.Sync(Game); }, canSync); // handle errors IdentifyRelease.ThrownExceptions.Subscribe(e => { _logger.Error(e, "Error matching game."); }); // result switch IdentifyRelease.Select(r => r.Count > 0).Subscribe(hasResults => { HasResults = hasResults; }); // add to db button AddGame = ReactiveCommand.Create(() => _gameManager.AddGame(Game)); // remove from db button RemoveGame = ReactiveCommand.Create(() => _pinballXManager.RemoveGame(Game.XmlGame)); // close button CloseResults = ReactiveCommand.Create(() => { ShowResults = false; }); // hide button HideGame = ReactiveCommand.Create(() => _gameManager.HideGame(Game)); // unhide button UnHideGame = ReactiveCommand.Create(() => _gameManager.UnHideGame(Game)); // download button DownloadMissing = ReactiveCommand.Create(() => _gameManager.DownloadGame(Game)); // spinner IdentifyRelease.IsExecuting.ToProperty(this, vm => vm.IsExecuting, out _isExecuting); // global buttons hide this.WhenAnyValue( vm => vm.ShowResults, vm => vm.IsExecuting, vm => vm.IsDownloading, vm => vm.IsQueued, (showResults, isExecuting, isDownloading, isQueued) => !showResults && !isExecuting && !isDownloading && !isQueued ).ToProperty(this, vm => vm.ShowButtons, out _showButtons); // identify button visibility this.WhenAny( vm => vm.Game.HasLocalFile, vm => vm.Game.MappedTableFile, vm => vm.ShowButtons, (hasLocalFile, mappedFile, showButtons) => hasLocalFile.Value && mappedFile.Value == null && showButtons.Value ).ToProperty(this, vm => vm.ShowIdentifyButton, out _showIdentifyButton); // hide button visibility this.WhenAny( vm => vm.Game.Mapping, vm => vm.Game.HasLocalFile, vm => vm.Game.HasMappedRelease, vm => vm.Game.HasXmlGame, vm => vm.ShowButtons, (mapping, hasLocalFile, hasMappedRelease, hasXmlGame, showButtons) => (mapping.Value == null || !mapping.Value.IsHidden) && hasLocalFile.Value && !hasMappedRelease.Value && !hasXmlGame.Value && showButtons.Value ).ToProperty(this, vm => vm.ShowHideButton, out _showHideButton); // unhide button visibility this.WhenAny( vm => vm.Game.Mapping, vm => vm.ShowButtons, (mapping, showButtons) => mapping.Value != null && mapping.Value.IsHidden && showButtons.Value ).ToProperty(this, vm => vm.ShowUnHideButton, out _showUnHideButton); // add to db button visibility this.WhenAny( vm => vm.Game.HasLocalFile, vm => vm.Game.HasXmlGame, vm => vm.Game.MappedTableFile, vm => vm.ShowButtons, (hasLocalFile, hasXmlGame, mappedFile, showButtons) => hasLocalFile.Value && !hasXmlGame.Value && mappedFile.Value != null && showButtons.Value ).ToProperty(this, vm => vm.ShowAddToDbButton, out _showAddToDbButton); // remove from db button visibility this.WhenAny( vm => vm.Game.HasLocalFile, vm => vm.Game.HasXmlGame, vm => vm.ShowButtons, (hasLocalFile, hasXmlGame, showButtons) => !hasLocalFile.Value && hasXmlGame.Value && showButtons.Value ).ToProperty(this, vm => vm.ShowRemoveFromDbButton, out _showRemoveFromDbButton); // download button visibility this.WhenAny( vm => vm.Game.HasLocalFile, vm => vm.Game.MappedTableFile, vm => vm.ShowButtons, (hasLocalFile, mappedFile, showButtons) => !hasLocalFile.Value && mappedFile.Value != null && showButtons.Value ).ToProperty(this, vm => vm.ShowDownloadMissingButton, out _showDownloadMissingButton); // download progress this.WhenAnyValue(vm => vm.IsDownloading).Subscribe(isDownloading => { if (isDownloading) { Game.MappedJob.WhenAnyValue(j => j.TransferPercent) .Sample(TimeSpan.FromMilliseconds(300)) .Where(x => !Game.MappedJob.IsFinished) .Subscribe(progress => { // on main thread System.Windows.Application.Current.Dispatcher.Invoke(() => { DownloadPercent = progress; }); }); } }); }