/// <summary> /// Add a new entry to the information table. /// </summary> /// <param name="program">The program to add to the table.</param> /// <returns><c>true</c> if the program was added. If it was not, then a match based on CRC was found.</returns> public bool AddEntry(UserSpecifiedProgramInformation program) { // Check the incoming program's CRCs against the CRCs of existing entries. If any CRC in the incoming program matches a CRC of an entry, then it's a match! // TODO: Move to also include CfgCrc where appropriate. Ideally we would canonicalize CFG file content in memory and then check. var existingEntry = _programs.FirstOrDefault(e => e.Crcs.FirstOrDefault(c => program.Crcs.FirstOrDefault(crc => crc.Crc == c.Crc) != null) != null); bool addedEntry = existingEntry == null; if (addedEntry) { _programs.Add(program); } return(addedEntry); }
/// <summary> /// Initializes a new instance of ProgramDescription. /// </summary> /// <param name="crc">The CRC of the program.</param> /// <param name="rom">The ROM file or files.</param> /// <param name="programInfo">Additional information about the program.</param> public ProgramDescription(uint crc, IRom rom, IProgramInformation programInfo) { _crc = crc; _rom = rom; _programInfo = new UserSpecifiedProgramInformation(programInfo); _xmlVendor = new MetadataString() { Text = _programInfo.Vendor }; _name = _programInfo.GetNameForCrc(crc); _xmlName = new MetadataString() { Text = _name }; _shortName = programInfo.ShortName; _xmlShortName = new MetadataString() { Text = _shortName }; _programFiles = new ProgramSupportFiles(rom); var allIncompatibilities = _programInfo.Crcs.Select(c => c.Incompatibilities); var crcData = _programInfo.Crcs.First(c => c.Crc == crc); var romSpecificIncompatibilities = crcData.Incompatibilities; if ((romSpecificIncompatibilities != IncompatibilityFlags.None) || (allIncompatibilities.Distinct().Count() > 1)) { var incompatibilities = _programInfo.Features.ToIncompatibilityFlags(); if (romSpecificIncompatibilities != incompatibilities) { _features = romSpecificIncompatibilities.ApplyFlagsToProgramFeatures(_programInfo.Features.Clone()); } else { _features = programInfo.Features; } } else { _features = programInfo.Features; } }
/// <summary> /// Group an entry with an existing one in the table. /// </summary> /// <param name="program">The program to be combined with another in the table.</param> /// <param name="groupWithEntry">The entry to group with.</param> /// <returns><c>true</c> if the new entry was merged into the existing one.</returns> public bool GroupWithExistingEntry(UserSpecifiedProgramInformation program, UserSpecifiedProgramInformation groupWithEntry) { // TODO: Improvement should be done regarding CFG CRCs. var newCrcs = Enumerable.Empty <CrcData>(); if (_programs.Contains(groupWithEntry)) { var groupEntryCrcs = groupWithEntry.Crcs.Select(c => c.Crc).ToList(); newCrcs = program.Crcs.Where(c => !groupEntryCrcs.Contains(c.Crc)); } var groupedWithEntry = newCrcs.Any(); if (groupedWithEntry) { foreach (var crc in newCrcs) { groupWithEntry.AddCrc(crc.Crc, crc.Description, crc.Incompatibilities); } } return(groupedWithEntry); }
/// <summary> /// Merges the given IProgramInformation data to form a new, combined version of the information. /// </summary> /// <param name="programInformation">The "primary" source of information.</param> /// <param name="fieldsToMerge">Identifies which fields to merge.</param> /// <param name="otherSources">The other information sources to merge, and how to merge them. See Remarks.</param> /// <returns>The merged program information.</returns> /// <remarks>Each of the additional information sources in the merge will be combined with the primary. The newly /// merged information is initialized using the data from <paramref name="programInformation"/>. The /// other sources should be ordered such that the first entry is the "most important" and the final entry the /// "least important". Each entry also describes which fields it is allowed to override in the merge. /// This is to offer a means of conflict resolution in situations arising from multiple sources containing /// the same information and attempting to override the default. For example, consider the case in which /// <paramref name="programInformation"/> and two entries are provided via <paramref name="otherSources"/>, /// all three of which define a value for <see cref="IProgramInformation.Title"/>. Here are some ways in which /// this can be configured: /// <code> /// fieldsToMerge = ProgramInformationMergeFieldsFlags.None; /// </code> /// In this case, the information in <paramref name="programInformation"/> will not be changed. A copy of the /// data within it will be returned. Note, however, that the specific implementation of <see cref="IProgramInformation"/> /// used to deliver the resulting data may be different than the original! /// Now, consider this scenario: /// <code> /// fieldsToMerge = ProgramInformationMergeFieldsFlags.Title; /// otherSources[0].Item2 = ProgramInformationMergeFieldsFlags.Title; /// otherSources[1].Item2 = ProgramInformationMergeFieldsFlags.Title; /// </code> /// In this case, each entry allows 'Title' to be set. Because <paramref name="programInformation"/> is /// treated as the highest priority, its value will be retained unless it is not set (in this case, a <c>null</c> /// or empty string). So, if programInformation.Title has no value, but otherSources[0].Item1.Title does, then /// the final result contains otherSources[0].Item1.Title, regardless of the value of otherSources[1].Item1.Title. /// By carefully considering these flags, you can have a reasonably rich order-of-precedence defined for establishing /// the data in the merged result. /// </remarks> /// <exception cref="System.ArgumentException">Thrown if unrecognized flags are provided in <paramref name="fieldsToMerge"/> or via <paramref name="otherSources"/>.</exception> public static IProgramInformation Merge(this IProgramInformation programInformation, ProgramInformationMergeFieldsFlags fieldsToMerge, params Tuple <IProgramInformation, ProgramInformationMergeFieldsFlags>[] otherSources) { // Grr.... PCLs (at least in .NET 4.0) don't support Enum.GetValues() var flagsToProcess = new[] { ProgramInformationMergeFieldsFlags.Title, ProgramInformationMergeFieldsFlags.Vendor, ProgramInformationMergeFieldsFlags.Year, ProgramInformationMergeFieldsFlags.Features, ProgramInformationMergeFieldsFlags.ShortName, ProgramInformationMergeFieldsFlags.Crcs }; var unknownFlagsForMerge = otherSources.Select(s => s.Item2).Concat(new[] { fieldsToMerge }).Except(flagsToProcess).Except(new[] { ProgramInformationMergeFieldsFlags.None, ProgramInformationMergeFieldsFlags.All }); if (unknownFlagsForMerge.Any()) { var unknownFlags = unknownFlagsForMerge.Aggregate((all, flag) => all | flag); throw new ArgumentException(string.Format(Resources.Strings.ProgramInformation_InvalidFieldFormat, unknownFlags)); } var mergedProgramInformation = new UserSpecifiedProgramInformation(programInformation); var mergedPriorityFlags = fieldsToMerge; // accumulates if a "higher priority" info has already claimed a field foreach (var otherSource in otherSources) { var otherInformation = otherSource.Item1; var updateFlags = otherSource.Item2; foreach (var flag in flagsToProcess) { if (updateFlags.HasFlag(flag)) { switch (flag) { case ProgramInformationMergeFieldsFlags.Title: if (string.IsNullOrEmpty(mergedProgramInformation.Title) || !mergedPriorityFlags.HasFlag(flag)) { mergedProgramInformation.Title = otherInformation.Title; if (!string.IsNullOrEmpty(mergedProgramInformation.Title)) { mergedPriorityFlags |= flag; } } break; case ProgramInformationMergeFieldsFlags.Vendor: if (string.IsNullOrEmpty(mergedProgramInformation.Vendor) || !mergedPriorityFlags.HasFlag(flag)) { mergedProgramInformation.Vendor = otherInformation.Vendor; if (!string.IsNullOrEmpty(mergedProgramInformation.Vendor)) { mergedPriorityFlags |= flag; } } break; case ProgramInformationMergeFieldsFlags.Year: if (string.IsNullOrEmpty(mergedProgramInformation.Year) || !mergedPriorityFlags.HasFlag(flag)) { mergedProgramInformation.Year = otherInformation.Year; if (!string.IsNullOrEmpty(mergedProgramInformation.Year)) { mergedPriorityFlags |= flag; } } break; case ProgramInformationMergeFieldsFlags.Features: // This runs the risk of combining conflicting flags... mergedProgramInformation.Features = ProgramFeatures.Combine(mergedProgramInformation.Features, otherInformation.Features); mergedPriorityFlags |= flag; break; case ProgramInformationMergeFieldsFlags.ShortName: if (string.IsNullOrEmpty(mergedProgramInformation.ShortName) || !mergedPriorityFlags.HasFlag(flag)) { mergedProgramInformation.ShortName = otherInformation.ShortName; if (!string.IsNullOrEmpty(mergedProgramInformation.ShortName)) { mergedPriorityFlags |= flag; } } break; case ProgramInformationMergeFieldsFlags.Crcs: var crcsToMerge = otherInformation.Crcs.Where(o => !mergedProgramInformation.Crcs.Any(m => m.Crc == o.Crc)); foreach (var crc in crcsToMerge) { mergedProgramInformation.AddCrc(crc.Crc, crc.Description, crc.Incompatibilities); mergedPriorityFlags |= flag; } break; } } } } return(mergedProgramInformation); }