/// <summary> /// Subscribes to a given system if not already subscribed and kicks /// off <see cref="GamesUpdated"/>. /// </summary> /// <param name="system">System to kick-off and subscribe</param> private void AddSystem(PinballXSystem system) { // skip if already subscribed if (_systemDisposables.ContainsKey(system)) { return; } // kick off if (system.Enabled) { // current games system.Games.Keys.ToList().ForEach(databaseFile => { _gamesUpdated.OnNext(new Tuple <PinballXSystem, string, List <PinballXGame> >(system, databaseFile, system.Games[databaseFile])); }); // current mappings _mappingsUpdated.OnNext(new Tuple <PinballXSystem, List <Mapping> >(system, system.Mappings.ToList())); } // relay future changes var systemDisponsable = new CompositeDisposable { system.GamesUpdated.Subscribe(x => _gamesUpdated.OnNext(new Tuple <PinballXSystem, string, List <PinballXGame> >(system, x.Item1, x.Item2))), system.MappingsUpdated.Subscribe(mappings => _mappingsUpdated.OnNext(new Tuple <PinballXSystem, List <Mapping> >(system, mappings))) }; _systemDisposables.Add(system, systemDisponsable); }
public SystemMapping UnmarshallMappings(string databaseFile, PinballXSystem system) { if (!_file.Exists(databaseFile)) { return(new SystemMapping()); } _logger.Info("Reading mappings from {0}...", databaseFile); try { using (var sr = new StreamReader(databaseFile)) using (JsonReader reader = new JsonTextReader(sr)) { try { var systemMapping = _serializer.Deserialize <SystemMapping>(reader); reader.Close(); return(systemMapping); } catch (Exception e) { _logger.Error(e, "Error parsing vpdb.json, deleting and ignoring."); _crashManager.Report(e, "json"); reader.Close(); File.Delete(databaseFile); return(new SystemMapping()); } } } catch (Exception e) { _logger.Error(e, "Error reading vpdb.json, ignoring."); _crashManager.Report(e, "json"); return(new SystemMapping()); } }
/// <summary> /// Moves a downloaded file to the table folder of the platform. /// </summary> /// <param name="job">Job of downloaded file</param> /// <param name="system">System of the downloaded file</param> private void MoveDownloadedFile(Job job, PinballXSystem system) { // move downloaded file to table folder if (job.FilePath != null && File.Exists(job.FilePath)) { try { var dest = job.GetFileDestination(system); if (dest != null && !File.Exists(dest)) { _logger.Info("Moving downloaded file from \"{0}\" to \"{1}\"...", job.FilePath, dest); File.Move(job.FilePath, dest); } else { // todo see how to handle, probably name it differently. _logger.Warn("File \"{0}\" already exists at destination!", dest); } } catch (Exception e) { _logger.Error(e, "Error moving downloaded file."); _crashManager.Report(e, "fs"); } } else { _logger.Error("Downloaded file \"{0}\" does not exist.", job.FilePath); } }
/// <summary> /// Enables XML database and table folder watching for the given system. /// </summary> /// <param name="system">System to initialize</param> private void InitSystem(PinballXSystem system) { // xml database watching (triggered on change of system's `Enabled`) system.Initialize(); // folder watching: submit all systems to IFileSystemWatcher to figure out what to do system.WhenAnyValue(s => s.Enabled, s => s.TablePath).Subscribe(_ => _watcher.WatchTables(Systems)); }
public SystemItemViewModel(GamesViewModel parent, PinballXSystem system) { _parent = parent; System = system; ToggleDetails = ReactiveCommand.Create(() => { IsExpanded = !IsExpanded; }); this.WhenAnyValue(vm => vm.IsExpanded).Select(expanded => expanded ? 180d : 0d).ToProperty(this, vm => vm.ExpanderRotation, out _expanderRotation); }
/// <summary> /// Unsubscribes from a system and announce to <see cref="GamesUpdated"/>. /// </summary> /// <param name="system">System to unsubscribe from</param> private void RemoveSystem(PinballXSystem system) { _logger.Info("Removing system \"{0}\".", system.Name); // announce system removal system.Games.Keys.ToList().ForEach(databaseFile => _gamesUpdated.OnNext(new Tuple <PinballXSystem, string, List <PinballXGame> >(system, databaseFile, new List <PinballXGame>()))); // unsubscribe _systemDisposables[system]?.Dispose(); _systemDisposables.Remove(system); }
public void OnDatabaseFileFilterChanged(PinballXSystem system, string fileName, bool isChecked) { if (isChecked) { _databaseFileFilter[system].Remove(fileName); } else { _databaseFileFilter[system].Add(fileName); } RefreshGameVisibility(); }
public void OnExecutableFilterChanged(PinballXSystem system, string fileName, bool isChecked) { fileName = fileName == PinballXSystem.DefaultExecutableLabel ? "" : fileName; if (isChecked) { _executableFilter[system].Remove(fileName); } else { _executableFilter[system].Add(fileName); } RefreshGameVisibility(); }
/// <summary> /// Returns the absolute path of where the file should moved to after /// downloading. /// </summary> /// <param name="system">System of the table file belonging to the download</param> /// <returns>Absolute path to local download location</returns> public string GetFileDestination([NotNull] PinballXSystem system) { switch (FileType) { case FileType.TableFile: return(Path.Combine(system.TablePath, File.Name)); case FileType.TableScript: var scriptsFolder = Path.Combine(Path.GetDirectoryName(system.TablePath), "Scripts"); return(Path.Combine(Directory.Exists(scriptsFolder) ? scriptsFolder : system.TablePath, File.Name)); case FileType.TableAuxiliary: return(Path.Combine(system.TablePath, File.Name)); case FileType.TableMusic: var musicFolder = Path.Combine(Path.GetDirectoryName(system.TablePath), "Music"); if (!Directory.Exists(musicFolder)) { Directory.CreateDirectory(musicFolder); } return(Path.Combine(musicFolder, File.Name)); case FileType.TableImage: // todo handle desktop media (goes to "Table Images Desktop" folder) return(Path.Combine(system.MediaPath, MediaTableImages, Release.Game.DisplayName + Path.GetExtension(FilePath))); case FileType.TableVideo: // todo handle desktop media (goes to "Table Videos Desktop" folder) return(Path.Combine(system.MediaPath, MediaTableVideos, Release.Game.DisplayName + Path.GetExtension(FilePath))); case FileType.BackglassImage: return(Path.Combine(system.MediaPath, MediaBackglassImages, Release.Game.DisplayName + Path.GetExtension(FilePath))); case FileType.WheelImage: return(Path.Combine(system.MediaPath, MediaWheelImages, Release.Game.DisplayName + Path.GetExtension(FilePath))); case FileType.Rom: // todo default: throw new ArgumentOutOfRangeException(); } }
/// <summary> /// Merges a list of mappings of a given system into Global Games. /// </summary> /// /// <remarks> /// The provided mappings are exhaustive, i.e. exiting games not in the /// provided list for the given system are to be removed. /// </remarks> /// /// <param name="system">System of updated mappings</param> /// <param name="mappings">Mappings</param> private void MergeMappings(PinballXSystem system, IEnumerable <Mapping> mappings) { lock (AggregatedGames) { // if Global Games are empty, don't bother identifiying and add them all if (AggregatedGames.IsEmpty) { _threadManager.MainDispatcher.Invoke(() => { using (AggregatedGames.SuppressChangeNotifications()) { AggregatedGames.AddRange(mappings.Select(m => new AggregatedGame(m))); } }); _logger.Info("Added {0} games from mappings.", mappings.Count()); return; } // otherwise, retrieve the system's games from Global Games. var selectedGames = AggregatedGames.Where(g => g.HasSystem && ReferenceEquals(g.System, system) ).ToList(); var remainingGames = new HashSet <AggregatedGame>(selectedGames); var gamesToAdd = new List <AggregatedGame>(); var updated = 0; var removed = 0; var cleared = 0; // update games foreach (var mapping in mappings) { // todo could also use an index var game = AggregatedGames.FirstOrDefault(g => g.FileId == mapping.Id); // no match, add if (game == null) { gamesToAdd.Add(new AggregatedGame(mapping)); // match and not equal, so update. } else if (!game.EqualsMapping(mapping)) { _threadManager.MainDispatcher.Invoke(() => game.Update(mapping)); remainingGames.Remove(game); updated++; // match but equal, ignore. } else { remainingGames.Remove(game); } } // add games if (gamesToAdd.Count > 0) { _threadManager.MainDispatcher.Invoke(() => AggregatedGames.AddRange(gamesToAdd)); } // remove or clear games _threadManager.MainDispatcher.Invoke(() => { foreach (var game in remainingGames) { if (!game.HasLocalFile && !game.HasXmlGame) { AggregatedGames.Remove(game); removed++; } else { cleared++; game.ClearMapping(); } } }); // done! _logger.Info("Added {0} games, removed {1} ({2}), updated {3} from mappings.", gamesToAdd.Count, removed, cleared, updated); } }
/// <summary> /// Merges changed games from PinballX into Global Games. /// </summary> /// /// <remarks> /// The provided games list is exhaustive, meaning existing games of /// the given system and database file are to be removed. Provide an /// empty list to remove all of them. /// /// This is triggered on <see cref="PinballXSystem.GamesUpdated"/>. The /// received list is then merged into <see cref="AggregatedGames"/>, /// while only data that has changed is updated. /// </remarks> /// /// <param name="system">System of the updated games</param> /// <param name="databaseFile">Database file of the updated games</param> /// <param name="xmlGames">Parsed games</param> public void MergeXmlGames(PinballXSystem system, string databaseFile, List <PinballXGame> xmlGames) { lock (AggregatedGames) { // if Global Games are empty, don't bother identifiying and add them all if (AggregatedGames.IsEmpty) { _threadManager.MainDispatcher.Invoke(() => { using (AggregatedGames.SuppressChangeNotifications()) { AggregatedGames.AddRange(xmlGames.Select(g => new AggregatedGame(g))); } }); _logger.Info("Added {0} games from PinballX database.", xmlGames.Count()); return; } // otherwise, retrieve the system's games from Global Games. var selectedGames = AggregatedGames.Where(g => g.XmlGame != null && ReferenceEquals(g.XmlGame.System, system) && g.XmlGame.DatabaseFile == databaseFile ).ToList(); var remainingGames = new HashSet <AggregatedGame>(selectedGames); var gamesToAdd = new List <AggregatedGame>(); var updated = 0; var removed = 0; var cleared = 0; // update games foreach (var newGame in xmlGames) { // todo could also use an index var fileId = Path.Combine(newGame.System.TablePath, Path.GetFileName(newGame.FileName)); var oldGame = AggregatedGames.FirstOrDefault(g => g.FileId == fileId); // no match, add if (oldGame == null) { gamesToAdd.Add(new AggregatedGame(newGame)); // match and not equal, so update. } else if (!oldGame.EqualsXmlGame(newGame)) { _threadManager.MainDispatcher.Invoke(() => oldGame.Update(newGame)); remainingGames.Remove(oldGame); updated++; // match but equal, ignore. } else { remainingGames.Remove(oldGame); } } // add games if (gamesToAdd.Count > 0) { _threadManager.MainDispatcher.Invoke(() => AggregatedGames.AddRange(gamesToAdd)); } // remove or clear games _threadManager.MainDispatcher.Invoke(() => { foreach (var game in remainingGames) { if (!game.HasLocalFile && !game.HasMapping) { AggregatedGames.Remove(game); removed++; } else { cleared++; game.ClearXmlGame(); } } }); // done! _logger.Info("Added {0} games, removed {1} ({2}), updated {3} from PinballX database.", gamesToAdd.Count, removed, cleared, updated); } }
/// <summary> /// Constructor when linking game to VPDB /// </summary> /// <param name="game">Game to link to</param> /// <param name="system">System to link to</param> /// <param name="release">VPDB release</param> /// <param name="fileId">File ID of VPDB release</param> public Mapping(AggregatedGame game, PinballXSystem system, VpdbRelease release, string fileId) : this(game, system) { ReleaseId = release.Id; FileId = fileId; }
/// <summary> /// Constructor with given game /// </summary> /// <param name="game">Game to which the mapping is linked to</param> /// <param name="system">System to which the game is linked to</param> public Mapping(AggregatedGame game, PinballXSystem system) { System = system; FileName = Path.GetFileName(game.FilePath); }