diff --git a/SabreTools.DatFiles/Formats/Missfile.cs b/SabreTools.DatFiles/Formats/Missfile.cs index 73df03d2..28608c58 100644 --- a/SabreTools.DatFiles/Formats/Missfile.cs +++ b/SabreTools.DatFiles/Formats/Missfile.cs @@ -64,7 +64,7 @@ namespace SabreTools.DatFiles.Formats continue; // Resolve the names in the block - datItems = DatItem.ResolveNames(datItems); + datItems = DatItemTool.ResolveNames(datItems); for (int index = 0; index < datItems.Count; index++) { @@ -124,7 +124,7 @@ namespace SabreTools.DatFiles.Formats continue; // Resolve the names in the block - var items = DatItem.ResolveNamesDB([.. itemsDict]); + var items = DatItemTool.ResolveNamesDB([.. itemsDict]); foreach (var kvp in items) { diff --git a/SabreTools.DatFiles/Formats/SabreJSON.cs b/SabreTools.DatFiles/Formats/SabreJSON.cs index 2944fe85..9288bde1 100644 --- a/SabreTools.DatFiles/Formats/SabreJSON.cs +++ b/SabreTools.DatFiles/Formats/SabreJSON.cs @@ -399,7 +399,7 @@ namespace SabreTools.DatFiles.Formats continue; // Resolve the names in the block - datItems = DatItem.ResolveNames(datItems); + datItems = DatItemTool.ResolveNames(datItems); for (int index = 0; index < datItems.Count; index++) { @@ -479,7 +479,7 @@ namespace SabreTools.DatFiles.Formats continue; // Resolve the names in the block - var items = DatItem.ResolveNamesDB([.. itemsDict]); + var items = DatItemTool.ResolveNamesDB([.. itemsDict]); foreach (var kvp in items) { diff --git a/SabreTools.DatFiles/Formats/SabreXML.cs b/SabreTools.DatFiles/Formats/SabreXML.cs index 623e95e7..63bda68d 100644 --- a/SabreTools.DatFiles/Formats/SabreXML.cs +++ b/SabreTools.DatFiles/Formats/SabreXML.cs @@ -227,7 +227,7 @@ namespace SabreTools.DatFiles.Formats continue; // Resolve the names in the block - datItems = DatItem.ResolveNames(datItems); + datItems = DatItemTool.ResolveNames(datItems); for (int index = 0; index < datItems.Count; index++) { @@ -308,7 +308,7 @@ namespace SabreTools.DatFiles.Formats continue; // Resolve the names in the block - var items = DatItem.ResolveNamesDB([.. itemsDict]); + var items = DatItemTool.ResolveNamesDB([.. itemsDict]); foreach (var kvp in items) { diff --git a/SabreTools.DatFiles/ItemDictionary.cs b/SabreTools.DatFiles/ItemDictionary.cs index e20524c4..8b5831f7 100644 --- a/SabreTools.DatFiles/ItemDictionary.cs +++ b/SabreTools.DatFiles/ItemDictionary.cs @@ -759,11 +759,11 @@ namespace SabreTools.DatFiles #endif // Sort the list of items to be consistent - DatItem.Sort(ref sortedlist, false); + DatItemTool.Sort(ref sortedlist, false); // If we're merging the roms, do so if (dedupeType == DedupeType.Full || (dedupeType == DedupeType.Game && bucketBy == ItemKey.Machine)) - sortedlist = DatItem.Merge(sortedlist); + sortedlist = DatItemTool.Merge(sortedlist); // Add the list back to the dictionary Reset(key); @@ -794,7 +794,7 @@ namespace SabreTools.DatFiles // Sort the list of items to be consistent if (sortedlist != null) - DatItem.Sort(ref sortedlist, false); + DatItemTool.Sort(ref sortedlist, false); #if NET40_OR_GREATER || NETCOREAPP }); #else diff --git a/SabreTools.DatItems/DatItem.cs b/SabreTools.DatItems/DatItem.cs index 4b4aedb3..d1dee63d 100644 --- a/SabreTools.DatItems/DatItem.cs +++ b/SabreTools.DatItems/DatItem.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Reflection; using System.Xml.Serialization; using Newtonsoft.Json; @@ -9,10 +6,8 @@ using SabreTools.Core; using SabreTools.Core.Filter; using SabreTools.Core.Tools; using SabreTools.DatItems.Formats; -using SabreTools.FileTypes; using SabreTools.Hashing; using SabreTools.IO.Logging; -using SabreTools.Matching.Compare; namespace SabreTools.DatItems { @@ -104,8 +99,6 @@ namespace SabreTools.DatItems #endregion - #region Instance Methods - #region Accessors /// @@ -122,44 +115,6 @@ namespace SabreTools.DatItems #endregion - #region Constructors - - /// - /// Create a specific type of DatItem to be used based on a BaseFile - /// - /// BaseFile containing information to be created - /// TreatAsFile representing special format scanning - /// DatItem of the specific internal type that corresponds to the inputs - public static DatItem? Create(BaseFile? baseFile, TreatAsFile asFile = 0x00) - { - return baseFile switch - { - // Disk -#if NET20 || NET35 - FileTypes.CHD.CHDFile when (asFile & TreatAsFile.CHD) == 0 => new Disk(baseFile), -#else - FileTypes.CHD.CHDFile when !asFile.HasFlag(TreatAsFile.CHD) => new Disk(baseFile), -#endif - - // Media -#if NET20 || NET35 - FileTypes.Aaru.AaruFormat when (asFile & TreatAsFile.AaruFormat) == 0 => new Media(baseFile), -#else - FileTypes.Aaru.AaruFormat when !asFile.HasFlag(TreatAsFile.AaruFormat) => new Media(baseFile), -#endif - - // Rom - BaseArchive => new Rom(baseFile), - Folder => null, // Folders cannot be a DatItem - BaseFile => new Rom(baseFile), - - // Miscellaneous - _ => null, - }; - } - - #endregion - #region Cloning Methods /// @@ -510,442 +465,6 @@ namespace SabreTools.DatItems } #endregion - - #endregion // Instance Methods - - #region Static Methods - - #region Sorting and Merging - - /// - /// Merge an arbitrary set of ROMs based on the supplied information - /// - /// List of File objects representing the roms to be merged - /// A List of DatItem objects representing the merged roms - public static List Merge(List? infiles) - { - // Check for null or blank roms first - if (infiles == null || infiles.Count == 0) - return []; - - // Create output list - List outfiles = []; - - // Then deduplicate them by checking to see if data matches previous saved roms - int nodumpCount = 0; - for (int f = 0; f < infiles.Count; f++) - { - DatItem item = infiles[f]; - - // If we somehow have a null item, skip - if (item == null) - continue; - - // If we don't have a Disk, File, Media, or Rom, we skip checking for duplicates - if (item is not Disk && item is not Formats.File && item is not Media && item is not Rom) - continue; - - // If it's a nodump, add and skip - if (item is Rom rom && rom.GetStringFieldValue(Models.Metadata.Rom.StatusKey).AsEnumValue() == ItemStatus.Nodump) - { - outfiles.Add(item); - nodumpCount++; - continue; - } - else if (item is Disk disk && disk.GetStringFieldValue(Models.Metadata.Disk.StatusKey).AsEnumValue() == ItemStatus.Nodump) - { - outfiles.Add(item); - nodumpCount++; - continue; - } - // If it's the first non-nodump rom in the list, don't touch it - else if (outfiles.Count == 0 || outfiles.Count == nodumpCount) - { - outfiles.Add(item); - continue; - } - - // Check if the rom is a duplicate - DupeType dupetype = 0x00; - DatItem saveditem = new Blank(); - int pos = -1; - for (int i = 0; i < outfiles.Count; i++) - { - DatItem lastrom = outfiles[i]; - - // Get the duplicate status - dupetype = item.GetDuplicateStatus(lastrom); - - // If it's a duplicate, skip adding it to the output but add any missing information - if (dupetype != 0x00) - { - saveditem = lastrom; - pos = i; - - // Disks, Media, and Roms have more information to fill - if (item is Disk disk && saveditem is Disk savedDisk) - savedDisk.FillMissingInformation(disk); - else if (item is Formats.File fileItem && saveditem is Formats.File savedFile) - savedFile.FillMissingInformation(fileItem); - else if (item is Media media && saveditem is Media savedMedia) - savedMedia.FillMissingInformation(media); - else if (item is Rom romItem && saveditem is Rom savedRom) - savedRom.FillMissingInformation(romItem); - - saveditem.SetFieldValue(DatItem.DupeTypeKey, dupetype); - - // If the current system has a lower ID than the previous, set the system accordingly - if (item.GetFieldValue(DatItem.SourceKey)?.Index < saveditem.GetFieldValue(DatItem.SourceKey)?.Index) - { - item.SetFieldValue(DatItem.SourceKey, item.GetFieldValue(DatItem.SourceKey)!.Clone() as Source); - saveditem.CopyMachineInformation(item); - saveditem.SetName(item.GetName()); - } - - // If the current machine is a child of the new machine, use the new machine instead - if (saveditem.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey) == item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey) - || saveditem.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.RomOfKey) == item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey)) - { - saveditem.CopyMachineInformation(item); - saveditem.SetName(item.GetName()); - } - - break; - } - } - - // If no duplicate is found, add it to the list - if (dupetype == 0x00) - { - outfiles.Add(item); - } - // Otherwise, if a new rom information is found, add that - else - { - outfiles.RemoveAt(pos); - outfiles.Insert(pos, saveditem); - } - } - - // Then return the result - return outfiles; - } - - /// - /// Resolve name duplicates in an arbitrary set of ROMs based on the supplied information - /// - /// List of File objects representing the roms to be merged - /// A List of DatItem objects representing the renamed roms - public static List ResolveNames(List infiles) - { - // Create the output list - List output = []; - - // First we want to make sure the list is in alphabetical order - Sort(ref infiles, true); - - // Now we want to loop through and check names - DatItem? lastItem = null; - string? lastrenamed = null; - int lastid = 0; - for (int i = 0; i < infiles.Count; i++) - { - DatItem datItem = infiles[i]; - - // If we have the first item, we automatically add it - if (lastItem == null) - { - output.Add(datItem); - lastItem = datItem; - continue; - } - - // Get the last item name, if applicable - string lastItemName = lastItem.GetName() - ?? lastItem.GetStringFieldValue(Models.Metadata.DatItem.TypeKey).AsEnumValue().AsStringValue() - ?? string.Empty; - - // Get the current item name, if applicable - string datItemName = datItem.GetName() - ?? datItem.GetStringFieldValue(Models.Metadata.DatItem.TypeKey).AsEnumValue().AsStringValue() - ?? string.Empty; - - // If the current item exactly matches the last item, then we don't add it -#if NET20 || NET35 - if ((datItem.GetDuplicateStatus(lastItem) & DupeType.All) != 0) -#else - if (datItem.GetDuplicateStatus(lastItem).HasFlag(DupeType.All)) -#endif - { - staticLogger.Verbose($"Exact duplicate found for '{datItemName}'"); - continue; - } - - // If the current name matches the previous name, rename the current item - else if (datItemName == lastItemName) - { - staticLogger.Verbose($"Name duplicate found for '{datItemName}'"); - - if (datItem is Disk || datItem is Formats.File || datItem is Media || datItem is Rom) - { - datItemName += GetDuplicateSuffix(datItem); - lastrenamed ??= datItemName; - } - - // If we have a conflict with the last renamed item, do the right thing - if (datItemName == lastrenamed) - { - lastrenamed = datItemName; - datItemName += (lastid == 0 ? string.Empty : "_" + lastid); - lastid++; - } - // If we have no conflict, then we want to reset the lastrenamed and id - else - { - lastrenamed = null; - lastid = 0; - } - - // Set the item name back to the datItem - datItem.SetName(datItemName); - - output.Add(datItem); - } - - // Otherwise, we say that we have a valid named file - else - { - output.Add(datItem); - lastItem = datItem; - lastrenamed = null; - lastid = 0; - } - } - - // One last sort to make sure this is ordered - Sort(ref output, true); - - return output; - } - - /// - /// Resolve name duplicates in an arbitrary set of ROMs based on the supplied information - /// - /// List of File objects representing the roms to be merged - /// A List of DatItem objects representing the renamed roms - public static List> ResolveNamesDB(List> infiles) - { - // Create the output dict - List> output = []; - - // First we want to make sure the list is in alphabetical order - Sort(ref infiles, true); - - // Now we want to loop through and check names - DatItem? lastItem = null; - string? lastrenamed = null; - int lastid = 0; - foreach (var datItem in infiles) - { - // If we have the first item, we automatically add it - if (lastItem == null) - { - output.Add(datItem); - lastItem = datItem.Value; - continue; - } - - // Get the last item name, if applicable - string lastItemName = lastItem.GetName() - ?? lastItem.GetStringFieldValue(Models.Metadata.DatItem.TypeKey).AsEnumValue().AsStringValue() - ?? string.Empty; - - // Get the current item name, if applicable - string datItemName = datItem.Value.GetName() - ?? datItem.Value.GetStringFieldValue(Models.Metadata.DatItem.TypeKey).AsEnumValue().AsStringValue() - ?? string.Empty; - - // If the current item exactly matches the last item, then we don't add it -#if NET20 || NET35 - if ((datItem.Value.GetDuplicateStatus(lastItem) & DupeType.All) != 0) -#else - if (datItem.Value.GetDuplicateStatus(lastItem).HasFlag(DupeType.All)) -#endif - { - staticLogger.Verbose($"Exact duplicate found for '{datItemName}'"); - continue; - } - - // If the current name matches the previous name, rename the current item - else if (datItemName == lastItemName) - { - staticLogger.Verbose($"Name duplicate found for '{datItemName}'"); - - if (datItem.Value is Disk || datItem.Value is Formats.File || datItem.Value is Media || datItem.Value is Rom) - { - datItemName += GetDuplicateSuffix(datItem.Value); - lastrenamed ??= datItemName; - } - - // If we have a conflict with the last renamed item, do the right thing - if (datItemName == lastrenamed) - { - lastrenamed = datItemName; - datItemName += (lastid == 0 ? string.Empty : "_" + lastid); - lastid++; - } - // If we have no conflict, then we want to reset the lastrenamed and id - else - { - lastrenamed = null; - lastid = 0; - } - - // Set the item name back to the datItem - datItem.Value.SetName(datItemName); - output.Add(datItem); - } - - // Otherwise, we say that we have a valid named file - else - { - output.Add(datItem); - lastItem = datItem.Value; - lastrenamed = null; - lastid = 0; - } - } - - // One last sort to make sure this is ordered - Sort(ref output, true); - - return output; - } - - /// - /// Get duplicate suffix based on the item type - /// - private static string GetDuplicateSuffix(DatItem datItem) - { - return datItem switch - { - Disk disk => disk.GetDuplicateSuffix(), - Formats.File file => file.GetDuplicateSuffix(), - Media media => media.GetDuplicateSuffix(), - Rom rom => rom.GetDuplicateSuffix(), - _ => "_1", - }; - } - - /// - /// Sort a list of File objects by SourceID, Game, and Name (in order) - /// - /// List of File objects representing the roms to be sorted - /// True if files are not renamed, false otherwise - /// True if it sorted correctly, false otherwise - public static bool Sort(ref List roms, bool norename) - { - roms.Sort(delegate (DatItem x, DatItem y) - { - try - { - var nc = new NaturalComparer(); - - // If machine names don't match - string? xMachineName = x.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey); - string? yMachineName = y.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey); - if (xMachineName != yMachineName) - return nc.Compare(xMachineName, yMachineName); - - // If types don't match - string? xType = x.GetStringFieldValue(Models.Metadata.DatItem.TypeKey); - string? yType = y.GetStringFieldValue(Models.Metadata.DatItem.TypeKey); - if (xType != yType) - return xType.AsEnumValue() - yType.AsEnumValue(); - - // If directory names don't match - string? xDirectoryName = Path.GetDirectoryName(TextHelper.RemovePathUnsafeCharacters(x.GetName() ?? string.Empty)); - string? yDirectoryName = Path.GetDirectoryName(TextHelper.RemovePathUnsafeCharacters(y.GetName() ?? string.Empty)); - if (xDirectoryName != yDirectoryName) - return nc.Compare(xDirectoryName, yDirectoryName); - - // If item names don't match - string? xName = Path.GetFileName(TextHelper.RemovePathUnsafeCharacters(x.GetName() ?? string.Empty)); - string? yName = Path.GetFileName(TextHelper.RemovePathUnsafeCharacters(y.GetName() ?? string.Empty)); - if (xName != yName) - return nc.Compare(xName, yName); - - // Otherwise, compare on machine or source, depending on the flag - int? xSourceIndex = x.GetFieldValue(DatItem.SourceKey)?.Index; - int? ySourceIndex = y.GetFieldValue(DatItem.SourceKey)?.Index; - return (norename ? nc.Compare(xMachineName, yMachineName) : (xSourceIndex - ySourceIndex) ?? 0); - } - catch - { - // Absorb the error - return 0; - } - }); - - return true; - } - - /// - /// Sort a list of File objects by SourceID, Game, and Name (in order) - /// - /// List of File objects representing the roms to be sorted - /// True if files are not renamed, false otherwise - /// True if it sorted correctly, false otherwise - public static bool Sort(ref List> roms, bool norename) - { - roms.Sort(delegate (KeyValuePair x, KeyValuePair y) - { - try - { - var nc = new NaturalComparer(); - - // If machine names don't match - string? xMachineName = x.Value.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey); - string? yMachineName = y.Value.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey); - if (xMachineName != yMachineName) - return nc.Compare(xMachineName, yMachineName); - - // If types don't match - string? xType = x.Value.GetStringFieldValue(Models.Metadata.DatItem.TypeKey); - string? yType = y.Value.GetStringFieldValue(Models.Metadata.DatItem.TypeKey); - if (xType != yType) - return xType.AsEnumValue() - yType.AsEnumValue(); - - // If directory names don't match - string? xDirectoryName = Path.GetDirectoryName(TextHelper.RemovePathUnsafeCharacters(x.Value.GetName() ?? string.Empty)); - string? yDirectoryName = Path.GetDirectoryName(TextHelper.RemovePathUnsafeCharacters(y.Value.GetName() ?? string.Empty)); - if (xDirectoryName != yDirectoryName) - return nc.Compare(xDirectoryName, yDirectoryName); - - // If item names don't match - string? xName = Path.GetFileName(TextHelper.RemovePathUnsafeCharacters(x.Value.GetName() ?? string.Empty)); - string? yName = Path.GetFileName(TextHelper.RemovePathUnsafeCharacters(y.Value.GetName() ?? string.Empty)); - if (xName != yName) - return nc.Compare(xName, yName); - - // Otherwise, compare on machine or source, depending on the flag - int? xSourceIndex = x.Value.GetFieldValue(DatItem.SourceKey)?.Index; - int? ySourceIndex = y.Value.GetFieldValue(DatItem.SourceKey)?.Index; - return (norename ? nc.Compare(xMachineName, yMachineName) : (xSourceIndex - ySourceIndex) ?? 0); - } - catch - { - // Absorb the error - return 0; - } - }); - - return true; - } - - #endregion - - #endregion // Static Methods } /// diff --git a/SabreTools.DatItems/DatItemTool.cs b/SabreTools.DatItems/DatItemTool.cs new file mode 100644 index 00000000..c12be568 --- /dev/null +++ b/SabreTools.DatItems/DatItemTool.cs @@ -0,0 +1,490 @@ +using System.Collections.Generic; +using System.IO; +using SabreTools.Core.Tools; +using SabreTools.DatItems.Formats; +using SabreTools.FileTypes; +using SabreTools.IO.Logging; +using SabreTools.Matching.Compare; + +namespace SabreTools.DatItems +{ + public static class DatItemTool + { + #region Logging + + /// + /// Static logger for static methods + /// + private static readonly Logger staticLogger = new(); + + #endregion + + #region Creation + + /// + /// Create a specific type of DatItem to be used based on a BaseFile + /// + /// BaseFile containing information to be created + /// TreatAsFile representing special format scanning + /// DatItem of the specific internal type that corresponds to the inputs + public static DatItem? CreateDatItem(BaseFile? baseFile, TreatAsFile asFile = 0x00) + { + return baseFile switch + { + // Disk +#if NET20 || NET35 + FileTypes.CHD.CHDFile when (asFile & TreatAsFile.CHD) == 0 => new Disk(baseFile), +#else + FileTypes.CHD.CHDFile when !asFile.HasFlag(TreatAsFile.CHD) => new Disk(baseFile), +#endif + + // Media +#if NET20 || NET35 + FileTypes.Aaru.AaruFormat when (asFile & TreatAsFile.AaruFormat) == 0 => new Media(baseFile), +#else + FileTypes.Aaru.AaruFormat when !asFile.HasFlag(TreatAsFile.AaruFormat) => new Media(baseFile), +#endif + + // Rom + BaseArchive => new Rom(baseFile), + Folder => null, // Folders cannot be a DatItem + BaseFile => new Rom(baseFile), + + // Miscellaneous + _ => null, + }; + } + + #endregion + + #region Sorting and Merging + + /// + /// Merge an arbitrary set of DatItems based on the supplied information + /// + /// List of File objects representing the roms to be merged + /// A List of DatItem objects representing the merged roms + public static List Merge(List? infiles) + { + // Check for null or blank roms first + if (infiles == null || infiles.Count == 0) + return []; + + // Create output list + List outfiles = []; + + // Then deduplicate them by checking to see if data matches previous saved roms + int nodumpCount = 0; + for (int f = 0; f < infiles.Count; f++) + { + DatItem item = infiles[f]; + + // If we somehow have a null item, skip + if (item == null) + continue; + + // If we don't have a Disk, File, Media, or Rom, we skip checking for duplicates + if (item is not Disk && item is not Formats.File && item is not Media && item is not Rom) + continue; + + // If it's a nodump, add and skip + if (item is Rom rom && rom.GetStringFieldValue(Models.Metadata.Rom.StatusKey).AsEnumValue() == ItemStatus.Nodump) + { + outfiles.Add(item); + nodumpCount++; + continue; + } + else if (item is Disk disk && disk.GetStringFieldValue(Models.Metadata.Disk.StatusKey).AsEnumValue() == ItemStatus.Nodump) + { + outfiles.Add(item); + nodumpCount++; + continue; + } + // If it's the first non-nodump rom in the list, don't touch it + else if (outfiles.Count == 0 || outfiles.Count == nodumpCount) + { + outfiles.Add(item); + continue; + } + + // Check if the rom is a duplicate + DupeType dupetype = 0x00; + DatItem saveditem = new Blank(); + int pos = -1; + for (int i = 0; i < outfiles.Count; i++) + { + DatItem lastrom = outfiles[i]; + + // Get the duplicate status + dupetype = item.GetDuplicateStatus(lastrom); + + // If it's a duplicate, skip adding it to the output but add any missing information + if (dupetype != 0x00) + { + saveditem = lastrom; + pos = i; + + // Disks, Media, and Roms have more information to fill + if (item is Disk disk && saveditem is Disk savedDisk) + savedDisk.FillMissingInformation(disk); + else if (item is Formats.File fileItem && saveditem is Formats.File savedFile) + savedFile.FillMissingInformation(fileItem); + else if (item is Media media && saveditem is Media savedMedia) + savedMedia.FillMissingInformation(media); + else if (item is Rom romItem && saveditem is Rom savedRom) + savedRom.FillMissingInformation(romItem); + + saveditem.SetFieldValue(DatItem.DupeTypeKey, dupetype); + + // If the current system has a lower ID than the previous, set the system accordingly + if (item.GetFieldValue(DatItem.SourceKey)?.Index < saveditem.GetFieldValue(DatItem.SourceKey)?.Index) + { + item.SetFieldValue(DatItem.SourceKey, item.GetFieldValue(DatItem.SourceKey)!.Clone() as Source); + saveditem.CopyMachineInformation(item); + saveditem.SetName(item.GetName()); + } + + // If the current machine is a child of the new machine, use the new machine instead + if (saveditem.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.CloneOfKey) == item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey) + || saveditem.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.RomOfKey) == item.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey)) + { + saveditem.CopyMachineInformation(item); + saveditem.SetName(item.GetName()); + } + + break; + } + } + + // If no duplicate is found, add it to the list + if (dupetype == 0x00) + { + outfiles.Add(item); + } + // Otherwise, if a new rom information is found, add that + else + { + outfiles.RemoveAt(pos); + outfiles.Insert(pos, saveditem); + } + } + + // Then return the result + return outfiles; + } + + /// + /// Resolve name duplicates in an arbitrary set of DatItems based on the supplied information + /// + /// List of File objects representing the roms to be merged + /// A List of DatItem objects representing the renamed roms + public static List ResolveNames(List infiles) + { + // Create the output list + List output = []; + + // First we want to make sure the list is in alphabetical order + Sort(ref infiles, true); + + // Now we want to loop through and check names + DatItem? lastItem = null; + string? lastrenamed = null; + int lastid = 0; + for (int i = 0; i < infiles.Count; i++) + { + DatItem datItem = infiles[i]; + + // If we have the first item, we automatically add it + if (lastItem == null) + { + output.Add(datItem); + lastItem = datItem; + continue; + } + + // Get the last item name, if applicable + string lastItemName = lastItem.GetName() + ?? lastItem.GetStringFieldValue(Models.Metadata.DatItem.TypeKey).AsEnumValue().AsStringValue() + ?? string.Empty; + + // Get the current item name, if applicable + string datItemName = datItem.GetName() + ?? datItem.GetStringFieldValue(Models.Metadata.DatItem.TypeKey).AsEnumValue().AsStringValue() + ?? string.Empty; + + // If the current item exactly matches the last item, then we don't add it +#if NET20 || NET35 + if ((datItem.GetDuplicateStatus(lastItem) & DupeType.All) != 0) +#else + if (datItem.GetDuplicateStatus(lastItem).HasFlag(DupeType.All)) +#endif + { + staticLogger.Verbose($"Exact duplicate found for '{datItemName}'"); + continue; + } + + // If the current name matches the previous name, rename the current item + else if (datItemName == lastItemName) + { + staticLogger.Verbose($"Name duplicate found for '{datItemName}'"); + + if (datItem is Disk || datItem is Formats.File || datItem is Media || datItem is Rom) + { + datItemName += GetDuplicateSuffix(datItem); + lastrenamed ??= datItemName; + } + + // If we have a conflict with the last renamed item, do the right thing + if (datItemName == lastrenamed) + { + lastrenamed = datItemName; + datItemName += (lastid == 0 ? string.Empty : "_" + lastid); + lastid++; + } + // If we have no conflict, then we want to reset the lastrenamed and id + else + { + lastrenamed = null; + lastid = 0; + } + + // Set the item name back to the datItem + datItem.SetName(datItemName); + + output.Add(datItem); + } + + // Otherwise, we say that we have a valid named file + else + { + output.Add(datItem); + lastItem = datItem; + lastrenamed = null; + lastid = 0; + } + } + + // One last sort to make sure this is ordered + Sort(ref output, true); + + return output; + } + + /// + /// Resolve name duplicates in an arbitrary set of DatItems based on the supplied information + /// + /// List of File objects representing the roms to be merged + /// A List of DatItem objects representing the renamed roms + public static List> ResolveNamesDB(List> infiles) + { + // Create the output dict + List> output = []; + + // First we want to make sure the list is in alphabetical order + Sort(ref infiles, true); + + // Now we want to loop through and check names + DatItem? lastItem = null; + string? lastrenamed = null; + int lastid = 0; + foreach (var datItem in infiles) + { + // If we have the first item, we automatically add it + if (lastItem == null) + { + output.Add(datItem); + lastItem = datItem.Value; + continue; + } + + // Get the last item name, if applicable + string lastItemName = lastItem.GetName() + ?? lastItem.GetStringFieldValue(Models.Metadata.DatItem.TypeKey).AsEnumValue().AsStringValue() + ?? string.Empty; + + // Get the current item name, if applicable + string datItemName = datItem.Value.GetName() + ?? datItem.Value.GetStringFieldValue(Models.Metadata.DatItem.TypeKey).AsEnumValue().AsStringValue() + ?? string.Empty; + + // If the current item exactly matches the last item, then we don't add it +#if NET20 || NET35 + if ((datItem.Value.GetDuplicateStatus(lastItem) & DupeType.All) != 0) +#else + if (datItem.Value.GetDuplicateStatus(lastItem).HasFlag(DupeType.All)) +#endif + { + staticLogger.Verbose($"Exact duplicate found for '{datItemName}'"); + continue; + } + + // If the current name matches the previous name, rename the current item + else if (datItemName == lastItemName) + { + staticLogger.Verbose($"Name duplicate found for '{datItemName}'"); + + if (datItem.Value is Disk || datItem.Value is Formats.File || datItem.Value is Media || datItem.Value is Rom) + { + datItemName += GetDuplicateSuffix(datItem.Value); + lastrenamed ??= datItemName; + } + + // If we have a conflict with the last renamed item, do the right thing + if (datItemName == lastrenamed) + { + lastrenamed = datItemName; + datItemName += (lastid == 0 ? string.Empty : "_" + lastid); + lastid++; + } + // If we have no conflict, then we want to reset the lastrenamed and id + else + { + lastrenamed = null; + lastid = 0; + } + + // Set the item name back to the datItem + datItem.Value.SetName(datItemName); + output.Add(datItem); + } + + // Otherwise, we say that we have a valid named file + else + { + output.Add(datItem); + lastItem = datItem.Value; + lastrenamed = null; + lastid = 0; + } + } + + // One last sort to make sure this is ordered + Sort(ref output, true); + + return output; + } + + /// + /// Get duplicate suffix based on the item type + /// + private static string GetDuplicateSuffix(DatItem datItem) + { + return datItem switch + { + Disk disk => disk.GetDuplicateSuffix(), + Formats.File file => file.GetDuplicateSuffix(), + Media media => media.GetDuplicateSuffix(), + Rom rom => rom.GetDuplicateSuffix(), + _ => "_1", + }; + } + + /// + /// Sort a list of File objects by SourceID, Game, and Name (in order) + /// + /// List of File objects representing the roms to be sorted + /// True if files are not renamed, false otherwise + /// True if it sorted correctly, false otherwise + public static bool Sort(ref List roms, bool norename) + { + roms.Sort(delegate (DatItem x, DatItem y) + { + try + { + var nc = new NaturalComparer(); + + // If machine names don't match + string? xMachineName = x.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey); + string? yMachineName = y.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey); + if (xMachineName != yMachineName) + return nc.Compare(xMachineName, yMachineName); + + // If types don't match + string? xType = x.GetStringFieldValue(Models.Metadata.DatItem.TypeKey); + string? yType = y.GetStringFieldValue(Models.Metadata.DatItem.TypeKey); + if (xType != yType) + return xType.AsEnumValue() - yType.AsEnumValue(); + + // If directory names don't match + string? xDirectoryName = Path.GetDirectoryName(TextHelper.RemovePathUnsafeCharacters(x.GetName() ?? string.Empty)); + string? yDirectoryName = Path.GetDirectoryName(TextHelper.RemovePathUnsafeCharacters(y.GetName() ?? string.Empty)); + if (xDirectoryName != yDirectoryName) + return nc.Compare(xDirectoryName, yDirectoryName); + + // If item names don't match + string? xName = Path.GetFileName(TextHelper.RemovePathUnsafeCharacters(x.GetName() ?? string.Empty)); + string? yName = Path.GetFileName(TextHelper.RemovePathUnsafeCharacters(y.GetName() ?? string.Empty)); + if (xName != yName) + return nc.Compare(xName, yName); + + // Otherwise, compare on machine or source, depending on the flag + int? xSourceIndex = x.GetFieldValue(DatItem.SourceKey)?.Index; + int? ySourceIndex = y.GetFieldValue(DatItem.SourceKey)?.Index; + return (norename ? nc.Compare(xMachineName, yMachineName) : (xSourceIndex - ySourceIndex) ?? 0); + } + catch + { + // Absorb the error + return 0; + } + }); + + return true; + } + + /// + /// Sort a list of File objects by SourceID, Game, and Name (in order) + /// + /// List of File objects representing the roms to be sorted + /// True if files are not renamed, false otherwise + /// True if it sorted correctly, false otherwise + public static bool Sort(ref List> roms, bool norename) + { + roms.Sort(delegate (KeyValuePair x, KeyValuePair y) + { + try + { + var nc = new NaturalComparer(); + + // If machine names don't match + string? xMachineName = x.Value.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey); + string? yMachineName = y.Value.GetFieldValue(DatItem.MachineKey)!.GetStringFieldValue(Models.Metadata.Machine.NameKey); + if (xMachineName != yMachineName) + return nc.Compare(xMachineName, yMachineName); + + // If types don't match + string? xType = x.Value.GetStringFieldValue(Models.Metadata.DatItem.TypeKey); + string? yType = y.Value.GetStringFieldValue(Models.Metadata.DatItem.TypeKey); + if (xType != yType) + return xType.AsEnumValue() - yType.AsEnumValue(); + + // If directory names don't match + string? xDirectoryName = Path.GetDirectoryName(TextHelper.RemovePathUnsafeCharacters(x.Value.GetName() ?? string.Empty)); + string? yDirectoryName = Path.GetDirectoryName(TextHelper.RemovePathUnsafeCharacters(y.Value.GetName() ?? string.Empty)); + if (xDirectoryName != yDirectoryName) + return nc.Compare(xDirectoryName, yDirectoryName); + + // If item names don't match + string? xName = Path.GetFileName(TextHelper.RemovePathUnsafeCharacters(x.Value.GetName() ?? string.Empty)); + string? yName = Path.GetFileName(TextHelper.RemovePathUnsafeCharacters(y.Value.GetName() ?? string.Empty)); + if (xName != yName) + return nc.Compare(xName, yName); + + // Otherwise, compare on machine or source, depending on the flag + int? xSourceIndex = x.Value.GetFieldValue(DatItem.SourceKey)?.Index; + int? ySourceIndex = y.Value.GetFieldValue(DatItem.SourceKey)?.Index; + return (norename ? nc.Compare(xMachineName, yMachineName) : (xSourceIndex - ySourceIndex) ?? 0); + } + catch + { + // Absorb the error + return 0; + } + }); + + return true; + } + + #endregion + } +} \ No newline at end of file diff --git a/SabreTools.DatTools/DatFileTool.cs b/SabreTools.DatTools/DatFileTool.cs index dbe58151..43192ddd 100644 --- a/SabreTools.DatTools/DatFileTool.cs +++ b/SabreTools.DatTools/DatFileTool.cs @@ -577,7 +577,7 @@ namespace SabreTools.DatTools foreach (var key in datFile.Items.Keys) #endif { - List items = DatItem.Merge(datFile.Items[key]); + List items = DatItemTool.Merge(datFile.Items[key]); // If the rom list is empty or null, just skip it if (items == null || items.Count == 0) @@ -802,7 +802,7 @@ namespace SabreTools.DatTools foreach (var key in datFile.Items.Keys) #endif { - List items = DatItem.Merge(datFile.Items[key]); + List items = DatItemTool.Merge(datFile.Items[key]); // If the rom list is empty or null, just skip it if (items == null || items.Count == 0) @@ -1012,7 +1012,7 @@ namespace SabreTools.DatTools foreach (var key in datFile.Items.Keys) #endif { - List items = DatItem.Merge(datFile.Items[key]); + List items = DatItemTool.Merge(datFile.Items[key]); // If the rom list is empty or null, just skip it if (items == null || items.Count == 0) @@ -1326,7 +1326,7 @@ namespace SabreTools.DatTools foreach (var key in datFile.Items.Keys) #endif { - List items = DatItem.Merge(datFile.Items[key]); + List items = DatItemTool.Merge(datFile.Items[key]); // If the rom list is empty or null, just skip it if (items == null || items.Count == 0) diff --git a/SabreTools.DatTools/DatFromDir.cs b/SabreTools.DatTools/DatFromDir.cs index ecf45cef..0b9d2c1c 100644 --- a/SabreTools.DatTools/DatFromDir.cs +++ b/SabreTools.DatTools/DatFromDir.cs @@ -263,7 +263,7 @@ namespace SabreTools.DatTools foreach (var baseFile in extracted) #endif { - DatItem? datItem = DatItem.Create(baseFile); + DatItem? datItem = DatItemTool.CreateDatItem(baseFile); if (datItem == null) #if NET40_OR_GREATER || NETCOREAPP return; @@ -407,7 +407,7 @@ namespace SabreTools.DatTools logger.Verbose($"'{Path.GetFileName(item)}' treated like a file"); var header = datFile.Header.GetStringFieldValue(Models.Metadata.Header.HeaderKey); BaseFile? baseFile = FileTypeTool.GetInfo(item, header, _hashes); - DatItem? datItem = DatItem.Create(baseFile, asFile); + DatItem? datItem = DatItemTool.CreateDatItem(baseFile, asFile); if (datItem != null) ProcessFileHelper(datFile, item, datItem, basePath, string.Empty); } diff --git a/SabreTools.DatTools/Rebuilder.cs b/SabreTools.DatTools/Rebuilder.cs index aff1edaf..fdebfddb 100644 --- a/SabreTools.DatTools/Rebuilder.cs +++ b/SabreTools.DatTools/Rebuilder.cs @@ -353,7 +353,7 @@ namespace SabreTools.DatTools { foreach (BaseFile entry in entries) { - DatItem? internalDatItem = DatItem.Create(entry); + DatItem? internalDatItem = DatItemTool.CreateDatItem(entry); if (internalDatItem == null) continue; diff --git a/SabreTools.DatTools/Splitter.cs b/SabreTools.DatTools/Splitter.cs index 494281fb..d19d5672 100644 --- a/SabreTools.DatTools/Splitter.cs +++ b/SabreTools.DatTools/Splitter.cs @@ -904,7 +904,7 @@ namespace SabreTools.DatTools foreach (var key in datFile.Items.Keys) #endif { - List items = DatItem.Merge(datFile.Items[key]); + List items = DatItemTool.Merge(datFile.Items[key]); // If the rom list is empty or null, just skip it if (items == null || items.Count == 0) diff --git a/SabreTools.Test/DatItems/DatItemTests.cs b/SabreTools.Test/DatItems/DatItemTests.cs index 8e05c2cf..5c377312 100644 --- a/SabreTools.Test/DatItems/DatItemTests.cs +++ b/SabreTools.Test/DatItems/DatItemTests.cs @@ -20,7 +20,7 @@ namespace SabreTools.Test.DatItems public void CreateBaseFileTest(FileType fileType, ItemType? expected) { var baseFile = CreateBaseFile(fileType); - var actual = DatItem.Create(baseFile); + var actual = DatItemTool.CreateDatItem(baseFile); Assert.Equal(expected, actual?.GetStringFieldValue(Models.Metadata.DatItem.TypeKey).AsEnumValue()); }