2020-07-31 23:17:12 -07:00
using System ;
using System.Collections.Generic ;
2020-08-27 21:40:08 -07:00
using System.IO ;
2020-08-27 22:19:31 -07:00
using System.Threading.Tasks ;
2020-08-28 10:32:17 -07:00
2020-08-27 21:40:08 -07:00
using SabreTools.Library.Data ;
2020-07-31 23:17:12 -07:00
using SabreTools.Library.DatFiles ;
2020-08-01 21:42:28 -07:00
using SabreTools.Library.DatItems ;
2020-07-31 23:17:12 -07:00
using SabreTools.Library.Help ;
2020-08-01 23:04:11 -07:00
using SabreTools.Library.IO ;
2020-08-27 22:19:31 -07:00
using SabreTools.Library.Tools ;
2020-07-31 23:17:12 -07:00
namespace SabreTools.Features
{
internal class Update : BaseFeature
{
public const string Value = "Update" ;
public Update ( )
{
Name = Value ;
Flags = new List < string > ( ) { "-ud" , "--update" } ;
Description = "Update and manipulate DAT(s)" ;
_featureType = FeatureType . Flag ;
LongDescription = "This is the multitool part of the program, allowing for almost every manipulation to a DAT, or set of DATs. This is also a combination of many different programs that performed DAT manipulation that work better together." ;
Features = new Dictionary < string , Feature > ( ) ;
// Output Formats
AddFeature ( OutputTypeListInput ) ;
this [ OutputTypeListInput ] . AddFeature ( PrefixStringInput ) ;
this [ OutputTypeListInput ] . AddFeature ( PostfixStringInput ) ;
this [ OutputTypeListInput ] . AddFeature ( QuotesFlag ) ;
this [ OutputTypeListInput ] . AddFeature ( RomsFlag ) ;
this [ OutputTypeListInput ] . AddFeature ( GamePrefixFlag ) ;
this [ OutputTypeListInput ] . AddFeature ( AddExtensionStringInput ) ;
this [ OutputTypeListInput ] . AddFeature ( ReplaceExtensionStringInput ) ;
this [ OutputTypeListInput ] . AddFeature ( RemoveExtensionsFlag ) ;
this [ OutputTypeListInput ] . AddFeature ( RombaFlag ) ;
2020-08-18 23:39:13 -07:00
this [ OutputTypeListInput ] [ RombaFlag ] . AddFeature ( RombaDepthInt32Input ) ;
2020-07-31 23:17:12 -07:00
this [ OutputTypeListInput ] . AddFeature ( DeprecatedFlag ) ;
AddHeaderFeatures ( ) ;
AddFeature ( KeepEmptyGamesFlag ) ;
AddFeature ( CleanFlag ) ;
AddFeature ( RemoveUnicodeFlag ) ;
AddFeature ( DescriptionAsNameFlag ) ;
AddInternalSplitFeatures ( ) ;
AddFeature ( TrimFlag ) ;
this [ TrimFlag ] . AddFeature ( RootDirStringInput ) ;
AddFeature ( SingleSetFlag ) ;
AddFeature ( DedupFlag ) ;
AddFeature ( GameDedupFlag ) ;
AddFeature ( MergeFlag ) ;
this [ MergeFlag ] . AddFeature ( NoAutomaticDateFlag ) ;
AddFeature ( DiffAllFlag ) ;
this [ DiffAllFlag ] . AddFeature ( NoAutomaticDateFlag ) ;
AddFeature ( DiffDuplicatesFlag ) ;
this [ DiffDuplicatesFlag ] . AddFeature ( NoAutomaticDateFlag ) ;
AddFeature ( DiffIndividualsFlag ) ;
this [ DiffIndividualsFlag ] . AddFeature ( NoAutomaticDateFlag ) ;
AddFeature ( DiffNoDuplicatesFlag ) ;
this [ DiffNoDuplicatesFlag ] . AddFeature ( NoAutomaticDateFlag ) ;
AddFeature ( DiffAgainstFlag ) ;
this [ DiffAgainstFlag ] . AddFeature ( BaseDatListInput ) ;
2020-08-01 15:10:41 -07:00
this [ DiffAgainstFlag ] . AddFeature ( ByGameFlag ) ;
2020-07-31 23:17:12 -07:00
AddFeature ( BaseReplaceFlag ) ;
this [ BaseReplaceFlag ] . AddFeature ( BaseDatListInput ) ;
this [ BaseReplaceFlag ] . AddFeature ( UpdateFieldListInput ) ;
this [ BaseReplaceFlag ] [ UpdateFieldListInput ] . AddFeature ( OnlySameFlag ) ;
this [ BaseReplaceFlag ] . AddFeature ( UpdateNamesFlag ) ;
this [ BaseReplaceFlag ] . AddFeature ( UpdateHashesFlag ) ;
this [ BaseReplaceFlag ] . AddFeature ( UpdateDescriptionFlag ) ;
this [ BaseReplaceFlag ] [ UpdateDescriptionFlag ] . AddFeature ( OnlySameFlag ) ;
this [ BaseReplaceFlag ] . AddFeature ( UpdateGameTypeFlag ) ;
this [ BaseReplaceFlag ] . AddFeature ( UpdateYearFlag ) ;
this [ BaseReplaceFlag ] . AddFeature ( UpdateManufacturerFlag ) ;
this [ BaseReplaceFlag ] . AddFeature ( UpdateParentsFlag ) ;
AddFeature ( ReverseBaseReplaceFlag ) ;
this [ ReverseBaseReplaceFlag ] . AddFeature ( BaseDatListInput ) ;
this [ ReverseBaseReplaceFlag ] . AddFeature ( UpdateFieldListInput ) ;
this [ ReverseBaseReplaceFlag ] [ UpdateFieldListInput ] . AddFeature ( OnlySameFlag ) ;
this [ ReverseBaseReplaceFlag ] . AddFeature ( UpdateNamesFlag ) ;
this [ ReverseBaseReplaceFlag ] . AddFeature ( UpdateHashesFlag ) ;
this [ ReverseBaseReplaceFlag ] . AddFeature ( UpdateDescriptionFlag ) ;
this [ ReverseBaseReplaceFlag ] [ UpdateDescriptionFlag ] . AddFeature ( OnlySameFlag ) ;
this [ ReverseBaseReplaceFlag ] . AddFeature ( UpdateGameTypeFlag ) ;
this [ ReverseBaseReplaceFlag ] . AddFeature ( UpdateYearFlag ) ;
this [ ReverseBaseReplaceFlag ] . AddFeature ( UpdateManufacturerFlag ) ;
this [ ReverseBaseReplaceFlag ] . AddFeature ( UpdateParentsFlag ) ;
AddFeature ( DiffCascadeFlag ) ;
this [ DiffCascadeFlag ] . AddFeature ( SkipFirstOutputFlag ) ;
AddFeature ( DiffReverseCascadeFlag ) ;
this [ DiffReverseCascadeFlag ] . AddFeature ( SkipFirstOutputFlag ) ;
2020-08-21 10:38:42 -07:00
AddFeature ( ExtraIniListInput ) ;
2020-07-31 23:17:12 -07:00
AddFilteringFeatures ( ) ;
AddFeature ( OutputDirStringInput ) ;
AddFeature ( InplaceFlag ) ;
AddFeature ( ThreadsInt32Input ) ;
}
public override void ProcessFeatures ( Dictionary < string , Feature > features )
{
base . ProcessFeatures ( features ) ;
// Get feature flags
var updateFields = GetUpdateFields ( features ) ;
var updateMode = GetUpdateMode ( features ) ;
// Normalize the extensions
Header . AddExtension = ( string . IsNullOrWhiteSpace ( Header . AddExtension ) | | Header . AddExtension . StartsWith ( "." )
? Header . AddExtension
: $".{Header.AddExtension}" ) ;
Header . ReplaceExtension = ( string . IsNullOrWhiteSpace ( Header . ReplaceExtension ) | | Header . ReplaceExtension . StartsWith ( "." )
? Header . ReplaceExtension
: $".{Header.ReplaceExtension}" ) ;
// If we're in a special update mode and the names aren't set, set defaults
if ( updateMode ! = 0 )
{
// Get the values that will be used
if ( string . IsNullOrWhiteSpace ( Header . Date ) )
Header . Date = DateTime . Now . ToString ( "yyyy-MM-dd" ) ;
if ( string . IsNullOrWhiteSpace ( Header . Name ) )
{
Header . Name = ( updateMode ! = 0 ? "DiffDAT" : "MergeDAT" )
+ ( Header . Type = = "SuperDAT" ? "-SuperDAT" : string . Empty )
+ ( Header . DedupeRoms ! = DedupeType . None ? "-deduped" : string . Empty ) ;
}
if ( string . IsNullOrWhiteSpace ( Header . Description ) )
{
Header . Description = ( updateMode ! = 0 ? "DiffDAT" : "MergeDAT" )
+ ( Header . Type = = "SuperDAT" ? "-SuperDAT" : string . Empty )
+ ( Header . DedupeRoms ! = DedupeType . None ? " - deduped" : string . Empty ) ;
if ( ! GetBoolean ( features , NoAutomaticDateValue ) )
Header . Description + = $" ({Header.Date})" ;
}
if ( string . IsNullOrWhiteSpace ( Header . Category ) & & updateMode ! = 0 )
Header . Category = "DiffDAT" ;
if ( string . IsNullOrWhiteSpace ( Header . Author ) )
Header . Author = "SabreTools" ;
}
// If no update fields are set, default to Names
if ( updateFields = = null | | updateFields . Count = = 0 )
2020-08-24 22:25:47 -07:00
updateFields = new List < Field > ( ) { Field . DatItem_Name } ;
2020-07-31 23:17:12 -07:00
2020-08-01 21:42:28 -07:00
// Ensure we only have files in the inputs
2020-08-27 21:40:08 -07:00
List < ParentablePath > inputPaths = DirectoryExtensions . GetFilesOnly ( Inputs , appendparent : true ) ;
List < ParentablePath > basePaths = DirectoryExtensions . GetFilesOnly ( GetList ( features , BaseDatListValue ) ) ;
2020-08-01 21:42:28 -07:00
// If we're in standard update mode, run through all of the inputs
if ( updateMode = = UpdateMode . None )
{
2020-08-27 21:40:08 -07:00
// Loop through each input and update
2020-08-27 22:19:31 -07:00
Parallel . ForEach ( inputPaths , Globals . ParallelOptions , inputPath = >
2020-08-27 21:40:08 -07:00
{
// Create a new base DatFile
DatFile datFile = DatFile . Create ( Header ) ;
Globals . Logger . User ( $"Processing '{Path.GetFileName(inputPath.CurrentPath)}'" ) ;
datFile . Parse ( inputPath , keep : true ,
keepext : datFile . Header . DatFormat . HasFlag ( DatFormat . TSV )
| | datFile . Header . DatFormat . HasFlag ( DatFormat . CSV )
| | datFile . Header . DatFormat . HasFlag ( DatFormat . SSV ) ) ;
datFile . ApplyExtras ( Extras ) ;
2020-08-28 13:54:53 -07:00
datFile . ApplySplitting ( GetSplitType ( features ) , false ) ;
datFile . ApplyFilter ( Filter ) ;
2020-08-28 13:33:05 -07:00
datFile . ApplyCleaning ( Cleaner ) ;
2020-08-27 21:40:08 -07:00
// Get the correct output path
string realOutDir = inputPath . GetOutputPath ( OutputDir , GetBoolean ( features , InplaceValue ) ) ;
// Try to output the file, overwriting only if it's not in the current directory
datFile . Write ( realOutDir , overwrite : GetBoolean ( features , InplaceValue ) ) ;
2020-08-27 22:19:31 -07:00
} ) ;
2020-08-27 21:40:08 -07:00
2020-08-01 21:42:28 -07:00
return ;
}
// Reverse inputs if we're in a required mode
if ( updateMode . HasFlag ( UpdateMode . DiffReverseCascade ) )
{
updateMode | = UpdateMode . DiffCascade ;
2020-08-27 21:40:08 -07:00
inputPaths . Reverse ( ) ;
2020-08-01 21:42:28 -07:00
}
if ( updateMode . HasFlag ( UpdateMode . ReverseBaseReplace ) )
{
updateMode | = UpdateMode . BaseReplace ;
2020-08-27 21:40:08 -07:00
basePaths . Reverse ( ) ;
2020-08-01 21:42:28 -07:00
}
// Create a DAT to capture inputs
2020-07-31 23:17:12 -07:00
DatFile userInputDat = DatFile . Create ( Header ) ;
2020-08-01 21:42:28 -07:00
// Populate using the correct set
List < DatHeader > datHeaders ;
if ( updateMode . HasFlag ( UpdateMode . DiffAgainst ) | | updateMode . HasFlag ( UpdateMode . BaseReplace ) )
2020-08-27 21:46:19 -07:00
datHeaders = userInputDat . PopulateUserData ( basePaths ) ;
2020-08-01 21:42:28 -07:00
else
2020-08-27 21:46:19 -07:00
datHeaders = userInputDat . PopulateUserData ( inputPaths ) ;
2020-08-28 13:33:05 -07:00
// Apply the extras, filter, and cleaning
2020-08-27 21:46:19 -07:00
userInputDat . ApplyExtras ( Extras ) ;
2020-08-28 13:54:53 -07:00
userInputDat . ApplySplitting ( GetSplitType ( features ) , false ) ;
userInputDat . ApplyFilter ( Filter ) ;
2020-08-28 13:33:05 -07:00
userInputDat . ApplyCleaning ( Cleaner ) ;
2020-08-01 21:42:28 -07:00
// Output only DatItems that are duplicated across inputs
if ( updateMode . HasFlag ( UpdateMode . DiffDupesOnly ) )
2020-08-27 22:27:23 -07:00
{
DatFile dupeData = userInputDat . DiffDuplicates ( inputPaths ) ;
InternalStopwatch watch = new InternalStopwatch ( "Outputting duplicate DAT" ) ;
dupeData . Write ( OutputDir , overwrite : false ) ;
watch . Stop ( ) ;
}
2020-08-01 21:42:28 -07:00
// Output only DatItems that are not duplicated across inputs
if ( updateMode . HasFlag ( UpdateMode . DiffNoDupesOnly ) )
2020-08-27 22:27:23 -07:00
{
DatFile outerDiffData = userInputDat . DiffNoDuplicates ( inputPaths ) ;
InternalStopwatch watch = new InternalStopwatch ( "Outputting no duplicate DAT" ) ;
outerDiffData . Write ( OutputDir , overwrite : false ) ;
watch . Stop ( ) ;
}
2020-08-01 21:42:28 -07:00
// Output only DatItems that are unique to each input
if ( updateMode . HasFlag ( UpdateMode . DiffIndividualsOnly ) )
2020-08-27 22:27:23 -07:00
{
// Get all of the output DatFiles
List < DatFile > datFiles = userInputDat . DiffIndividuals ( inputPaths ) ;
// Loop through and output the new DatFiles
InternalStopwatch watch = new InternalStopwatch ( "Outputting all individual DATs" ) ;
Parallel . For ( 0 , inputPaths . Count , Globals . ParallelOptions , j = >
{
string path = inputPaths [ j ] . GetOutputPath ( OutputDir , GetBoolean ( features , InplaceValue ) ) ;
// Try to output the file
datFiles [ j ] . Write ( path , overwrite : GetBoolean ( features , InplaceValue ) ) ;
} ) ;
watch . Stop ( ) ;
}
2020-08-01 21:42:28 -07:00
// Output cascaded diffs
if ( updateMode . HasFlag ( UpdateMode . DiffCascade ) )
{
2020-08-27 22:19:31 -07:00
// Preprocess the DatHeaders
Parallel . For ( 0 , datHeaders . Count , Globals . ParallelOptions , j = >
{
// If we're outputting to the runtime folder, rename
if ( ! GetBoolean ( features , InplaceValue ) & & OutputDir = = Environment . CurrentDirectory )
{
string innerpost = $" ({j} - {inputPaths[j].GetNormalizedFileName(true)} Only)" ;
datHeaders [ j ] = userInputDat . Header ;
datHeaders [ j ] . FileName + = innerpost ;
datHeaders [ j ] . Name + = innerpost ;
datHeaders [ j ] . Description + = innerpost ;
}
} ) ;
// Get all of the output DatFiles
List < DatFile > datFiles = userInputDat . DiffCascade ( datHeaders ) ;
// Loop through and output the new DatFiles
InternalStopwatch watch = new InternalStopwatch ( "Outputting all created DATs" ) ;
int startIndex = GetBoolean ( features , SkipFirstOutputValue ) ? 1 : 0 ;
Parallel . For ( startIndex , inputPaths . Count , Globals . ParallelOptions , j = >
{
string path = inputPaths [ j ] . GetOutputPath ( OutputDir , GetBoolean ( features , InplaceValue ) ) ;
// Try to output the file
datFiles [ j ] . Write ( path , overwrite : GetBoolean ( features , InplaceValue ) ) ;
} ) ;
watch . Stop ( ) ;
2020-08-01 21:42:28 -07:00
}
// Output differences against a base DAT
if ( updateMode . HasFlag ( UpdateMode . DiffAgainst ) )
{
2020-08-27 21:40:08 -07:00
// Loop through each input and diff against the base
2020-08-27 22:19:31 -07:00
Parallel . ForEach ( inputPaths , Globals . ParallelOptions , inputPath = >
2020-08-27 21:40:08 -07:00
{
2020-08-28 13:33:05 -07:00
// Parse and process the path to a new DatFile
2020-08-27 21:40:08 -07:00
DatFile repDat = DatFile . Create ( userInputDat . Header . CloneFiltering ( ) ) ;
repDat . Parse ( inputPath , indexId : 1 , keep : true ) ;
repDat . ApplyExtras ( Extras ) ;
2020-08-28 13:54:53 -07:00
repDat . ApplySplitting ( GetSplitType ( features ) , false ) ;
repDat . ApplyFilter ( Filter ) ;
2020-08-28 13:33:05 -07:00
repDat . ApplyCleaning ( Cleaner ) ;
2020-08-27 21:40:08 -07:00
// Now replace the fields from the base DatFile
userInputDat . DiffAgainst ( repDat , GetBoolean ( Features , ByGameValue ) ) ;
// Finally output the diffed DatFile
string interOutDir = inputPath . GetOutputPath ( OutputDir , GetBoolean ( features , InplaceValue ) ) ;
repDat . Write ( interOutDir , overwrite : GetBoolean ( features , InplaceValue ) ) ;
2020-08-27 22:19:31 -07:00
} ) ;
2020-08-01 21:42:28 -07:00
}
2020-08-27 21:40:08 -07:00
// Output DATs after replacing fields from a base DatFile
2020-08-01 21:42:28 -07:00
if ( updateMode . HasFlag ( UpdateMode . BaseReplace ) )
{
2020-08-27 21:40:08 -07:00
// Loop through each input and apply the base DatFile
2020-08-27 22:19:31 -07:00
Parallel . ForEach ( inputPaths , Globals . ParallelOptions , inputPath = >
2020-08-27 21:40:08 -07:00
{
2020-08-28 13:33:05 -07:00
// Parse and process the path to a new DatFile
2020-08-27 21:40:08 -07:00
DatFile repDat = DatFile . Create ( userInputDat . Header . CloneFiltering ( ) ) ;
repDat . Parse ( inputPath , indexId : 1 , keep : true ) ;
repDat . ApplyExtras ( Extras ) ;
2020-08-28 13:54:53 -07:00
repDat . ApplySplitting ( GetSplitType ( features ) , false ) ;
repDat . ApplyFilter ( Filter ) ;
2020-08-28 13:33:05 -07:00
repDat . ApplyCleaning ( Cleaner ) ;
2020-08-27 21:40:08 -07:00
// Now replace the fields from the base DatFile
userInputDat . BaseReplace ( repDat , updateFields , GetBoolean ( features , OnlySameValue ) ) ;
// Finally output the replaced DatFile
string interOutDir = inputPath . GetOutputPath ( OutputDir , GetBoolean ( features , InplaceValue ) ) ;
repDat . Write ( interOutDir , overwrite : GetBoolean ( features , InplaceValue ) ) ;
2020-08-27 22:19:31 -07:00
} ) ;
2020-08-01 21:42:28 -07:00
}
2020-08-01 21:54:42 -07:00
// Merge all input files and write
// This has to be last due to the SuperDAT handling
if ( updateMode . HasFlag ( UpdateMode . Merge ) )
2020-08-27 21:40:08 -07:00
{
// If we're in SuperDAT mode, prefix all games with their respective DATs
if ( string . Equals ( userInputDat . Header . Type , "SuperDAT" , StringComparison . OrdinalIgnoreCase ) )
userInputDat . ApplySuperDAT ( inputPaths ) ;
userInputDat . Write ( OutputDir ) ;
}
2020-07-31 23:17:12 -07:00
}
}
}