Create FileTypeTool and move static methods

This commit is contained in:
Matt Nadareski
2025-01-04 21:17:02 -05:00
parent c449f34410
commit 0cab005a78
12 changed files with 423 additions and 458 deletions

View File

@@ -147,13 +147,13 @@ namespace SabreTools.DatTools
return;
// Initialize possible archive variables
BaseArchive? archive = BaseArchive.Create(item);
BaseArchive? archive = FileTypeTool.CreateArchiveType(item);
// Process archives according to flags
if (archive != null)
{
// Set the archive flags
archive.AvailableHashTypes = _hashes;
archive.SetHashTypes(_hashes);
// Skip if we're treating archives as files and skipping files
#if NET20 || NET35
@@ -406,7 +406,7 @@ namespace SabreTools.DatTools
{
logger.Verbose($"'{Path.GetFileName(item)}' treated like a file");
var header = datFile.Header.GetStringFieldValue(Models.Metadata.Header.HeaderKey);
BaseFile? baseFile = BaseFile.GetInfo(item, header, _hashes, asFiles);
BaseFile? baseFile = FileTypeTool.GetInfo(item, header, _hashes, asFiles);
DatItem? datItem = DatItem.Create(baseFile);
if (datItem != null)
ProcessFileHelper(datFile, item, datItem, basePath, string.Empty);

View File

@@ -311,19 +311,19 @@ namespace SabreTools.DatTools
bool isSingleTorrent = tgz.IsTorrent() || txz.IsTorrent();
// Get the base archive first
BaseArchive? archive = BaseArchive.Create(file);
BaseArchive? archive = FileTypeTool.CreateArchiveType(file);
// Now get all extracted items from the archive
if (archive != null)
{
archive.AvailableHashTypes = quickScan ? [HashType.CRC32] : [HashType.CRC32, HashType.MD5, HashType.SHA1];
archive.SetHashTypes(quickScan ? [HashType.CRC32] : [HashType.CRC32, HashType.MD5, HashType.SHA1]);
entries = archive.GetChildren();
}
// If the entries list is null, we encountered an error or have a file and should scan externally
if (entries == null && System.IO.File.Exists(file))
{
BaseFile? internalFileInfo = BaseFile.GetInfo(file, asFiles: asFiles);
BaseFile? internalFileInfo = FileTypeTool.GetInfo(file, asFiles: asFiles);
// Create the correct DatItem
DatItem? internalDatItem;
@@ -498,7 +498,7 @@ namespace SabreTools.DatTools
if (rule.TransformStream(fileStream, transformStream, keepReadOpen: true, keepWriteOpen: true))
{
// Get the file informations that we will be using
Rom headerless = new(BaseFile.GetInfo(transformStream, keepReadOpen: true));
Rom headerless = new(FileTypeTool.GetInfo(transformStream, keepReadOpen: true));
// If we have duplicates and we're not filtering
if (ShouldRebuild(datFile, headerless, transformStream, false, out dupes))
@@ -572,7 +572,7 @@ namespace SabreTools.DatTools
string? machinename = null;
// Get the item from the current file
Rom item = new(BaseFile.GetInfo(stream, keepReadOpen: true));
Rom item = new(FileTypeTool.GetInfo(stream, keepReadOpen: true));
item.GetFieldValue<Machine>(DatItem.MachineKey)!.SetFieldValue<string?>(Models.Metadata.Machine.DescriptionKey, Path.GetFileNameWithoutExtension(item.GetName()));
item.GetFieldValue<Machine>(DatItem.MachineKey)!.SetFieldValue<string?>(Models.Metadata.Machine.NameKey, Path.GetFileNameWithoutExtension(item.GetName()));
@@ -629,7 +629,7 @@ namespace SabreTools.DatTools
string? machinename = null;
// Get the item from the current file
var item = new Rom(BaseFile.GetInfo(stream, keepReadOpen: true));
var item = new Rom(FileTypeTool.GetInfo(stream, keepReadOpen: true));
// Create a machine for the current item
var machine = new Machine();
@@ -764,7 +764,7 @@ namespace SabreTools.DatTools
// If we have a zipfile, extract the stream to memory
if (isZip != null)
{
BaseArchive? archive = BaseArchive.Create(file);
BaseArchive? archive = FileTypeTool.CreateArchiveType(file);
if (archive == null)
return false;
@@ -823,7 +823,7 @@ namespace SabreTools.DatTools
/// <returns>Folder configured with proper flags</returns>
private static Folder? GetPreconfiguredFolder(DatFile datFile, bool date, OutputFormat outputFormat)
{
Folder? outputArchive = Folder.Create(outputFormat);
Folder? outputArchive = FileTypeTool.CreateFolderType(outputFormat);
if (outputArchive is BaseArchive baseArchive && date)
baseArchive.UseDates = date;

View File

@@ -105,16 +105,16 @@ namespace SabreTools.FileTypes.Archives
catch (EndOfStreamException ex)
{
// Catch this but don't count it as an error because SharpCompress is unsafe
logger.Verbose(ex);
_logger.Verbose(ex);
}
catch (InvalidOperationException ex)
{
logger.Warning(ex);
_logger.Warning(ex);
encounteredErrors = true;
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
encounteredErrors = true;
}
@@ -185,7 +185,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
return (null, null);
}
}
@@ -224,7 +224,7 @@ namespace SabreTools.FileTypes.Archives
BaseFile gzipEntryRom = new();
// Perform a quickscan, if flagged to
if (AvailableHashTypes.Length == 1 && AvailableHashTypes[0] == HashType.CRC32)
if (_hashTypes.Length == 1 && _hashTypes[0] == HashType.CRC32)
{
gzipEntryRom.Filename = gamename;
@@ -239,7 +239,7 @@ namespace SabreTools.FileTypes.Archives
var gz = new gZip();
ZipReturn ret = gz.ZipFileOpen(Filename);
ret = gz.ZipFileOpenReadStream(0, out Stream? gzstream, out ulong streamSize);
gzipEntryRom = GetInfo(gzstream, hashes: AvailableHashTypes);
gzipEntryRom = GetInfo(gzstream, hashes: _hashTypes);
gzipEntryRom.Filename = gz.GetLocalFile(0).Filename;
gzipEntryRom.Parent = gamename;
gzipEntryRom.Date = (gz.TimeStamp > 0 ? gz.TimeStamp.ToString() : null);
@@ -252,7 +252,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
return null;
}
@@ -279,21 +279,21 @@ namespace SabreTools.FileTypes.Archives
// If we have the romba depot files, just skip them gracefully
if (datum == ".romba_size" || datum == ".romba_size.backup")
{
logger.Verbose($"Romba depot file found, skipping: {Filename}");
_logger.Verbose($"Romba depot file found, skipping: {Filename}");
return false;
}
// Check if the name is the right length
if (!Regex.IsMatch(datum, @"^[0-9a-f]{" + Constants.SHA1Length + @"}\.gz"))
{
logger.Warning($"Non SHA-1 filename found, skipping: '{Path.GetFullPath(Filename)}'");
_logger.Warning($"Non SHA-1 filename found, skipping: '{Path.GetFullPath(Filename)}'");
return false;
}
// Check if the file is at least the minimum length
if (filesize < 40 /* bytes */)
{
logger.Warning($"Possibly corrupt file '{Path.GetFullPath(Filename)}' with size {filesize}");
_logger.Warning($"Possibly corrupt file '{Path.GetFullPath(Filename)}' with size {filesize}");
return false;
}
@@ -340,21 +340,21 @@ namespace SabreTools.FileTypes.Archives
// If we have the romba depot files, just skip them gracefully
if (datum == ".romba_size" || datum == ".romba_size.backup")
{
logger.Verbose($"Romba depot file found, skipping: {Filename}");
_logger.Verbose($"Romba depot file found, skipping: {Filename}");
return null;
}
// Check if the name is the right length
if (!Regex.IsMatch(datum, @"^[0-9a-f]{" + Constants.SHA1Length + @"}\.gz"))
{
logger.Warning($"Non SHA-1 filename found, skipping: '{Path.GetFullPath(Filename)}'");
_logger.Warning($"Non SHA-1 filename found, skipping: '{Path.GetFullPath(Filename)}'");
return null;
}
// Check if the file is at least the minimum length
if (filesize < 40 /* bytes */)
{
logger.Warning($"Possibly corrupt file '{Path.GetFullPath(Filename)}' with size {filesize}");
_logger.Warning($"Possibly corrupt file '{Path.GetFullPath(Filename)}' with size {filesize}");
return null;
}
@@ -414,7 +414,7 @@ namespace SabreTools.FileTypes.Archives
// Check that the input file exists
if (!File.Exists(inputFile))
{
logger.Warning($"File '{inputFile}' does not exist!");
_logger.Warning($"File '{inputFile}' does not exist!");
return false;
}

View File

@@ -67,16 +67,16 @@ namespace SabreTools.FileTypes.Archives
catch (EndOfStreamException ex)
{
// Catch this but don't count it as an error because SharpCompress is unsafe
logger.Verbose(ex);
_logger.Verbose(ex);
}
catch (InvalidOperationException ex)
{
logger.Warning(ex);
_logger.Warning(ex);
encounteredErrors = true;
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
encounteredErrors = true;
}
@@ -168,7 +168,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
return (null, null);
}
#else
@@ -201,7 +201,7 @@ namespace SabreTools.FileTypes.Archives
BaseFile rarEntryRom = new();
// Perform a quickscan, if flagged to
if (AvailableHashTypes.Length == 1 && AvailableHashTypes[0] == HashType.CRC32)
if (_hashTypes.Length == 1 && _hashTypes[0] == HashType.CRC32)
{
rarEntryRom.Size = entry.Size;
rarEntryRom.CRC = BitConverter.GetBytes(entry.Crc);
@@ -210,7 +210,7 @@ namespace SabreTools.FileTypes.Archives
else
{
using Stream entryStream = entry.OpenEntryStream();
rarEntryRom = GetInfo(entryStream, size: entry.Size, hashes: AvailableHashTypes);
rarEntryRom = GetInfo(entryStream, size: entry.Size, hashes: _hashTypes);
}
// Fill in common details and add to the list
@@ -225,7 +225,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
return null;
}
@@ -271,7 +271,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
}
return empties;

View File

@@ -118,16 +118,16 @@ namespace SabreTools.FileTypes.Archives
catch (EndOfStreamException ex)
{
// Catch this but don't count it as an error because SharpCompress is unsafe
logger.Verbose(ex);
_logger.Verbose(ex);
}
catch (InvalidOperationException ex)
{
logger.Warning(ex);
_logger.Warning(ex);
encounteredErrors = true;
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
encounteredErrors = true;
}
@@ -221,7 +221,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
return (null, null);
}
}
@@ -265,7 +265,7 @@ namespace SabreTools.FileTypes.Archives
// If we get a read error, log it and continue
if (zr != ZipReturn.ZipGood)
{
logger.Warning($"An error occurred while reading archive {Filename}: Zip Error - {zr}");
_logger.Warning($"An error occurred while reading archive {Filename}: Zip Error - {zr}");
zr = zf.ZipFileCloseReadStream();
continue;
}
@@ -274,7 +274,7 @@ namespace SabreTools.FileTypes.Archives
BaseFile zipEntryRom = new();
// Perform a quickscan, if flagged to
if (AvailableHashTypes.Length == 1 && AvailableHashTypes[0] == HashType.CRC32)
if (_hashTypes.Length == 1 && _hashTypes[0] == HashType.CRC32)
{
zipEntryRom.Size = (long)zf.GetLocalFile(i).UncompressedSize;
zipEntryRom.CRC = zf.GetLocalFile(i).CRC;
@@ -282,7 +282,7 @@ namespace SabreTools.FileTypes.Archives
// Otherwise, use the stream directly
else
{
zipEntryRom = GetInfo(readStream, size: (long)zf.GetLocalFile(i).UncompressedSize, hashes: AvailableHashTypes, keepReadOpen: true);
zipEntryRom = GetInfo(readStream, size: (long)zf.GetLocalFile(i).UncompressedSize, hashes: _hashTypes, keepReadOpen: true);
}
// Fill in common details and add to the list
@@ -297,7 +297,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
return null;
}
@@ -347,7 +347,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
}
return empties;
@@ -560,7 +560,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
success = false;
}
finally
@@ -774,7 +774,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
success = false;
}

View File

@@ -68,16 +68,16 @@ namespace SabreTools.FileTypes.Archives
catch (EndOfStreamException ex)
{
// Catch this but don't count it as an error because SharpCompress is unsafe
logger.Verbose(ex);
_logger.Verbose(ex);
}
catch (InvalidOperationException ex)
{
logger.Warning(ex);
_logger.Warning(ex);
encounteredErrors = true;
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
encounteredErrors = true;
}
@@ -165,7 +165,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
return (null, null);
}
#else
@@ -194,7 +194,7 @@ namespace SabreTools.FileTypes.Archives
BaseFile tarEntryRom = new();
// Perform a quickscan, if flagged to
if (AvailableHashTypes.Length == 1 && AvailableHashTypes[0] == HashType.CRC32)
if (_hashTypes.Length == 1 && _hashTypes[0] == HashType.CRC32)
{
tarEntryRom.Size = entry.Size;
tarEntryRom.CRC = BitConverter.GetBytes(entry.Crc);
@@ -203,7 +203,7 @@ namespace SabreTools.FileTypes.Archives
else
{
using Stream entryStream = entry.OpenEntryStream();
tarEntryRom = GetInfo(entryStream, size: entry.Size, hashes: AvailableHashTypes);
tarEntryRom = GetInfo(entryStream, size: entry.Size, hashes: _hashTypes);
}
// Fill in common details and add to the list
@@ -218,7 +218,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
return null;
}
@@ -260,7 +260,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
}
return empties;
@@ -409,7 +409,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
success = false;
}
finally
@@ -578,7 +578,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
success = false;
}
finally

View File

@@ -80,16 +80,16 @@ namespace SabreTools.FileTypes.Archives
catch (EndOfStreamException ex)
{
// Catch this but don't count it as an error because SharpCompress is unsafe
logger.Verbose(ex);
_logger.Verbose(ex);
}
catch (InvalidOperationException ex)
{
logger.Warning(ex);
_logger.Warning(ex);
encounteredErrors = true;
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
encounteredErrors = true;
}
@@ -161,7 +161,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
return (null, null);
}
#else
@@ -200,7 +200,7 @@ namespace SabreTools.FileTypes.Archives
BaseFile xzEntryRom = new();
// Perform a quickscan, if flagged to
if (AvailableHashTypes.Length == 1 && AvailableHashTypes[0] == HashType.CRC32)
if (_hashTypes.Length == 1 && _hashTypes[0] == HashType.CRC32)
{
xzEntryRom.Filename = gamename;
@@ -213,7 +213,7 @@ namespace SabreTools.FileTypes.Archives
else
{
var xzStream = new XZStream(File.OpenRead(Filename!));
xzEntryRom = GetInfo(xzStream, hashes: AvailableHashTypes);
xzEntryRom = GetInfo(xzStream, hashes: _hashTypes);
xzEntryRom.Filename = gamename;
xzStream.Dispose();
}
@@ -224,7 +224,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
return null;
}
@@ -254,7 +254,7 @@ namespace SabreTools.FileTypes.Archives
// Check if the name is the right length
if (!Regex.IsMatch(datum, @"^[0-9a-f]{" + Constants.SHA1Length + @"}\.xz"))
{
logger.Warning($"Non SHA-1 filename found, skipping: '{Path.GetFullPath(Filename)}'");
_logger.Warning($"Non SHA-1 filename found, skipping: '{Path.GetFullPath(Filename)}'");
return false;
}
@@ -276,7 +276,7 @@ namespace SabreTools.FileTypes.Archives
// Check if the name is the right length
if (!Regex.IsMatch(datum, @"^[0-9a-f]{" + Constants.SHA1Length + @"}\.xz"))
{
logger.Warning($"Non SHA-1 filename found, skipping: '{Path.GetFullPath(Filename)}'");
_logger.Warning($"Non SHA-1 filename found, skipping: '{Path.GetFullPath(Filename)}'");
return null;
}
@@ -301,7 +301,7 @@ namespace SabreTools.FileTypes.Archives
// Check that the input file exists
if (!File.Exists(inputFile))
{
logger.Warning($"File '{inputFile}' does not exist!");
_logger.Warning($"File '{inputFile}' does not exist!");
return false;
}

View File

@@ -157,16 +157,16 @@ namespace SabreTools.FileTypes.Archives
catch (EndOfStreamException ex)
{
// Catch this but don't count it as an error because SharpCompress is unsafe
logger.Verbose(ex);
_logger.Verbose(ex);
}
catch (InvalidOperationException ex)
{
logger.Warning(ex);
_logger.Warning(ex);
encounteredErrors = true;
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
encounteredErrors = true;
}
@@ -296,7 +296,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
return (null, null);
}
}
@@ -345,7 +345,7 @@ namespace SabreTools.FileTypes.Archives
// If we get a read error, log it and continue
if (zr != ZipReturn.ZipGood || readStream == null)
{
logger.Warning($"An error occurred while reading archive {Filename}: Zip Error - {zr}");
_logger.Warning($"An error occurred while reading archive {Filename}: Zip Error - {zr}");
continue;
}
@@ -353,7 +353,7 @@ namespace SabreTools.FileTypes.Archives
var zipEntryRom = new BaseFile();
// Perform a quickscan, if flagged to
if (AvailableHashTypes.Length == 1 && AvailableHashTypes[0] == HashType.CRC32)
if (_hashTypes.Length == 1 && _hashTypes[0] == HashType.CRC32)
{
zipEntryRom.Size = (long)localFile.UncompressedSize;
zipEntryRom.CRC = localFile.CRC;
@@ -363,7 +363,7 @@ namespace SabreTools.FileTypes.Archives
{
zipEntryRom = GetInfo(readStream,
size: (long)localFile.UncompressedSize,
hashes: AvailableHashTypes,
hashes: _hashTypes,
keepReadOpen: true);
}
@@ -406,7 +406,7 @@ namespace SabreTools.FileTypes.Archives
var zipEntryRom = new BaseFile();
// Perform a quickscan, if flagged to
if (AvailableHashTypes.Length == 1 && AvailableHashTypes[0] == HashType.CRC32)
if (_hashTypes.Length == 1 && _hashTypes[0] == HashType.CRC32)
{
zipEntryRom.Size = localFile.Length;
#if NETCOREAPP
@@ -420,7 +420,7 @@ namespace SabreTools.FileTypes.Archives
{
zipEntryRom = GetInfo(readStream,
size: localFile.Length,
hashes: AvailableHashTypes,
hashes: _hashTypes,
keepReadOpen: false);
}
@@ -437,7 +437,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
return null;
}
@@ -532,7 +532,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
}
return empties;
@@ -734,7 +734,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
success = false;
}
finally
@@ -949,7 +949,7 @@ namespace SabreTools.FileTypes.Archives
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
success = false;
}

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.IO;
using SabreTools.FileTypes.Archives;
namespace SabreTools.FileTypes
{
@@ -29,84 +28,13 @@ namespace SabreTools.FileTypes
/// <summary>
/// Create a new Archive with no base file
/// </summary>
public BaseArchive()
{
}
public BaseArchive() : base() { }
/// <summary>
/// Create a new BaseArchive from the given file
/// </summary>
/// <param name="filename">Name of the file to use</param>
public BaseArchive(string filename)
: base(filename)
{
}
/// <summary>
/// Create an archive object from a filename, if possible
/// </summary>
/// <param name="input">Name of the file to create the archive from</param>
/// <returns>Archive object representing the inputs</returns>
public static BaseArchive? Create(string input)
{
BaseArchive? archive = null;
// First get the archive type
FileType? at = GetFileType(input);
// If we got back null, then it's not an archive, so we we return
if (at == null)
return archive;
// Create the archive based on the type
staticLogger.Verbose($"Found archive of type: {at}");
switch (at)
{
case FileType.GZipArchive:
archive = new GZipArchive(input);
break;
case FileType.RarArchive:
archive = new RarArchive(input);
break;
case FileType.SevenZipArchive:
archive = new SevenZipArchive(input);
break;
case FileType.TapeArchive:
archive = new TapeArchive(input);
break;
case FileType.ZipArchive:
archive = new ZipArchive(input);
break;
default:
// We ignore all other types for now
break;
}
return archive;
}
/// <summary>
/// Create an archive object of the specified type, if possible
/// </summary>
/// <param name="archiveType">SharpCompress.Common.ArchiveType representing the archive to create</param>
/// <returns>Archive object representing the inputs</returns>
public static BaseArchive? Create(FileType archiveType)
{
return archiveType switch
{
FileType.GZipArchive => new GZipArchive(),
FileType.RarArchive => new RarArchive(),
FileType.SevenZipArchive => new SevenZipArchive(),
FileType.TapeArchive => new TapeArchive(),
FileType.ZipArchive => new ZipArchive(),
_ => null,
};
}
public BaseArchive(string filename) : base(filename) { }
#endregion

View File

@@ -1,29 +1,7 @@
using System.IO;
using SabreTools.FileTypes.Aaru;
using SabreTools.FileTypes.CHD;
using SabreTools.Hashing;
using SabreTools.IO.Extensions;
using SabreTools.Matching;
using SabreTools.Skippers;
namespace SabreTools.FileTypes
namespace SabreTools.FileTypes
{
public class BaseFile
{
#region Constants
protected static readonly byte[] SevenZipSignature = [0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c];
protected static readonly byte[] AaruFormatSignature = [0x41, 0x41, 0x52, 0x55, 0x46, 0x52, 0x4d, 0x54];
protected static readonly byte[] CHDSignature = [0x4d, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x48, 0x44];
protected static readonly byte[] GzSignature = [0x1f, 0x8b, 0x08];
protected static readonly byte[] RarSignature = [0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00];
protected static readonly byte[] RarFiveSignature = [0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x01, 0x00];
protected static readonly byte[] TarSignature = [0x75, 0x73, 0x74, 0x61, 0x72, 0x20, 0x20, 0x00];
protected static readonly byte[] TarZeroSignature = [0x75, 0x73, 0x74, 0x61, 0x72, 0x00, 0x30, 0x30];
protected static readonly byte[] XZSignature = [0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00];
#endregion
#region Fields
/// <summary>
@@ -46,56 +24,47 @@ namespace SabreTools.FileTypes
/// </summary>
public long? Size { get; set; }
/// <summary>
/// Hashes that are available for the file
/// </summary>
public HashType[] AvailableHashTypes { get; set; } = [HashType.CRC32, HashType.MD5, HashType.SHA1];
/// <summary>
/// CRC32 hash of the file
/// </summary>
public byte[]? CRC { get; set; } = null;
public byte[]? CRC { get; set; }
/// <summary>
/// MD5 hash of the file
/// </summary>
public byte[]? MD5 { get; set; } = null;
public byte[]? MD5 { get; set; }
/// <summary>
/// SHA-1 hash of the file
/// </summary>
public byte[]? SHA1 { get; set; } = null;
public byte[]? SHA1 { get; set; }
/// <summary>
/// SHA-256 hash of the file
/// </summary>
public byte[]? SHA256 { get; set; } = null;
public byte[]? SHA256 { get; set; }
/// <summary>
/// SHA-384 hash of the file
/// </summary>
public byte[]? SHA384 { get; set; } = null;
public byte[]? SHA384 { get; set; }
/// <summary>
/// SHA-512 hash of the file
/// </summary>
public byte[]? SHA512 { get; set; } = null;
public byte[]? SHA512 { get; set; }
/// <summary>
/// SpamSum fuzzy hash of the file
/// </summary>
public byte[]? SpamSum { get; set; } = null;
public byte[]? SpamSum { get; set; }
#endregion
#region Construtors
/// <summary>
/// Create a new BaseFile with no base file
/// </summary>
public BaseFile()
{
}
public BaseFile() { }
/// <summary>
/// Create a new BaseFile from the given file
@@ -105,235 +74,5 @@ namespace SabreTools.FileTypes
{
Filename = filename;
}
#endregion
#region Static Methods
/// <summary>
/// Returns the file type of an input file
/// </summary>
/// <param name="input">Input file to check</param>
/// <returns>FileType of inputted file (null on error)</returns>
public static FileType? GetFileType(string input)
{
FileType? outFileType = null;
// If the file is null, then we have no archive type
if (input == null)
return outFileType;
// First line of defense is going to be the extension, for better or worse
if (!HasValidArchiveExtension(input))
return outFileType;
// Read the first bytes of the file and get the magic number
BinaryReader br = new(File.OpenRead(input));
byte[] magic = br.ReadBytes(8);
#if NET40_OR_GREATER
br.Dispose();
#endif
// Now try to match it to a known signature
if (magic.StartsWith(SevenZipSignature))
{
outFileType = FileType.SevenZipArchive;
}
else if (magic.StartsWith(AaruFormatSignature))
{
outFileType = FileType.AaruFormat;
}
else if (magic.StartsWith(CHDSignature))
{
outFileType = FileType.CHD;
}
else if (magic.StartsWith(GzSignature))
{
outFileType = FileType.GZipArchive;
}
else if (magic.StartsWith(RarSignature)
|| magic.StartsWith(RarFiveSignature))
{
outFileType = FileType.RarArchive;
}
else if (magic.StartsWith(TarSignature)
|| magic.StartsWith(TarZeroSignature))
{
outFileType = FileType.TapeArchive;
}
else if (magic.StartsWith(XZSignature))
{
outFileType = FileType.XZArchive;
}
else if (magic.StartsWith(Models.PKZIP.Constants.LocalFileHeaderSignatureBytes)
|| magic.StartsWith(Models.PKZIP.Constants.EndOfCentralDirectoryRecordSignatureBytes)
|| magic.StartsWith(Models.PKZIP.Constants.DataDescriptorSignatureBytes))
{
outFileType = FileType.ZipArchive;
}
return outFileType;
}
/// <summary>
/// Retrieve file information for a single file
/// </summary>
/// <param name="input">Filename to get information from</param>
/// <param name="header">Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise</param>
/// <param name="hashes">Hashes to include in the information</param>
/// <param name="asFiles">TreatAsFiles representing special format scanning</param>
/// <returns>Populated BaseFile object if success, empty one on error</returns>
public static BaseFile? GetInfo(string input, string? header = null, HashType[]? hashes = null, TreatAsFile asFiles = 0x00)
{
// Add safeguard if file doesn't exist
if (!File.Exists(input))
return null;
// If no hashes are set, use the standard array
hashes ??= [HashType.CRC32, HashType.MD5, HashType.SHA1];
// Get input information
var fileType = GetFileType(input);
Stream inputStream = File.OpenRead(input);
// Try to match the supplied header skipper
if (header != null)
{
SkipperMatch.Init();
var rule = SkipperMatch.GetMatchingRule(input, Path.GetFileNameWithoutExtension(header));
// If there's a match, transform the stream before getting info
if (rule.Tests != null && rule.Tests.Length != 0)
{
// Create the output stream
MemoryStream outputStream = new();
// Transform the stream and get the information from it
rule.TransformStream(inputStream, outputStream, keepReadOpen: false, keepWriteOpen: true);
inputStream = outputStream;
}
}
// Get the info in the proper manner
BaseFile? baseFile;
#if NET20 || NET35
if (fileType == FileType.AaruFormat && (asFiles & TreatAsFile.AaruFormat) == 0)
baseFile = AaruFormat.Create(inputStream);
else if (fileType == FileType.CHD && (asFiles & TreatAsFile.CHD) == 0)
baseFile = CHDFile.Create(inputStream);
#else
if (fileType == FileType.AaruFormat && !asFiles.HasFlag(TreatAsFile.AaruFormat))
baseFile = AaruFormat.Create(inputStream);
else if (fileType == FileType.CHD && !asFiles.HasFlag(TreatAsFile.CHD))
baseFile = CHDFile.Create(inputStream);
#endif
else
baseFile = GetInfo(inputStream, hashes: hashes, keepReadOpen: false);
// Dispose of the input stream
inputStream?.Dispose();
// Add unique data from the file
baseFile!.Filename = Path.GetFileName(input);
baseFile.Date = new FileInfo(input).LastWriteTime.ToString("yyyy/MM/dd HH:mm:ss");
return baseFile;
}
/// <summary>
/// Retrieve file information for a single file
/// </summary>
/// <param name="input">Filename to get information from</param>
/// <param name="size">Size of the input stream</param>
/// <param name="hashes">Hashes to include in the information</param>
/// <param name="keepReadOpen">True if the underlying read stream should be kept open, false otherwise</param>
/// <returns>Populated BaseFile object if success, empty one on error</returns>
public static BaseFile GetInfo(Stream? input, long size = -1, HashType[]? hashes = null, bool keepReadOpen = false)
{
// If we have no stream
if (input == null)
return new BaseFile();
// If no hashes are set, use the standard array
hashes ??= [HashType.CRC32, HashType.MD5, HashType.SHA1];
// If we want to automatically set the size
if (size == -1)
size = input.Length;
// Run the hashing on the input stream
var hashDict = HashTool.GetStreamHashes(input, hashes);
if (hashDict == null)
return new BaseFile();
// Create a base file with the resulting hashes
var baseFile = new BaseFile()
{
Size = size,
CRC = hashDict.ContainsKey(HashType.CRC32) ? hashDict[HashType.CRC32].FromHexString() : null,
MD5 = hashDict.ContainsKey(HashType.MD5) ? hashDict[HashType.MD5].FromHexString() : null,
SHA1 = hashDict.ContainsKey(HashType.SHA1) ? hashDict[HashType.SHA1].FromHexString() : null,
SHA256 = hashDict.ContainsKey(HashType.SHA256) ? hashDict[HashType.SHA256].FromHexString() : null,
SHA384 = hashDict.ContainsKey(HashType.SHA384) ? hashDict[HashType.SHA384].FromHexString() : null,
SHA512 = hashDict.ContainsKey(HashType.SHA512) ? hashDict[HashType.SHA512].FromHexString() : null,
SpamSum = hashDict.ContainsKey(HashType.SpamSum) ? hashDict[HashType.SpamSum].FromHexString() : null,
};
// Deal with the input stream
if (!keepReadOpen)
{
input.Close();
input.Dispose();
}
else
{
input.SeekIfPossible();
}
return baseFile;
}
/// <summary>
/// Get if the given path has a valid DAT extension
/// </summary>
/// <param name="path">Path to check</param>
/// <returns>True if the extension is valid, false otherwise</returns>
private static bool HasValidArchiveExtension(string path)
{
// Get the extension from the path, if possible
string? ext = path.GetNormalizedExtension();
// Check against the list of known archive extensions
return ext switch
{
// Aaruformat
"aaru" => true,
"aaruf" => true,
"aaruformat" => true,
"aif" => true,
"dicf" => true,
// Archive
"7z" => true,
"gz" => true,
"lzma" => true,
"rar" => true,
"rev" => true,
"r00" => true,
"r01" => true,
"tar" => true,
"tgz" => true,
"tlz" => true,
"zip" => true,
"zipx" => true,
// CHD
"chd" => true,
_ => false,
};
}
#endregion
}
}

View File

@@ -0,0 +1,303 @@
using System;
using System.IO;
using SabreTools.FileTypes.Aaru;
using SabreTools.FileTypes.Archives;
using SabreTools.FileTypes.CHD;
using SabreTools.Hashing;
using SabreTools.IO.Extensions;
using SabreTools.Matching;
using SabreTools.Skippers;
namespace SabreTools.FileTypes
{
public static class FileTypeTool
{
#region Constants
private static readonly byte[] SevenZipSignature = [0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c];
private static readonly byte[] AaruFormatSignature = [0x41, 0x41, 0x52, 0x55, 0x46, 0x52, 0x4d, 0x54];
private static readonly byte[] CHDSignature = [0x4d, 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x48, 0x44];
private static readonly byte[] GzSignature = [0x1f, 0x8b, 0x08];
private static readonly byte[] RarSignature = [0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00];
private static readonly byte[] RarFiveSignature = [0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x01, 0x00];
private static readonly byte[] TarSignature = [0x75, 0x73, 0x74, 0x61, 0x72, 0x20, 0x20, 0x00];
private static readonly byte[] TarZeroSignature = [0x75, 0x73, 0x74, 0x61, 0x72, 0x00, 0x30, 0x30];
private static readonly byte[] XZSignature = [0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00];
#endregion
#region File Info
/// <summary>
/// Retrieve file information for a single file
/// </summary>
/// <param name="input">Filename to get information from</param>
/// <param name="header">Populated string representing the name of the skipper to use, a blank string to use the first available checker, null otherwise</param>
/// <param name="hashes">Hashes to include in the information</param>
/// <param name="asFiles">TreatAsFiles representing special format scanning</param>
/// <returns>Populated BaseFile object if success, empty one on error</returns>
public static BaseFile? GetInfo(string input, string? header = null, HashType[]? hashes = null, TreatAsFile asFiles = 0x00)
{
// Add safeguard if file doesn't exist
if (!File.Exists(input))
return null;
// If no hashes are set, use the standard array
hashes ??= [HashType.CRC32, HashType.MD5, HashType.SHA1];
// Get input information
var fileType = GetFileType(input);
Stream inputStream = File.OpenRead(input);
// Try to match the supplied header skipper
if (header != null)
{
SkipperMatch.Init();
var rule = SkipperMatch.GetMatchingRule(input, Path.GetFileNameWithoutExtension(header));
// If there's a match, transform the stream before getting info
if (rule.Tests != null && rule.Tests.Length != 0)
{
// Create the output stream
MemoryStream outputStream = new();
// Transform the stream and get the information from it
rule.TransformStream(inputStream, outputStream, keepReadOpen: false, keepWriteOpen: true);
inputStream = outputStream;
}
}
// Get the info in the proper manner
BaseFile? baseFile;
#if NET20 || NET35
if (fileType == FileType.AaruFormat && (asFiles & TreatAsFile.AaruFormat) == 0)
baseFile = AaruFormat.Create(inputStream);
else if (fileType == FileType.CHD && (asFiles & TreatAsFile.CHD) == 0)
baseFile = CHDFile.Create(inputStream);
#else
if (fileType == FileType.AaruFormat && !asFiles.HasFlag(TreatAsFile.AaruFormat))
baseFile = AaruFormat.Create(inputStream);
else if (fileType == FileType.CHD && !asFiles.HasFlag(TreatAsFile.CHD))
baseFile = CHDFile.Create(inputStream);
#endif
else
baseFile = GetInfo(inputStream, hashes: hashes, keepReadOpen: false);
// Dispose of the input stream
inputStream?.Dispose();
// Add unique data from the file
baseFile!.Filename = Path.GetFileName(input);
baseFile.Date = new FileInfo(input).LastWriteTime.ToString("yyyy/MM/dd HH:mm:ss");
return baseFile;
}
/// <summary>
/// Retrieve file information for a single file
/// </summary>
/// <param name="input">Filename to get information from</param>
/// <param name="size">Size of the input stream</param>
/// <param name="hashes">Hashes to include in the information</param>
/// <param name="keepReadOpen">True if the underlying read stream should be kept open, false otherwise</param>
/// <returns>Populated BaseFile object if success, empty one on error</returns>
public static BaseFile GetInfo(Stream? input, long size = -1, HashType[]? hashes = null, bool keepReadOpen = false)
{
// If we have no stream
if (input == null)
return new BaseFile();
// If no hashes are set, use the standard array
hashes ??= [HashType.CRC32, HashType.MD5, HashType.SHA1];
// If we want to automatically set the size
if (size == -1)
size = input.Length;
// Run the hashing on the input stream
var hashDict = HashTool.GetStreamHashes(input, hashes);
if (hashDict == null)
return new BaseFile();
// Create a base file with the resulting hashes
var baseFile = new BaseFile()
{
Size = size,
CRC = hashDict.ContainsKey(HashType.CRC32) ? hashDict[HashType.CRC32].FromHexString() : null,
MD5 = hashDict.ContainsKey(HashType.MD5) ? hashDict[HashType.MD5].FromHexString() : null,
SHA1 = hashDict.ContainsKey(HashType.SHA1) ? hashDict[HashType.SHA1].FromHexString() : null,
SHA256 = hashDict.ContainsKey(HashType.SHA256) ? hashDict[HashType.SHA256].FromHexString() : null,
SHA384 = hashDict.ContainsKey(HashType.SHA384) ? hashDict[HashType.SHA384].FromHexString() : null,
SHA512 = hashDict.ContainsKey(HashType.SHA512) ? hashDict[HashType.SHA512].FromHexString() : null,
SpamSum = hashDict.ContainsKey(HashType.SpamSum) ? hashDict[HashType.SpamSum].FromHexString() : null,
};
// Deal with the input stream
if (!keepReadOpen)
{
input.Close();
input.Dispose();
}
else
{
input.SeekIfPossible();
}
return baseFile;
}
#endregion
#region File Type
/// <summary>
/// Create an archive object from a filename, if possible
/// </summary>
/// <param name="input">Name of the file to create the archive from</param>
/// <returns>Archive object representing the inputs</returns>
public static BaseArchive? CreateArchiveType(string input)
{
FileType? fileType = GetFileType(input);
return fileType switch
{
FileType.GZipArchive => new GZipArchive(input),
FileType.RarArchive => new RarArchive(input),
FileType.SevenZipArchive => new SevenZipArchive(input),
FileType.TapeArchive => new TapeArchive(input),
FileType.ZipArchive => new ZipArchive(input),
_ => null,
};
}
/// <summary>
/// Create an Folder object of the specified type, if possible
/// </summary>
/// <param name="outputFormat">OutputFormat representing the archive to create</param>
/// <returns>Archive object representing the inputs</returns>
public static Folder? CreateFolderType(OutputFormat outputFormat)
{
return outputFormat switch
{
OutputFormat.Folder => new Folder(false),
OutputFormat.ParentFolder => new Folder(true),
OutputFormat.TapeArchive => new TapeArchive(),
OutputFormat.Torrent7Zip => new SevenZipArchive(),
OutputFormat.TorrentGzip => new GZipArchive(),
OutputFormat.TorrentGzipRomba => new GZipArchive(),
OutputFormat.TorrentRar => new RarArchive(),
OutputFormat.TorrentXZ => new XZArchive(),
OutputFormat.TorrentXZRomba => new XZArchive(),
OutputFormat.TorrentZip => new ZipArchive(),
_ => null,
};
}
/// <summary>
/// Returns the file type of an input file
/// </summary>
/// <param name="input">Input file to check</param>
/// <returns>FileType of inputted file (null on error)</returns>
public static FileType? GetFileType(string input)
{
FileType? outFileType = null;
// If the file is null, then we have no archive type
if (input == null)
return outFileType;
// First line of defense is going to be the extension, for better or worse
if (!HasValidArchiveExtension(input))
return outFileType;
// Read the first bytes of the file and get the magic number
BinaryReader br = new(File.OpenRead(input));
byte[] magic = br.ReadBytes(8);
#if NET40_OR_GREATER
br.Dispose();
#endif
// Now try to match it to a known signature
if (magic.StartsWith(SevenZipSignature))
{
outFileType = FileType.SevenZipArchive;
}
else if (magic.StartsWith(AaruFormatSignature))
{
outFileType = FileType.AaruFormat;
}
else if (magic.StartsWith(CHDSignature))
{
outFileType = FileType.CHD;
}
else if (magic.StartsWith(GzSignature))
{
outFileType = FileType.GZipArchive;
}
else if (magic.StartsWith(RarSignature)
|| magic.StartsWith(RarFiveSignature))
{
outFileType = FileType.RarArchive;
}
else if (magic.StartsWith(TarSignature)
|| magic.StartsWith(TarZeroSignature))
{
outFileType = FileType.TapeArchive;
}
else if (magic.StartsWith(XZSignature))
{
outFileType = FileType.XZArchive;
}
else if (magic.StartsWith(Models.PKZIP.Constants.LocalFileHeaderSignatureBytes)
|| magic.StartsWith(Models.PKZIP.Constants.EndOfCentralDirectoryRecordSignatureBytes)
|| magic.StartsWith(Models.PKZIP.Constants.DataDescriptorSignatureBytes))
{
outFileType = FileType.ZipArchive;
}
return outFileType;
}
/// <summary>
/// Get if the given path has a valid DAT extension
/// </summary>
/// <param name="path">Path to check</param>
/// <returns>True if the extension is valid, false otherwise</returns>
private static bool HasValidArchiveExtension(string path)
{
// Get the extension from the path, if possible
string? ext = path.GetNormalizedExtension();
// Check against the list of known archive extensions
return ext switch
{
// Aaruformat
"aaru" => true,
"aaruf" => true,
"aaruformat" => true,
"aif" => true,
"dicf" => true,
// Archive
"7z" => true,
"gz" => true,
"lzma" => true,
"rar" => true,
"rev" => true,
"r00" => true,
"r01" => true,
"tar" => true,
"tgz" => true,
"tlz" => true,
"zip" => true,
"zipx" => true,
// CHD
"chd" => true,
_ => false,
};
}
#endregion
}
}

View File

@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.IO;
using SabreTools.Core.Tools;
using SabreTools.FileTypes.Archives;
using SabreTools.Hashing;
using SabreTools.IO;
using SabreTools.IO.Extensions;
using SabreTools.IO.Logging;
@@ -16,17 +16,25 @@ namespace SabreTools.FileTypes
{
#region Protected instance variables
/// <summary>
/// Hashes that are available for children
/// </summary>
protected HashType[] _hashTypes = [HashType.CRC32, HashType.MD5, HashType.SHA1];
/// <summary>
/// Set of children file objects
/// </summary>
protected List<BaseFile>? _children;
/// <summary>
/// Logging object
/// </summary>
protected Logger logger;
protected Logger _logger;
/// <summary>
/// Static logger for static methods
/// </summary>
protected static Logger staticLogger = new();
protected static Logger _staticLogger = new();
/// <summary>
/// Flag specific to Folder to omit Machine name from output path
@@ -41,11 +49,10 @@ namespace SabreTools.FileTypes
/// Create a new Folder with no base file
/// </summary>
/// <param name="writeToParent">True to write directly to parent, false otherwise</param>
public Folder(bool writeToParent = false)
: base()
public Folder(bool writeToParent = false) : base()
{
_writeToParent = writeToParent;
logger = new Logger(this);
_logger = new Logger(this);
}
/// <summary>
@@ -53,34 +60,10 @@ namespace SabreTools.FileTypes
/// </summary>
/// <param name="filename">Name of the folder to use</param>
/// <param name="writeToParent">True to write directly to parent, false otherwise</param>
public Folder(string filename, bool writeToParent = false)
: base(filename)
public Folder(string filename, bool writeToParent = false) : base(filename)
{
_writeToParent = writeToParent;
logger = new Logger(this);
}
/// <summary>
/// Create an folder object of the specified type, if possible
/// </summary>
/// <param name="outputFormat">OutputFormat representing the archive to create</param>
/// <returns>Archive object representing the inputs</returns>
public static Folder? Create(OutputFormat outputFormat)
{
return outputFormat switch
{
OutputFormat.Folder => new Folder(false),
OutputFormat.ParentFolder => new Folder(true),
OutputFormat.TapeArchive => new TapeArchive(),
OutputFormat.Torrent7Zip => new SevenZipArchive(),
OutputFormat.TorrentGzip => new GZipArchive(),
OutputFormat.TorrentGzipRomba => new GZipArchive(),
OutputFormat.TorrentRar => new RarArchive(),
OutputFormat.TorrentXZ => new XZArchive(),
OutputFormat.TorrentXZRomba => new XZArchive(),
OutputFormat.TorrentZip => new ZipArchive(),
_ => null,
};
_logger = new Logger(this);
}
#endregion
@@ -109,7 +92,7 @@ namespace SabreTools.FileTypes
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
return false;
}
@@ -191,7 +174,7 @@ namespace SabreTools.FileTypes
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
return realentry;
}
@@ -232,7 +215,7 @@ namespace SabreTools.FileTypes
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
return (null, null);
}
}
@@ -241,6 +224,18 @@ namespace SabreTools.FileTypes
#region Information
/// <summary>
/// Set the hash type that can be included in children
/// </summary>
public void SetHashType(HashType hashType)
=> SetHashTypes([hashType]);
/// <summary>
/// Set the hash types that can be included in children
/// </summary>
public void SetHashTypes(HashType[] hashTypes)
=> _hashTypes = hashTypes;
/// <summary>
/// Generate a list of immediate children from the current folder
/// </summary>
@@ -260,7 +255,7 @@ namespace SabreTools.FileTypes
foreach (string file in Directory.EnumerateFiles(Filename, "*", SearchOption.TopDirectoryOnly))
#endif
{
BaseFile? nf = GetInfo(file, hashes: AvailableHashTypes);
BaseFile? nf = FileTypeTool.GetInfo(file, hashes: _hashTypes);
if (nf != null)
_children.Add(nf);
}
@@ -384,7 +379,7 @@ namespace SabreTools.FileTypes
}
catch (Exception ex)
{
logger.Error(ex);
_logger.Error(ex);
return false;
}
finally