/// <summary> /// Seek to a specific point in the stream, if possible /// </summary> /// <param name="input">Input stream to try seeking on</param> /// <param name="offset">Optional offset to seek to</param> public static long SeekIfPossible(this Stream input, long offset = 0) { try { if (input.CanSeek) { if (offset < 0) { return(input.Seek(offset, SeekOrigin.End)); } else if (offset >= 0) { return(input.Seek(offset, SeekOrigin.Begin)); } } return(input.Position); } catch (NotSupportedException ex) { LoggerImpl.Verbose(ex, "Stream does not support seeking to starting offset. Stream position not changed"); } catch (NotImplementedException ex) { LoggerImpl.Warning(ex, "Stream does not support seeking to starting offset. Stream position not changed"); } return(-1); }
/// <summary> /// Retrieve a list of just directories from inputs /// </summary> /// <param name="inputs">List of strings representing directories and files</param> /// <param name="appendparent">True if the parent name should be included in the ParentablePath, false otherwise (default)</param> /// <returns>List of strings representing just directories from the inputs</returns> public static List <ParentablePath> GetDirectoriesOnly(List <string> inputs, bool appendparent = false) { List <ParentablePath> outputs = new List <ParentablePath>(); for (int i = 0; i < inputs.Count; i++) { string input = inputs[i]; // If we have a null or empty path if (string.IsNullOrEmpty(input)) { continue; } // If we have a wildcard string pattern = "*"; if (input.Contains("*") || input.Contains("?")) { pattern = Path.GetFileName(input); input = input.Substring(0, input.Length - pattern.Length); } // Get the parent path in case of appending string parentPath; try { parentPath = Path.GetFullPath(input); } catch (Exception ex) { LoggerImpl.Error(ex, $"An exception occurred getting the full path for '{input}'"); continue; } if (Directory.Exists(input)) { List <string> directories = GetDirectoriesOrdered(input, pattern); foreach (string dir in directories) { try { outputs.Add(new ParentablePath(Path.GetFullPath(dir), appendparent ? parentPath : string.Empty)); } catch (PathTooLongException ex) { LoggerImpl.Warning(ex, $"The path for '{dir}' was too long"); } catch (Exception ex) { LoggerImpl.Error(ex, $"An exception occurred processing '{dir}'"); } } } } return(outputs); }
/// <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, Hash hashes = Hash.Standard, bool keepReadOpen = false) { // If we want to automatically set the size if (size == -1) { size = input.Length; } try { // Get a list of hashers to run over the buffer List <Hasher> hashers = new List <Hasher>(); if (hashes.HasFlag(Hash.CRC)) { hashers.Add(new Hasher(Hash.CRC)); } if (hashes.HasFlag(Hash.MD5)) { hashers.Add(new Hasher(Hash.MD5)); } if (hashes.HasFlag(Hash.SHA1)) { hashers.Add(new Hasher(Hash.SHA1)); } if (hashes.HasFlag(Hash.SHA256)) { hashers.Add(new Hasher(Hash.SHA256)); } if (hashes.HasFlag(Hash.SHA384)) { hashers.Add(new Hasher(Hash.SHA384)); } if (hashes.HasFlag(Hash.SHA512)) { hashers.Add(new Hasher(Hash.SHA512)); } if (hashes.HasFlag(Hash.SpamSum)) { hashers.Add(new Hasher(Hash.SpamSum)); } // Initialize the hashing helpers var loadBuffer = new ThreadLoadBuffer(input); int buffersize = 3 * 1024 * 1024; byte[] buffer0 = new byte[buffersize]; byte[] buffer1 = new byte[buffersize]; /* * Please note that some of the following code is adapted from * RomVault. This is a modified version of how RomVault does * threaded hashing. As such, some of the terminology and code * is the same, though variable names and comments may have * been tweaked to better fit this code base. */ // Pre load the first buffer long refsize = size; int next = refsize > buffersize ? buffersize : (int)refsize; input.Read(buffer0, 0, next); int current = next; refsize -= next; bool bufferSelect = true; while (current > 0) { // Trigger the buffer load on the second buffer next = refsize > buffersize ? buffersize : (int)refsize; if (next > 0) { loadBuffer.Trigger(bufferSelect ? buffer1 : buffer0, next); } byte[] buffer = bufferSelect ? buffer0 : buffer1; // Run hashes in parallel Parallel.ForEach(hashers, Globals.ParallelOptions, h => h.Process(buffer, current)); // Wait for the load buffer worker, if needed if (next > 0) { loadBuffer.Wait(); } // Setup for the next hashing step current = next; refsize -= next; bufferSelect = !bufferSelect; } // Finalize all hashing helpers loadBuffer.Finish(); Parallel.ForEach(hashers, Globals.ParallelOptions, h => h.Terminate()); // Get the results BaseFile baseFile = new BaseFile() { Size = size, CRC = hashes.HasFlag(Hash.CRC) ? hashers.First(h => h.HashType == Hash.CRC).GetHash() : null, MD5 = hashes.HasFlag(Hash.MD5) ? hashers.First(h => h.HashType == Hash.MD5).GetHash() : null, SHA1 = hashes.HasFlag(Hash.SHA1) ? hashers.First(h => h.HashType == Hash.SHA1).GetHash() : null, SHA256 = hashes.HasFlag(Hash.SHA256) ? hashers.First(h => h.HashType == Hash.SHA256).GetHash() : null, SHA384 = hashes.HasFlag(Hash.SHA384) ? hashers.First(h => h.HashType == Hash.SHA384).GetHash() : null, SHA512 = hashes.HasFlag(Hash.SHA512) ? hashers.First(h => h.HashType == Hash.SHA512).GetHash() : null, SpamSum = hashes.HasFlag(Hash.SpamSum) ? hashers.First(h => h.HashType == Hash.SpamSum).GetHash() : null, }; // Dispose of the hashers loadBuffer.Dispose(); hashers.ForEach(h => h.Dispose()); return(baseFile); } catch (IOException ex) { LoggerImpl.Warning(ex, "An exception occurred during hashing."); return(new BaseFile()); } finally { if (!keepReadOpen) { input.Dispose(); } else { input.SeekIfPossible(); } } }
/// <summary> /// Populate the dictionary from an INI file /// </summary> /// <param name="ini">Path to INI file to populate from</param> /// <remarks> /// The INI file format that is supported here is not exactly the same /// as a traditional one. This expects a MAME extras format, which usually /// doesn't contain key value pairs and always at least contains one section /// called `ROOT_FOLDER`. If that's the name of a section, then we assume /// the value is boolean. If there's another section name, then that is set /// as the value instead. /// </remarks> public bool PopulateFromFile(string ini) { // Prepare all intenral variables IniReader ir = new IniReader(ini) { ValidateRows = false }; bool foundRootFolder = false; // If we got a null reader, just return if (ir == null) { return(false); } // Otherwise, read the file to the end try { while (!ir.EndOfStream) { // Read in the next line and process ir.ReadNextLine(); // We don't care about whitespace or comments if (ir.RowType == IniRowType.None || ir.RowType == IniRowType.Comment) { continue; } // If we have a section, just read it in if (ir.RowType == IniRowType.SectionHeader) { // If we've found the start of the extras, set the flag if (string.Equals(ir.Section, "ROOT_FOLDER", StringComparison.OrdinalIgnoreCase)) { foundRootFolder = true; } continue; } // If we have a value, then we start populating the dictionary else if (foundRootFolder) { // Get the value and machine name string value = ir.Section; string machineName = ir.CurrentLine.Trim(); // If the section is "ROOT_FOLDER", then we use the value "true" instead. // This is done because some INI files use the name of the file as the // category to be assigned to the items included. if (value == "ROOT_FOLDER") { value = "true"; } // Add the new mapping Mappings[machineName] = value; } } } catch (Exception ex) { LoggerImpl.Warning(ex, $"Exception found while parsing '{ini}'"); return(false); } ir.Dispose(); return(true); }