diff --git a/RombaSharp/RombaSharp.Help.cs b/RombaSharp/RombaSharp.Help.cs index 1895a5ee..0227fec2 100644 --- a/RombaSharp/RombaSharp.Help.cs +++ b/RombaSharp/RombaSharp.Help.cs @@ -1072,7 +1072,7 @@ particular DAT."; Globals.Logger.Error("This feature is not yet implemented: import"); // First ensure the inputs and database connection - Inputs = DirectoryExtensions.GetFilesOnly(Inputs); + Inputs = DirectoryExtensions.GetFilesOnly(Inputs).Select(p => p.CurrentPath).ToList(); SqliteConnection dbc = new SqliteConnection(_connectionString); SqliteCommand slc = new SqliteCommand(); dbc.Open(); @@ -1349,7 +1349,7 @@ particular DAT."; Globals.Logger.Error("This feature is not yet implemented: merge"); // Verify that the inputs are valid directories - Inputs = DirectoryExtensions.GetDirectoriesOnly(Inputs); + Inputs = DirectoryExtensions.GetDirectoriesOnly(Inputs).Select(p => p.CurrentPath).ToList(); // Loop over all input directories foreach (string input in Inputs) diff --git a/SabreTools.Library/DatFiles/DatFile.cs b/SabreTools.Library/DatFiles/DatFile.cs index 084b3650..c39a93af 100644 --- a/SabreTools.Library/DatFiles/DatFile.cs +++ b/SabreTools.Library/DatFiles/DatFile.cs @@ -249,8 +249,8 @@ namespace SabreTools.Library.DatFiles bool onlySame) { // Ensure we only have files in the inputs - List inputFileNames = DirectoryExtensions.GetFilesOnly(inputPaths, appendparent: true); - List baseFileNames = DirectoryExtensions.GetFilesOnly(basePaths); + List inputFileNames = DirectoryExtensions.GetFilesOnly(inputPaths, appendparent: true); + List baseFileNames = DirectoryExtensions.GetFilesOnly(basePaths); // If we're in standard update mode, run through all of the inputs if (updateMode == UpdateMode.None) @@ -318,7 +318,7 @@ namespace SabreTools.Library.DatFiles /// Paths to DATs to parse /// Filter object to be passed to the DatItem level /// List of DatData objects representing headers - private List PopulateUserData(List inputs, Filter filter) + private List PopulateUserData(List inputs, Filter filter) { DatFile[] datFiles = new DatFile[inputs.Count]; InternalStopwatch watch = new InternalStopwatch("Processing individual DATs"); @@ -326,8 +326,8 @@ namespace SabreTools.Library.DatFiles // Parse all of the DATs into their own DatFiles in the array Parallel.For(0, inputs.Count, Globals.ParallelOptions, i => { - string input = inputs[i]; - Globals.Logger.User($"Adding DAT: {input.Split('¬')[0]}"); + var input = inputs[i]; + Globals.Logger.User($"Adding DAT: {input.CurrentPath}"); datFiles[i] = Create(DatHeader.CloneFiltering()); datFiles[i].Parse(input, i, keep: true); }); @@ -358,7 +358,7 @@ namespace SabreTools.Library.DatFiles /// List of Fields representing what should be updated [only for base replacement] /// True if descriptions should only be replaced if the game name is the same, false otherwise private void BaseReplace( - List inputFileNames, + List inputFileNames, string outDir, bool inplace, Filter filter, @@ -423,9 +423,9 @@ namespace SabreTools.Library.DatFiles }; // We want to try to replace each item in each input DAT from the base - foreach (string path in inputFileNames) + foreach (ParentablePath path in inputFileNames) { - Globals.Logger.User($"Replacing items in '{path.Split('¬')[0]}' from the base DAT"); + Globals.Logger.User($"Replacing items in '{path.CurrentPath}' from the base DAT"); // First we parse in the DAT internally DatFile intDat = Create(DatHeader.CloneFiltering()); @@ -844,15 +844,15 @@ namespace SabreTools.Library.DatFiles /// Names of the input files /// Optional param for output directory /// True if the output files should overwrite their inputs, false otherwise - private void DiffAgainst(List inputFileNames, string outDir, bool inplace) + private void DiffAgainst(List inputFileNames, string outDir, bool inplace) { // For comparison's sake, we want to use CRC as the base ordering Items.BucketBy(BucketedBy.CRC, DedupeType.Full); // Now we want to compare each input DAT against the base - foreach (string path in inputFileNames) + foreach (ParentablePath path in inputFileNames) { - Globals.Logger.User($"Comparing '{path.Split('¬')[0]}' to base DAT"); + Globals.Logger.User($"Comparing '{path.CurrentPath}' to base DAT"); // First we parse in the DAT internally DatFile intDat = Create(); @@ -896,7 +896,7 @@ namespace SabreTools.Library.DatFiles /// Output directory to write the DATs to /// True if cascaded diffs are outputted in-place, false otherwise /// True if the first cascaded diff file should be skipped on output, false otherwise - private void DiffCascade(List inputs, List datHeaders, string outDir, bool inplace, bool skip) + private void DiffCascade(List inputs, List datHeaders, string outDir, bool inplace, bool skip) { // Create a list of DatData objects representing output files List outDats = new List(); @@ -979,7 +979,7 @@ namespace SabreTools.Library.DatFiles /// List of inputs to write out from /// Output directory to write the DATs to /// Non-zero flag for diffing mode, zero otherwise - private void DiffNoCascade(List inputs, string outDir, UpdateMode diff) + private void DiffNoCascade(List inputs, string outDir, UpdateMode diff) { InternalStopwatch watch = new InternalStopwatch("Initializing all output DATs"); @@ -1071,7 +1071,7 @@ namespace SabreTools.Library.DatFiles if (diff.HasFlag(UpdateMode.DiffNoDupesOnly)) { DatItem newrom = item.Clone() as DatItem; - newrom.MachineName += $" ({Path.GetFileNameWithoutExtension(inputs[item.IndexId].Split('¬')[0])})"; + newrom.MachineName += $" ({Path.GetFileNameWithoutExtension(inputs[item.IndexId].CurrentPath)})"; outerDiffData.Items.Add(key, newrom); } @@ -1084,7 +1084,7 @@ namespace SabreTools.Library.DatFiles if (item.DupeType.HasFlag(DupeType.External)) { DatItem newrom = item.Clone() as DatItem; - newrom.MachineName += $" ({Path.GetFileNameWithoutExtension(inputs[item.IndexId].Split('¬')[0])})"; + newrom.MachineName += $" ({Path.GetFileNameWithoutExtension(inputs[item.IndexId].CurrentPath)})"; dupeData.Items.Add(key, newrom); } @@ -1125,7 +1125,7 @@ namespace SabreTools.Library.DatFiles /// /// List of inputs to write out from /// Output directory to write the DATs to - private void MergeNoDiff(List inputs, string outDir) + private void MergeNoDiff(List inputs, string outDir) { // If we're in SuperDAT mode, prefix all games with their respective DATs if (DatHeader.Type == "SuperDAT") @@ -1137,8 +1137,8 @@ namespace SabreTools.Library.DatFiles foreach (DatItem item in items) { DatItem newItem = item; - string filename = inputs[newItem.IndexId].Split('¬')[0]; - string rootpath = inputs[newItem.IndexId].Split('¬')[1]; + string filename = inputs[newItem.IndexId].CurrentPath; + string rootpath = inputs[newItem.IndexId].ParentPath; rootpath += (string.IsNullOrWhiteSpace(rootpath) ? string.Empty : Path.DirectorySeparatorChar.ToString()); filename = filename.Remove(0, rootpath.Length); @@ -1165,13 +1165,13 @@ namespace SabreTools.Library.DatFiles /// Optional param for output directory /// True if the output files should overwrite their inputs, false otherwise /// Filter object to be passed to the DatItem level - private void Update(List inputFileNames, string outDir, bool inplace, Filter filter) + private void Update(List inputFileNames, string outDir, bool inplace, Filter filter) { // Iterate over the files - foreach (string file in inputFileNames) + foreach (ParentablePath file in inputFileNames) { DatFile innerDatdata = Create(DatHeader); - Globals.Logger.User($"Processing '{Path.GetFileName(file.Split('¬')[0])}'"); + Globals.Logger.User($"Processing '{Path.GetFileName(file.CurrentPath)}'"); innerDatdata.Parse(file, keep: true, keepext: innerDatdata.DatHeader.DatFormat.HasFlag(DatFormat.TSV) || innerDatdata.DatHeader.DatFormat.HasFlag(DatFormat.CSV) @@ -1197,7 +1197,7 @@ namespace SabreTools.Library.DatFiles public static DatFile CreateAndParse(string filename) { DatFile datFile = Create(); - datFile.Parse(filename); + datFile.Parse(new ParentablePath(filename)); return datFile; } @@ -1208,27 +1208,26 @@ namespace SabreTools.Library.DatFiles /// Index ID for the DAT /// True if full pathnames are to be kept, false otherwise (default) /// True if original extension should be kept, false otherwise (default) - public void Parse(string filename, int indexId = 0, bool keep = false, bool keepext = false) + public void Parse(ParentablePath filename, int indexId = 0, bool keep = false, bool keepext = false) { - // Check if we have a split path and get the filename accordingly - if (filename.Contains("¬")) - filename = filename.Split('¬')[0]; + // Get the current path from the filename + string currentPath = filename.CurrentPath; // Check the file extension first as a safeguard - if (!PathExtensions.HasValidDatExtension(filename)) + if (!PathExtensions.HasValidDatExtension(currentPath)) return; // If the output filename isn't set already, get the internal filename - DatHeader.FileName = (string.IsNullOrWhiteSpace(DatHeader.FileName) ? (keepext ? Path.GetFileName(filename) : Path.GetFileNameWithoutExtension(filename)) : DatHeader.FileName); + DatHeader.FileName = (string.IsNullOrWhiteSpace(DatHeader.FileName) ? (keepext ? Path.GetFileName(currentPath) : Path.GetFileNameWithoutExtension(currentPath)) : DatHeader.FileName); // If the output type isn't set already, get the internal output type - DatHeader.DatFormat = (DatHeader.DatFormat == 0 ? filename.GetDatFormat() : DatHeader.DatFormat); + DatHeader.DatFormat = (DatHeader.DatFormat == 0 ? currentPath.GetDatFormat() : DatHeader.DatFormat); Items.SetBucketedBy(BucketedBy.CRC); // Setting this because it can reduce issues later // Now parse the correct type of DAT try { - Create(filename.GetDatFormat(), this)?.ParseFile(filename, indexId, keep); + Create(currentPath.GetDatFormat(), this)?.ParseFile(currentPath, indexId, keep); } catch (Exception ex) { @@ -2551,10 +2550,10 @@ namespace SabreTools.Library.DatFiles return; // Get only files from the inputs - List files = DirectoryExtensions.GetFilesOnly(inputs, appendparent: true); + List files = DirectoryExtensions.GetFilesOnly(inputs, appendparent: true); // Loop over the input files - foreach (string file in files) + foreach (ParentablePath file in files) { // Create and fill the new DAT Parse(file); @@ -3059,10 +3058,6 @@ namespace SabreTools.Library.DatFiles Items.BucketBy(BucketedBy.Game, DedupeType.None, norename: true); - // TODO: How can the size be negative when dealing with Int64? - if (Items.Statistics.TotalSize < 0) - Items.Statistics.TotalSize = Int64.MaxValue + Items.Statistics.TotalSize; - var consoleOutput = BaseReport.Create(StatReportFormat.None, null, true, true); consoleOutput.ReplaceStatistics(DatHeader.FileName, Items.Keys.Count(), Items.Statistics); } diff --git a/SabreTools.Library/DatFiles/Filter.cs b/SabreTools.Library/DatFiles/Filter.cs index d9f5eb9a..b4c88e2d 100644 --- a/SabreTools.Library/DatFiles/Filter.cs +++ b/SabreTools.Library/DatFiles/Filter.cs @@ -5,7 +5,7 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; -using NaturalSort; + using SabreTools.Library.Data; using SabreTools.Library.DatItems; using SabreTools.Library.Tools; @@ -97,8 +97,16 @@ namespace SabreTools.Library.DatFiles /// private FilterItem RebuildTo = new FilterItem(); - // TODO: Machine.Devices - List - // TODO: Machine.SlotOptions - List + /// + /// Include or exclude machine devices + /// + private FilterItem Devices = new FilterItem(); + + /// + /// Include or exclude machine slotoptions + /// + private FilterItem SlotOptions = new FilterItem(); + // TODO: Machine.Infos - List> /// @@ -463,6 +471,20 @@ namespace SabreTools.Library.DatFiles RebuildTo.PositiveSet.Add(value); break; + case Field.Devices: + if (negate) + Devices.NegativeSet.Add(value); + else + Devices.PositiveSet.Add(value); + break; + + case Field.SlotOptions: + if (negate) + SlotOptions.NegativeSet.Add(value); + else + SlotOptions.PositiveSet.Add(value); + break; + case Field.MachineType: if (negate) MachineTypes.Negative |= value.AsMachineType(); @@ -1085,6 +1107,30 @@ namespace SabreTools.Library.DatFiles if (this.RebuildTo.MatchesNegativeSet(item.RebuildTo) == true) return false; + // Filter on devices + bool anyPositiveDevice = false; + bool anyNegativeDevice = false; + foreach (string device in item.Devices) + { + anyPositiveDevice |= this.Devices.MatchesPositiveSet(device) == true; + anyNegativeDevice |= this.Devices.MatchesNegativeSet(device) == false; + } + + if (!anyPositiveDevice || anyNegativeDevice) + return false; + + // Filter on slot options + bool anyPositiveSlotOption = false; + bool anyNegativeSlotOption = false; + foreach (string device in item.SlotOptions) + { + anyPositiveSlotOption |= this.SlotOptions.MatchesPositiveSet(device) == true; + anyNegativeSlotOption |= this.SlotOptions.MatchesNegativeSet(device) == false; + } + + if (!anyPositiveSlotOption || anyNegativeSlotOption) + return false; + // Filter on machine type if (this.MachineTypes.MatchesPositive(MachineType.NULL, item.MachineType) == false) return false; diff --git a/SabreTools.Library/DatFiles/ItemDictionary.cs b/SabreTools.Library/DatFiles/ItemDictionary.cs index 13d0c2f2..cb707885 100644 --- a/SabreTools.Library/DatFiles/ItemDictionary.cs +++ b/SabreTools.Library/DatFiles/ItemDictionary.cs @@ -35,11 +35,6 @@ namespace SabreTools.Library.DatFiles #region Publically available fields - /// - /// DatStats object for reporting - /// - public DatStats Statistics { get; set; } - /// /// Get the keys from the file dictionary /// @@ -63,6 +58,11 @@ namespace SabreTools.Library.DatFiles } } + /// + /// DatStats object for reporting + /// + public DatStats Statistics { get; private set; } + #endregion #region Accessors @@ -263,19 +263,6 @@ namespace SabreTools.Library.DatFiles Statistics = new DatStats(); } - /// - /// Constructor for statistics only - /// - /// Existing statistics to pre-populate - public ItemDictionary(DatStats stats) - { - bucketedBy = BucketedBy.Default; - mergedBy = DedupeType.None; - items = new Dictionary>(); - - Statistics = stats; - } - #endregion #region Custom Functionality diff --git a/SabreTools.Library/Help/Feature.cs b/SabreTools.Library/Help/Feature.cs index a849d5c7..338605f0 100644 --- a/SabreTools.Library/Help/Feature.cs +++ b/SabreTools.Library/Help/Feature.cs @@ -21,7 +21,7 @@ namespace SabreTools.Library.Help public string Name { get; protected set; } public List Flags { get; protected set; } public string Description { get; protected set; } - public string LongDescription { get; protected set; } // TODO: Use this to generate README.1ST? + public string LongDescription { get; protected set; } public Dictionary Features { get; protected set; } #endregion diff --git a/SabreTools.Library/Tools/DirectoryExtensions.cs b/SabreTools.Library/Tools/DirectoryExtensions.cs index e71e36dc..b6946429 100644 --- a/SabreTools.Library/Tools/DirectoryExtensions.cs +++ b/SabreTools.Library/Tools/DirectoryExtensions.cs @@ -62,21 +62,24 @@ namespace SabreTools.Library.Tools /// Retrieve a list of just directories from inputs /// /// List of strings representing directories and files - /// True if the parent name should be appended after the special character "¬", false otherwise (default) + /// True if the parent name should be included in the ParentablePath, false otherwise (default) /// List of strings representing just directories from the inputs - public static List GetDirectoriesOnly(List inputs, bool appendparent = false) + public static List GetDirectoriesOnly(List inputs, bool appendparent = false) { - List outputs = new List(); + List outputs = new List(); foreach (string input in inputs) { if (Directory.Exists(input)) { + // Get the parent path in case of appending + string parentPath = Path.GetFullPath(input); + List directories = GetDirectoriesOrdered(input); foreach (string dir in directories) { try { - outputs.Add(Path.GetFullPath(dir) + (appendparent ? $"¬{Path.GetFullPath(input)}" : string.Empty)); + outputs.Add(new ParentablePath(Path.GetFullPath(dir), appendparent ? parentPath : string.Empty)); } catch (PathTooLongException) { @@ -130,13 +133,16 @@ namespace SabreTools.Library.Tools /// Retrieve a list of just files from inputs /// /// List of strings representing directories and files - /// True if the parent name should be appended after the special character "¬", false otherwise (default) + /// True if the parent name should be be included in the ParentablePath, false otherwise (default) /// List of strings representing just files from the inputs - public static List GetFilesOnly(List inputs, bool appendparent = false) + public static List GetFilesOnly(List inputs, bool appendparent = false) { - List outputs = new List(); + List outputs = new List(); foreach (string input in inputs) { + // Get the parent path in case of appending + string parentPath = Path.GetFullPath(input); + if (Directory.Exists(input)) { List files = GetFilesOrdered(input); @@ -144,7 +150,7 @@ namespace SabreTools.Library.Tools { try { - outputs.Add(Path.GetFullPath(file) + (appendparent ? $"¬{Path.GetFullPath(input)}" : string.Empty)); + outputs.Add(new ParentablePath(Path.GetFullPath(file), appendparent ? parentPath : string.Empty)); } catch (PathTooLongException) { @@ -160,7 +166,7 @@ namespace SabreTools.Library.Tools { try { - outputs.Add(Path.GetFullPath(input) + (appendparent ? $"¬{Path.GetFullPath(input)}" : string.Empty)); + outputs.Add(new ParentablePath(Path.GetFullPath(input), appendparent ? parentPath : string.Empty)); } catch (PathTooLongException) { diff --git a/SabreTools.Library/Tools/ParentablePath.cs b/SabreTools.Library/Tools/ParentablePath.cs new file mode 100644 index 00000000..0a2857be --- /dev/null +++ b/SabreTools.Library/Tools/ParentablePath.cs @@ -0,0 +1,17 @@ +namespace SabreTools.Library.Tools +{ + /// + /// A path that optionally contains a parent root + /// + public class ParentablePath + { + public string CurrentPath { get; set; } + public string ParentPath { get; set; } + + public ParentablePath(string currentPath, string parentPath = null) + { + CurrentPath = currentPath; + ParentPath = parentPath; + } + } +} diff --git a/SabreTools.Library/Tools/PathExtensions.cs b/SabreTools.Library/Tools/PathExtensions.cs index 832e49df..f9940bee 100644 --- a/SabreTools.Library/Tools/PathExtensions.cs +++ b/SabreTools.Library/Tools/PathExtensions.cs @@ -40,36 +40,32 @@ namespace SabreTools.Library.Tools /// Input combined path to use /// True if path separators should be converted to '-', false otherwise /// Subpath for the file - public static string GetNormalizedFileName(string path, bool sanitize) + public static string GetNormalizedFileName(ParentablePath path, bool sanitize) { // Check that we have a combined path first - if (!path.Contains("¬")) + if (string.IsNullOrWhiteSpace(path.ParentPath)) { - string filename = Path.GetFileName(path); + string filename = Path.GetFileName(path.CurrentPath); if (sanitize) filename = filename.Replace(Path.DirectorySeparatorChar, '-').Replace(Path.AltDirectorySeparatorChar, '-'); return filename; } - // First separate out the parts - string child = path.Split('¬')[0]; - string parent = path.Split('¬')[1]; - // If the parts are the same, return the filename from the first part - if (string.Equals(child, parent, StringComparison.Ordinal)) + if (string.Equals(path.CurrentPath, path.ParentPath, StringComparison.Ordinal)) { - string filename = Path.GetFileName(child); + string filename = Path.GetFileName(path.CurrentPath); if (sanitize) filename = filename.Replace(Path.DirectorySeparatorChar, '-').Replace(Path.AltDirectorySeparatorChar, '-'); return filename; } - // Otherwise, remove the parent from the child and return the remainder + // Otherwise, remove the path.ParentPath from the path.CurrentPath and return the remainder else { - string filename = child.Remove(0, parent.Length + 1); + string filename = path.CurrentPath.Remove(0, path.ParentPath.Length + 1); if (sanitize) filename = filename.Replace(Path.DirectorySeparatorChar, '-').Replace(Path.AltDirectorySeparatorChar, '-'); @@ -84,36 +80,34 @@ namespace SabreTools.Library.Tools /// Input path to create output for /// True if the output file should go to the same input folder, false otherwise /// Complete output path - public static string GetOutputPath(string outDir, string inputpath, bool inplace) + public static string GetOutputPath(string outDir, ParentablePath inputPath, bool inplace) { // First, we need to ensure the output directory outDir = DirectoryExtensions.Ensure(outDir); // Check if we have a split path or not - bool splitpath = inputpath.Contains("¬"); + bool splitpath = !string.IsNullOrWhiteSpace(inputPath.ParentPath); // If we have a split path, we need to treat the input separately if (splitpath) { - string[] split = inputpath.Split('¬'); - // If we have an inplace output, use the directory name from the input path if (inplace) { - outDir = Path.GetDirectoryName(split[0]); + outDir = Path.GetDirectoryName(inputPath.CurrentPath); } // TODO: Should this be the default? Always create a subfolder if a folder is found? // If we are processing a path that is coming from a directory and we are outputting to the current directory, we want to get the subfolder to write to - else if (split[0].Length != split[1].Length && outDir == Environment.CurrentDirectory) + else if (inputPath.CurrentPath.Length != inputPath.ParentPath.Length && outDir == Environment.CurrentDirectory) { - outDir = Path.GetDirectoryName(Path.Combine(outDir, split[0].Remove(0, Path.GetDirectoryName(split[1]).Length + 1))); + outDir = Path.GetDirectoryName(Path.Combine(outDir, inputPath.CurrentPath.Remove(0, Path.GetDirectoryName(inputPath.ParentPath).Length + 1))); } // If we are processing a path that is coming from a directory, we want to get the subfolder to write to - else if (split[0].Length != split[1].Length) + else if (inputPath.CurrentPath.Length != inputPath.ParentPath.Length) { - outDir = Path.GetDirectoryName(Path.Combine(outDir, split[0].Remove(0, split[1].Length + 1))); + outDir = Path.GetDirectoryName(Path.Combine(outDir, inputPath.CurrentPath.Remove(0, inputPath.ParentPath.Length + 1))); } // If we are processing a single file from the root of a directory, we just use the output directory @@ -128,7 +122,7 @@ namespace SabreTools.Library.Tools // If we have an inplace output, use the directory name from the input path if (inplace) { - outDir = Path.GetDirectoryName(inputpath); + outDir = Path.GetDirectoryName(inputPath.CurrentPath); } // Otherwise, just use the supplied output directory