using System; using System.Collections.Generic; using System.Linq; using SabreTools.Library.Data; using SabreTools.Library.DatItems; using SabreTools.Library.Tools; #if MONO using System.IO; using Directory = Alphaleonis.Win32.Filesystem.Directory; using PathFormat = Alphaleonis.Win32.Filesystem.PathFormat; #else using Alphaleonis.Win32.Filesystem; using BinaryReader = System.IO.BinaryReader; using FileStream = System.IO.FileStream; using MemoryStream = System.IO.MemoryStream; using SearchOption = System.IO.SearchOption; using SeekOrigin = System.IO.SeekOrigin; using Stream = System.IO.Stream; #endif namespace SabreTools.Library.FileTypes { /// /// Represents a folder for reading and writing /// public class Folder : BaseFile { #region Protected instance variables protected List _children; #endregion #region Constructors /// /// Create a new folder with no base file /// public Folder() : base() { _fileType = FileType.Folder; } /// /// Create a new folder from the given file /// /// Name of the file to use as an archive /// True for opening file as read, false for opening file as write public Folder(string filename) : base(filename) { _fileType = FileType.Folder; } #endregion #region Extraction /// /// Attempt to extract a file as an archive /// /// Output directory for archive extraction /// True if the extraction was a success, false otherwise public virtual bool CopyAll(string outDir) { // Copy all files from the current folder to the output directory recursively try { // Make sure the folders exist Directory.CreateDirectory(_filename); Directory.CreateDirectory(outDir); Directory.Copy(_filename, outDir, true, PathFormat.FullPath); } catch (Exception ex) { Globals.Logger.Error(ex.ToString()); return false; } return true; } /// /// Attempt to extract a file from an archive /// /// Name of the entry to be extracted /// Output directory for archive extraction /// Name of the extracted file, null on error public virtual string CopyToFile(string entryName, string outDir) { string realentry = null; // Copy single file from the current folder to the output directory, if exists try { // Make sure the folders exist Directory.CreateDirectory(_filename); Directory.CreateDirectory(outDir); // Get all files from the input directory List files = Utilities.RetrieveFiles(_filename, new List()); // Now sort through to find the first file that matches string match = files.Where(s => s.EndsWith(entryName)).FirstOrDefault(); // If we had a file, copy that over to the new name if (!String.IsNullOrWhiteSpace(match)) { realentry = match; File.Copy(match, Path.Combine(outDir, entryName)); } } catch (Exception ex) { Globals.Logger.Error(ex.ToString()); return realentry; } return realentry; } /// /// Attempt to extract a stream from an archive /// /// Name of the entry to be extracted /// Output representing the entry name that was found /// MemoryStream representing the entry, null on error public virtual (MemoryStream, string) CopyToStream(string entryName) { MemoryStream ms = new MemoryStream(); string realentry = null; // Copy single file from the current folder to the output directory, if exists try { // Make sure the folders exist Directory.CreateDirectory(_filename); // Get all files from the input directory List files = Utilities.RetrieveFiles(_filename, new List()); // Now sort through to find the first file that matches string match = files.Where(s => s.EndsWith(entryName)).FirstOrDefault(); // If we had a file, copy that over to the new name if (!String.IsNullOrWhiteSpace(match)) { Utilities.TryOpenRead(match).CopyTo(ms); realentry = match; } } catch (Exception ex) { Globals.Logger.Error(ex.ToString()); return (ms, realentry); } return (ms, realentry); } #endregion #region Information /// /// Generate a list of immediate children from the current folder /// /// Hash representing the hashes that should be skipped /// True if entry dates should be included, false otherwise (default) /// List of BaseFile objects representing the found data /// TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually public virtual List GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false) { if (_children == null || _children.Count == 0) { _children = new List(); foreach (string file in Directory.EnumerateFiles(_filename, "*", SearchOption.TopDirectoryOnly)) { BaseFile nf = Utilities.GetFileInfo(file, omitFromScan: omitFromScan, date: date); _children.Add(nf); } foreach (string dir in Directory.EnumerateDirectories(_filename, "*", SearchOption.TopDirectoryOnly)) { Folder fl = new Folder(dir); _children.Add(fl); } } return _children; } /// /// Generate a list of empty folders in an archive /// /// Input file to get data from /// List of empty folders in the folder public virtual List GetEmptyFolders() { return Utilities.GetEmptyDirectories(_filename).ToList(); } #endregion #region Writing /// /// Write an input file to an output folder /// /// Input filename to be moved /// Output directory to build to /// DatItem representing the new information /// True if the date from the DAT should be used if available, false otherwise (default) /// True if files should be output in Romba depot folders, false otherwise /// True if the write was a success, false otherwise /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code. public virtual bool Write(string inputFile, string outDir, Rom rom, bool date = false, bool romba = false) { throw new NotImplementedException(); } /// /// Write an input stream to an output folder /// /// Input stream to be moved /// Output directory to build to /// DatItem representing the new information /// True if the date from the DAT should be used if available, false otherwise (default) /// True if files should be output in Romba depot folders, false otherwise /// True if the write was a success, false otherwise /// This works for now, but it can be sped up by using Ionic.Zip or another zlib wrapper that allows for header values built-in. See edc's code. public virtual bool Write(Stream inputStream, string outDir, Rom rom, bool date = false, bool romba = false) { bool success = false; // If either input is null or empty, return if (inputStream == null || rom == null || rom.Name == null) { return success; } // If the stream is not readable, return if (!inputStream.CanRead) { return success; } // Set internal variables FileStream outputStream = null; // Get the output folder name from the first rebuild rom string fileName = Path.Combine(outDir, Utilities.RemovePathUnsafeCharacters(rom.MachineName), Utilities.RemovePathUnsafeCharacters(rom.Name)); try { // If the full output path doesn't exist, create it if (!Directory.Exists(Path.GetDirectoryName(fileName))) { Directory.CreateDirectory(Path.GetDirectoryName(fileName)); } // Overwrite output files by default outputStream = Utilities.TryCreate(fileName); // If the output stream isn't null if (outputStream != null) { // Copy the input stream to the output inputStream.Seek(0, SeekOrigin.Begin); int bufferSize = 4096 * 128; byte[] ibuffer = new byte[bufferSize]; int ilen; while ((ilen = inputStream.Read(ibuffer, 0, bufferSize)) > 0) { outputStream.Write(ibuffer, 0, ilen); outputStream.Flush(); } outputStream.Dispose(); if (rom.Type == ItemType.Rom) { if (date && !String.IsNullOrWhiteSpace(((Rom)rom).Date)) { File.SetCreationTime(fileName, DateTime.Parse(((Rom)rom).Date)); } } success = true; } } catch (Exception ex) { Console.WriteLine(ex); success = false; } finally { inputStream.Dispose(); outputStream?.Dispose(); } return success; } /// /// Write a set of input files to an output folder (assuming the same output archive name) /// /// Input files to be moved /// Output directory to build to /// DatItem representing the new information /// True if the date from the DAT should be used if available, false otherwise (default) /// True if files should be output in Romba depot folders, false otherwise /// True if the archive was written properly, false otherwise public virtual bool Write(List inputFiles, string outDir, List roms, bool date = false, bool romba = false) { throw new NotImplementedException(); } #endregion } }