2016-11-04 11:57:32 -07:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Threading.Tasks ;
using SabreTools.Helper.Data ;
using SabreTools.Helper.Tools ;
#if MONO
using System.IO ;
#else
using Alphaleonis.Win32.Filesystem ;
using IOException = System . IO . IOException ;
using SearchOption = System . IO . SearchOption ;
#endif
using SharpCompress.Common ;
namespace SabreTools.Helper.Dats
{
public partial class DatFile
{
#region Populate DAT from Directory [ MODULAR DONE , FOR NOW ]
/// <summary>
/// Create a new Dat from a directory
/// </summary>
/// <param name="basePath">Base folder to be used in creating the DAT</param>
2017-02-26 23:05:31 -08:00
/// <param name="omitFromScan">Hash flag saying what hashes should not be calculated</param>
2016-11-04 11:57:32 -07:00
/// <param name="bare">True if the date should be omitted from the DAT, false otherwise</param>
/// <param name="archivesAsFiles">True if archives should be treated as files, false otherwise</param>
/// <param name="enableGzip">True if GZIP archives should be treated as files, false otherwise</param>
2017-05-03 14:11:38 -07:00
/// <param name="skipFileType">Type of files that should be skipped</param>
2016-11-04 11:57:32 -07:00
/// <param name="addBlanks">True if blank items should be created for empty folders, false otherwise</param>
/// <param name="addDate">True if dates should be archived for all files, false otherwise</param>
/// <param name="tempDir">Name of the directory to create a temp folder in (blank is current directory)</param>
/// <param name="outDir">Output directory to </param>
/// <param name="copyFiles">True if files should be copied to the temp directory before hashing, false otherwise</param>
/// <param name="headerToCheckAgainst">Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise</param>
2017-05-03 14:11:38 -07:00
public bool PopulateFromDir ( string basePath , Hash omitFromScan , bool bare , bool archivesAsFiles , bool enableGzip ,
SkipFileType skipFileType , bool addBlanks , bool addDate , string tempDir , bool copyFiles , string headerToCheckAgainst )
2016-11-04 11:57:32 -07:00
{
// If the description is defined but not the name, set the name from the description
if ( String . IsNullOrEmpty ( Name ) & & ! String . IsNullOrEmpty ( Description ) )
{
Name = Description ;
}
// If the name is defined but not the description, set the description from the name
else if ( ! String . IsNullOrEmpty ( Name ) & & String . IsNullOrEmpty ( Description ) )
{
2017-01-27 16:53:29 -08:00
Description = Name + ( bare ? "" : " (" + Date + ")" ) ;
2016-11-04 11:57:32 -07:00
}
// If neither the name or description are defined, set them from the automatic values
else if ( String . IsNullOrEmpty ( Name ) & & String . IsNullOrEmpty ( Description ) )
{
Name = basePath . Split ( Path . DirectorySeparatorChar ) . Last ( ) ;
2017-01-27 16:53:29 -08:00
Description = Name + ( bare ? "" : " (" + Date + ")" ) ;
2016-11-04 11:57:32 -07:00
}
// Process the input
if ( Directory . Exists ( basePath ) )
{
2017-03-01 21:26:27 -08:00
Globals . Logger . Verbose ( "Folder found: " + basePath ) ;
2016-11-04 11:57:32 -07:00
// Process the files in the main folder
List < string > files = Directory . EnumerateFiles ( basePath , "*" , SearchOption . TopDirectoryOnly ) . ToList ( ) ;
2017-03-17 23:58:35 -07:00
Parallel . ForEach ( files , Globals . ParallelOptions , item = >
2017-03-14 20:28:23 -07:00
{
2017-05-03 14:11:38 -07:00
PopulateFromDirCheckFile ( item , basePath , omitFromScan , bare , archivesAsFiles , enableGzip , skipFileType ,
addBlanks , addDate , tempDir , copyFiles , headerToCheckAgainst ) ;
2017-03-14 20:28:23 -07:00
} ) ;
2016-11-04 11:57:32 -07:00
// Find all top-level subfolders
files = Directory . EnumerateDirectories ( basePath , "*" , SearchOption . TopDirectoryOnly ) . ToList ( ) ;
2017-03-17 23:58:35 -07:00
Parallel . ForEach ( files , Globals . ParallelOptions , item = >
2017-03-14 20:28:23 -07:00
{
List < string > subfiles = Directory . EnumerateFiles ( item , "*" , SearchOption . AllDirectories ) . ToList ( ) ;
2017-03-17 23:58:35 -07:00
Parallel . ForEach ( subfiles , Globals . ParallelOptions , subitem = >
{
2017-05-03 14:11:38 -07:00
PopulateFromDirCheckFile ( subitem , basePath , omitFromScan , bare , archivesAsFiles , enableGzip , skipFileType ,
addBlanks , addDate , tempDir , copyFiles , headerToCheckAgainst ) ;
2017-03-17 23:58:35 -07:00
} ) ;
2017-03-14 20:28:23 -07:00
} ) ;
2016-11-04 11:57:32 -07:00
// Now find all folders that are empty, if we are supposed to
if ( ! Romba & & addBlanks )
{
2017-03-14 20:36:16 -07:00
List < string > empties = FileTools . GetEmptyDirectories ( basePath ) . ToList ( ) ;
2017-03-17 23:58:35 -07:00
Parallel . ForEach ( empties , Globals . ParallelOptions , dir = >
2017-03-14 20:28:23 -07:00
{
// Get the full path for the directory
string fulldir = Path . GetFullPath ( dir ) ;
2017-03-14 19:59:19 -07:00
2017-03-14 20:28:23 -07:00
// Set the temporary variables
string gamename = "" ;
string romname = "" ;
2017-03-14 19:59:19 -07:00
2017-03-14 20:28:23 -07:00
// If we have a SuperDAT, we want anything that's not the base path as the game, and the file as the rom
if ( Type = = "SuperDAT" )
{
gamename = fulldir . Remove ( 0 , basePath . Length + 1 ) ;
2017-03-15 20:10:06 -07:00
romname = "_" ;
2017-03-14 20:28:23 -07:00
}
2017-03-14 19:59:19 -07:00
2017-03-14 20:28:23 -07:00
// Otherwise, we want just the top level folder as the game, and the file as everything else
else
{
gamename = fulldir . Remove ( 0 , basePath . Length + 1 ) . Split ( Path . DirectorySeparatorChar ) [ 0 ] ;
2017-03-15 20:10:06 -07:00
romname = Path . Combine ( fulldir . Remove ( 0 , basePath . Length + 1 + gamename . Length ) , "_" ) ;
2017-03-14 20:28:23 -07:00
}
2017-03-14 19:59:19 -07:00
2017-03-14 20:28:23 -07:00
// Sanitize the names
if ( gamename . StartsWith ( Path . DirectorySeparatorChar . ToString ( ) ) )
{
gamename = gamename . Substring ( 1 ) ;
}
if ( gamename . EndsWith ( Path . DirectorySeparatorChar . ToString ( ) ) )
{
gamename = gamename . Substring ( 0 , gamename . Length - 1 ) ;
}
if ( romname . StartsWith ( Path . DirectorySeparatorChar . ToString ( ) ) )
{
romname = romname . Substring ( 1 ) ;
}
if ( romname . EndsWith ( Path . DirectorySeparatorChar . ToString ( ) ) )
{
romname = romname . Substring ( 0 , romname . Length - 1 ) ;
}
2017-03-14 19:59:19 -07:00
2017-03-14 20:28:23 -07:00
Globals . Logger . Verbose ( "Adding blank empty folder: " + gamename ) ;
this [ "null" ] . Add ( new Rom ( romname , gamename , omitFromScan ) ) ;
2017-03-14 19:59:19 -07:00
} ) ;
2016-11-04 11:57:32 -07:00
}
}
else if ( File . Exists ( basePath ) )
{
2017-05-03 14:11:38 -07:00
PopulateFromDirCheckFile ( basePath , Path . GetDirectoryName ( Path . GetDirectoryName ( basePath ) ) , omitFromScan , bare , archivesAsFiles , enableGzip ,
skipFileType , addBlanks , addDate , tempDir , copyFiles , headerToCheckAgainst ) ;
2016-11-04 11:57:32 -07:00
}
// Now that we're done, delete the temp folder (if it's not the default)
2017-03-01 21:26:27 -08:00
Globals . Logger . User ( "Cleaning temp folder" ) ;
2017-03-15 14:44:44 -07:00
if ( tempDir ! = Path . GetTempPath ( ) )
2016-11-04 11:57:32 -07:00
{
2017-03-15 20:07:28 -07:00
FileTools . TryDeleteDirectory ( tempDir ) ;
2016-11-04 11:57:32 -07:00
}
return true ;
}
/// <summary>
/// Check a given file for hashes, based on current settings
/// </summary>
/// <param name="item">Filename of the item to be checked</param>
/// <param name="basePath">Base folder to be used in creating the DAT</param>
2017-02-26 23:05:31 -08:00
/// <param name="omitFromScan">Hash flag saying what hashes should not be calculated</param>
2016-11-04 11:57:32 -07:00
/// <param name="bare">True if the date should be omitted from the DAT, false otherwise</param>
/// <param name="archivesAsFiles">True if archives should be treated as files, false otherwise</param>
/// <param name="enableGzip">True if GZIP archives should be treated as files, false otherwise</param>
2017-05-03 14:11:38 -07:00
/// <param name="skipFileType">Type of files that should be skipped</param>
2016-11-04 11:57:32 -07:00
/// <param name="addBlanks">True if blank items should be created for empty folders, false otherwise</param>
/// <param name="addDate">True if dates should be archived for all files, false otherwise</param>
/// <param name="tempDir">Name of the directory to create a temp folder in (blank is current directory)</param>
/// <param name="copyFiles">True if files should be copied to the temp directory before hashing, false otherwise</param>
/// <param name="headerToCheckAgainst">Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise</param>
2017-02-26 23:05:31 -08:00
private void PopulateFromDirCheckFile ( string item , string basePath , Hash omitFromScan , bool bare , bool archivesAsFiles ,
2017-05-03 14:11:38 -07:00
bool enableGzip , SkipFileType skipFileType , bool addBlanks , bool addDate , string tempDir , bool copyFiles , string headerToCheckAgainst )
2016-11-04 11:57:32 -07:00
{
// Define the temporary directory
string tempSubDir = Path . GetFullPath ( Path . Combine ( tempDir , Path . GetRandomFileName ( ) ) ) + Path . DirectorySeparatorChar ;
// Special case for if we are in Romba mode (all names are supposed to be SHA-1 hashes)
if ( Romba )
{
2017-03-01 21:26:27 -08:00
Rom rom = ArchiveTools . GetTorrentGZFileInfo ( item ) ;
2016-11-04 11:57:32 -07:00
// If the rom is valid, write it out
if ( rom ! = null & & rom . Name ! = null )
{
// Add the list if it doesn't exist already
2016-11-08 15:29:52 -08:00
Add ( rom . Size + "-" + rom . CRC , rom ) ;
2017-03-01 21:26:27 -08:00
Globals . Logger . User ( "File added: " + Path . GetFileNameWithoutExtension ( item ) + Environment . NewLine ) ;
2016-11-04 11:57:32 -07:00
}
else
{
2017-03-01 21:26:27 -08:00
Globals . Logger . User ( "File not added: " + Path . GetFileNameWithoutExtension ( item ) + Environment . NewLine ) ;
2016-11-04 11:57:32 -07:00
return ;
}
return ;
}
// If we're copying files, copy it first and get the new filename
string newItem = item ;
string newBasePath = basePath ;
if ( copyFiles )
{
newBasePath = Path . Combine ( tempDir , Path . GetRandomFileName ( ) ) ;
newItem = Path . GetFullPath ( Path . Combine ( newBasePath , Path . GetFullPath ( item ) . Remove ( 0 , basePath . Length + 1 ) ) ) ;
Directory . CreateDirectory ( Path . GetDirectoryName ( newItem ) ) ;
File . Copy ( item , newItem , true ) ;
}
2017-03-16 14:17:35 -07:00
// Create a list for all found items
2017-03-29 11:32:22 -07:00
List < Rom > extracted = null ;
2017-03-16 14:17:35 -07:00
2017-03-29 11:32:22 -07:00
// Temporarily set the archivesAsFiles if we have a GZip archive and we're not supposed to use it as one
if ( archivesAsFiles & & ! enableGzip & & newItem . EndsWith ( ".gz" ) )
2016-11-04 11:57:32 -07:00
{
2017-03-29 11:32:22 -07:00
archivesAsFiles = false ;
2016-11-04 11:57:32 -07:00
}
2017-03-29 11:32:22 -07:00
// If we don't have archives as files, try to scan the file as an archive
if ( ! archivesAsFiles )
2016-11-04 11:57:32 -07:00
{
2017-03-29 11:32:22 -07:00
// If all deep hash skip flags are set, do a quickscan
if ( omitFromScan = = Hash . SecureHashes )
{
extracted = ArchiveTools . GetArchiveFileInfo ( newItem , date : addDate ) ;
}
// Otherwise, get the list with whatever hashes are wanted
else
{
extracted = ArchiveTools . GetExtendedArchiveFileInfo ( newItem , omitFromScan : omitFromScan , date : addDate ) ;
}
2017-03-16 14:17:35 -07:00
}
2016-11-04 11:57:32 -07:00
2017-05-03 14:11:38 -07:00
// If the file should be skipped based on type, do so now
if ( ( extracted ! = null & & skipFileType = = SkipFileType . Archive )
| | ( extracted = = null & & skipFileType = = SkipFileType . File ) )
{
return ;
}
2017-03-16 14:17:35 -07:00
// If the extracted list is null, just scan the item itself
2017-03-29 11:32:22 -07:00
if ( extracted = = null | | archivesAsFiles )
2017-03-16 14:17:35 -07:00
{
PopulateFromDirProcessFile ( newItem , "" , newBasePath , omitFromScan , addDate , headerToCheckAgainst ) ;
}
// Otherwise, add all of the found items
else
{
// First take care of the found items
2017-03-18 21:26:50 -07:00
Parallel . ForEach ( extracted , Globals . ParallelOptions , rom = >
2017-03-16 14:17:35 -07:00
{
PopulateFromDirProcessFileHelper ( newItem ,
rom ,
basePath ,
( Path . GetDirectoryName ( Path . GetFullPath ( item ) ) + Path . DirectorySeparatorChar ) . Remove ( 0 , basePath . Length ) + Path . GetFileNameWithoutExtension ( item ) ) ;
2017-03-18 21:26:50 -07:00
} ) ;
2016-11-04 11:57:32 -07:00
2017-03-16 14:17:35 -07:00
// Then, if we're looking for blanks, get all of the blank folders and add them
if ( addBlanks )
2016-11-04 11:57:32 -07:00
{
2017-03-16 14:17:35 -07:00
List < string > empties = ArchiveTools . GetEmptyFoldersInArchive ( newItem ) ;
2017-03-17 23:58:35 -07:00
Parallel . ForEach ( empties , Globals . ParallelOptions , empty = >
2017-03-14 15:18:07 -07:00
{
2017-03-16 14:17:35 -07:00
Rom emptyRom = new Rom ( Path . Combine ( empty , "_" ) , newItem , omitFromScan ) ;
PopulateFromDirProcessFileHelper ( newItem ,
emptyRom ,
basePath ,
( Path . GetDirectoryName ( Path . GetFullPath ( item ) ) + Path . DirectorySeparatorChar ) . Remove ( 0 , basePath . Length ) + Path . GetFileNameWithoutExtension ( item ) ) ;
} ) ;
2016-11-04 11:57:32 -07:00
}
}
// Cue to delete the file if it's a copy
if ( copyFiles & & item ! = newItem )
{
2017-03-15 20:07:28 -07:00
FileTools . TryDeleteDirectory ( newBasePath ) ;
2016-11-04 11:57:32 -07:00
}
// Delete the sub temp directory
2017-03-15 20:07:28 -07:00
FileTools . TryDeleteDirectory ( tempSubDir ) ;
2016-11-04 11:57:32 -07:00
}
/// <summary>
/// Process a single file as a file
/// </summary>
/// <param name="item">File to be added</param>
/// <param name="parent">Parent game to be used</param>
/// <param name="basePath">Path the represents the parent directory</param>
2017-02-26 23:05:31 -08:00
/// <param name="omitFromScan">Hash flag saying what hashes should not be calculated</param>
2016-11-04 11:57:32 -07:00
/// <param name="addDate">True if dates should be archived for all files, false otherwise</param>
/// <param name="headerToCheckAgainst">Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise</param>
2017-02-26 23:05:31 -08:00
private void PopulateFromDirProcessFile ( string item , string parent , string basePath , Hash omitFromScan ,
2017-03-01 21:26:27 -08:00
bool addDate , string headerToCheckAgainst )
2016-11-04 11:57:32 -07:00
{
2017-03-01 21:26:27 -08:00
Globals . Logger . Verbose ( Path . GetFileName ( item ) + " treated like a file" ) ;
Rom rom = FileTools . GetFileInfo ( item , omitFromScan : omitFromScan , date : addDate , header : headerToCheckAgainst ) ;
2016-11-04 11:57:32 -07:00
2017-03-01 21:26:27 -08:00
PopulateFromDirProcessFileHelper ( item , rom , basePath , parent ) ;
2016-11-04 11:57:32 -07:00
}
/// <summary>
/// Process a single file as a file (with found Rom data)
/// </summary>
/// <param name="item">File to be added</param>
/// <param name="item">Rom data to be used to write to file</param>
/// <param name="basepath">Path the represents the parent directory</param>
/// <param name="parent">Parent game to be used</param>
2017-03-01 21:26:27 -08:00
private void PopulateFromDirProcessFileHelper ( string item , DatItem datItem , string basepath , string parent )
2016-11-04 11:57:32 -07:00
{
// If the datItem isn't a Rom or Disk, return
if ( datItem . Type ! = ItemType . Rom & & datItem . Type ! = ItemType . Disk )
{
return ;
}
2017-01-27 16:53:29 -08:00
string key = "" ;
2016-11-04 11:57:32 -07:00
if ( datItem . Type = = ItemType . Rom )
{
key = ( ( Rom ) datItem ) . Size + "-" + ( ( Rom ) datItem ) . CRC ;
}
else
{
key = ( ( Disk ) datItem ) . MD5 ;
}
// Add the list if it doesn't exist already
2016-11-08 15:29:52 -08:00
Add ( key ) ;
2016-11-04 11:57:32 -07:00
try
{
// If the basepath ends with a directory separator, remove it
if ( ! basepath . EndsWith ( Path . DirectorySeparatorChar . ToString ( ) ) )
{
basepath + = Path . DirectorySeparatorChar . ToString ( ) ;
}
// Make sure we have the full item path
item = Path . GetFullPath ( item ) ;
// Get the data to be added as game and item names
2017-01-27 16:53:29 -08:00
string gamename = "" ;
string romname = "" ;
2016-11-04 11:57:32 -07:00
// If the parent is blank, then we have a non-archive file
2017-01-27 16:53:29 -08:00
if ( parent = = "" )
2016-11-04 11:57:32 -07:00
{
// If we have a SuperDAT, we want anything that's not the base path as the game, and the file as the rom
if ( Type = = "SuperDAT" )
{
gamename = Path . GetDirectoryName ( item . Remove ( 0 , basepath . Length ) ) ;
romname = Path . GetFileName ( item ) ;
}
// Otherwise, we want just the top level folder as the game, and the file as everything else
else
{
gamename = item . Remove ( 0 , basepath . Length ) . Split ( Path . DirectorySeparatorChar ) [ 0 ] ;
romname = item . Remove ( 0 , ( Path . Combine ( basepath , gamename ) . Length ) ) ;
}
}
// Otherwise, we assume that we have an archive
else
{
// If we have a SuperDAT, we want the archive name as the game, and the file as everything else (?)
if ( Type = = "SuperDAT" )
{
gamename = parent ;
2017-03-16 14:17:35 -07:00
romname = datItem . Name ;
2016-11-04 11:57:32 -07:00
}
// Otherwise, we want the archive name as the game, and the file as everything else
else
{
gamename = parent ;
2017-03-16 14:17:35 -07:00
romname = datItem . Name ;
2016-11-04 11:57:32 -07:00
}
}
// Sanitize the names
2017-03-30 18:02:52 -07:00
if ( romname = = null )
{
romname = "" ;
}
2016-11-04 11:57:32 -07:00
if ( gamename . StartsWith ( Path . DirectorySeparatorChar . ToString ( ) ) )
{
gamename = gamename . Substring ( 1 ) ;
}
if ( gamename . EndsWith ( Path . DirectorySeparatorChar . ToString ( ) ) )
{
gamename = gamename . Substring ( 0 , gamename . Length - 1 ) ;
}
if ( romname . StartsWith ( Path . DirectorySeparatorChar . ToString ( ) ) )
{
romname = romname . Substring ( 1 ) ;
}
if ( romname . EndsWith ( Path . DirectorySeparatorChar . ToString ( ) ) )
{
romname = romname . Substring ( 0 , romname . Length - 1 ) ;
}
if ( ! String . IsNullOrEmpty ( gamename ) & & String . IsNullOrEmpty ( romname ) )
{
romname = gamename ;
gamename = "Default" ;
}
// Update rom information
datItem . Name = romname ;
if ( datItem . Machine = = null )
{
datItem . Machine = new Machine
{
Name = gamename ,
Description = gamename ,
} ;
}
else
{
datItem . Machine . Name = gamename ;
datItem . Machine . Description = gamename ;
}
// Add the file information to the DAT
2016-11-08 15:29:52 -08:00
Add ( key , datItem ) ;
2016-11-04 11:57:32 -07:00
2017-03-01 21:26:27 -08:00
Globals . Logger . User ( "File added: " + romname + Environment . NewLine ) ;
2016-11-04 11:57:32 -07:00
}
catch ( IOException ex )
{
2017-03-01 21:26:27 -08:00
Globals . Logger . Error ( ex . ToString ( ) ) ;
2016-11-04 11:57:32 -07:00
return ;
}
}
#endregion
}
}