/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; using System.Text; using System.Threading.Tasks; using ICSharpCode.SharpZipLib.Core; using ICSharpCode.SharpZipLib.GZip; using ICSharpCode.SharpZipLib.Tar; using Ionic.Zip; using QuantConnect.Logging; using ZipEntry = ICSharpCode.SharpZipLib.Zip.ZipEntry; using ZipFile = Ionic.Zip.ZipFile; using ZipInputStream = ICSharpCode.SharpZipLib.Zip.ZipInputStream; using ZipOutputStream = ICSharpCode.SharpZipLib.Zip.ZipOutputStream; namespace QuantConnect { /// /// Compression class manages the opening and extraction of compressed files (zip, tar, tar.gz). /// /// QuantConnect's data library is stored in zip format locally on the hard drive. public static class Compression { /// /// Global Flag :: Operating System /// private static bool IsLinux { get { var p = (int)Environment.OSVersion.Platform; return (p == 4) || (p == 6) || (p == 128); } } /// /// Create a zip file of the supplied file names and string data source /// /// Output location to save the file. /// File names and data in a dictionary format. /// True on successfully creating the zip file. public static bool ZipData(string zipPath, Dictionary filenamesAndData) { try { //Create our output using (var stream = new ZipOutputStream(File.Create(zipPath))) { stream.SetLevel(0); foreach (var kvp in filenamesAndData) { var filename = kvp.Key; //Create the space in the zip file: var entry = new ZipEntry(filename); var bytes = Encoding.Default.GetBytes(kvp.Value); stream.PutNextEntry(entry); stream.Write(bytes, 0, bytes.Length); stream.CloseEntry(); } // End For Each File. //Close stream: stream.Finish(); stream.Close(); } // End Using } catch (Exception err) { Log.Error(err); return false; } return true; } /// /// Create a zip file of the supplied file names and data using a byte array /// /// Output location to save the file. /// File names and data in a dictionary format. /// True on successfully saving the file public static bool ZipData(string zipPath, IEnumerable> filenamesAndData) { var success = true; var buffer = new byte[4096]; try { //Create our output using (var stream = new ZipOutputStream(File.Create(zipPath))) { foreach (var file in filenamesAndData) { //Create the space in the zip file: var entry = new ZipEntry(file.Key); //Get a Byte[] of the file data: stream.PutNextEntry(entry); using (var ms = new MemoryStream(file.Value)) { int sourceBytes; do { sourceBytes = ms.Read(buffer, 0, buffer.Length); stream.Write(buffer, 0, sourceBytes); } while (sourceBytes > 0); } } // End For Each File. //Close stream: stream.Finish(); stream.Close(); } // End Using } catch (Exception err) { Log.Error(err); success = false; } return success; } /// /// Zips the specified lines of text into the zipPath /// /// The destination zip file path /// The entry name in the zip /// The lines to be written to the zip /// True if successful, otherwise false public static bool ZipData(string zipPath, string zipEntry, IEnumerable lines) { try { using (var stream = new ZipOutputStream(File.Create(zipPath))) using (var writer = new StreamWriter(stream)) { var entry = new ZipEntry(zipEntry); stream.PutNextEntry(entry); foreach (var line in lines) { writer.WriteLine(line); } } return true; } catch (Exception err) { Log.Error(err); return false; } } /// /// Append the zip data to the file-entry specified. /// /// The zip file path /// The entry name /// The entry data /// True if should override entry if it already exists /// True on success public static bool ZipCreateAppendData(string path, string entry, string data, bool overrideEntry = false) { try { using (var zip = File.Exists(path) ? ZipFile.Read(path) : new ZipFile(path)) { if (zip.ContainsEntry(entry) && overrideEntry) { zip.RemoveEntry(entry); } zip.AddEntry(entry, data); zip.UseZip64WhenSaving = Zip64Option.Always; zip.Save(); } } catch (Exception err) { Log.Error(err); return false; } return true; } /// /// Append the zip data to the file-entry specified. /// /// The zip file path /// The entry name /// The entry data /// True if should override entry if it already exists /// True on success public static bool ZipCreateAppendData(string path, string entry, byte[] data, bool overrideEntry = false) { try { using (var zip = File.Exists(path) ? ZipFile.Read(path) : new ZipFile(path)) { if (overrideEntry && zip.ContainsEntry(entry)) { zip.RemoveEntry(entry); } zip.AddEntry(entry, data); zip.UseZip64WhenSaving = Zip64Option.Always; zip.Save(); } } catch (Exception err) { Log.Error(err, $"file: {path} entry: {entry}"); return false; } return true; } /// /// Uncompress zip data byte array into a dictionary string array of filename-contents. /// /// Byte data array of zip compressed information /// Specifies the encoding used to read the bytes. If not specified, defaults to ASCII /// Uncompressed dictionary string-sting of files in the zip public static Dictionary UnzipData(byte[] zipData, Encoding encoding = null) { using var stream = new MemoryStream(zipData); return UnzipDataAsync(stream, encoding).ConfigureAwait(false).GetAwaiter().GetResult(); } /// /// Uncompress zip data byte array into a dictionary string array of filename-contents. /// /// Stream data of zip compressed information /// Specifies the encoding used to read the bytes. If not specified, defaults to ASCII /// Uncompressed dictionary string-sting of files in the zip public static async Task> UnzipDataAsync(Stream stream, Encoding encoding = null) { // Initialize: var data = new Dictionary(); try { //Read out the zipped data into a string, save in array: using (var zipStream = new ZipInputStream(stream)) { while (true) { //Get the next file var entry = zipStream.GetNextEntry(); if (entry != null) { // Read the file into buffer: var buffer = new byte[entry.Size]; await zipStream.ReadAsync(buffer, 0, (int)entry.Size).ConfigureAwait(false); //Save into array: var str = (encoding ?? Encoding.ASCII).GetString(buffer); data[entry.Name] = str; } else { break; } } } // End Zip Stream. } catch (Exception err) { Log.Error(err); } return data; } /// /// Performs an in memory zip of the specified bytes /// /// The file contents in bytes to be zipped /// The zip entry name /// The zipped file as a byte array public static byte[] ZipBytes(byte[] bytes, string zipEntryName) { using var memoryStream = new MemoryStream(); ZipBytesAsync(memoryStream, bytes, zipEntryName, null).ConfigureAwait(false).GetAwaiter().GetResult(); return memoryStream.ToArray(); } /// /// Performs an in memory zip of the specified bytes in the target stream /// /// The target stream /// The file contents in bytes to be zipped /// The zip entry name /// The archive mode /// The desired compression level /// The zipped file as a byte array public static async Task ZipBytesAsync(Stream target, byte[] data, string zipEntryName, ZipArchiveMode? mode = null, CompressionLevel? compressionLevel = null) { await ZipBytesAsync(target, [new KeyValuePair(data, zipEntryName)], mode, compressionLevel).ConfigureAwait(false); } /// /// Performs an in memory zip of the specified bytes in the target stream /// /// The target stream /// The file contents in bytes to be zipped /// The archive mode /// The desired compression level /// The zipped file as a byte array public static async Task ZipBytesAsync(Stream target, IEnumerable> data, ZipArchiveMode? mode = null, CompressionLevel? compressionLevel = null) { compressionLevel ??= CompressionLevel.SmallestSize; using var archive = new ZipArchive(target, mode ?? ZipArchiveMode.Create, true); foreach (var kvp in data) { var entry = archive.CreateEntry(kvp.Value, compressionLevel.Value); using var entryStream = entry.Open(); await entryStream.WriteAsync(kvp.Key).ConfigureAwait(false); } } /// /// Performs an in memory zip of the specified stream in the target stream /// /// The target stream /// The file contents in bytes to be zipped /// The archive mode /// The desired compression level /// The zipped file as a byte array public static async Task ZipStreamsAsync(string target, IEnumerable> data, ZipArchiveMode? mode = null, CompressionLevel? compressionLevel = null) { using var fileStream = mode == ZipArchiveMode.Update ? new FileStream(target, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None) : new FileStream(target, FileMode.Create, FileAccess.Write, FileShare.None); await ZipStreamsAsync(fileStream, data, mode, compressionLevel).ConfigureAwait(false); } /// /// Performs an in memory zip of the specified stream in the target stream /// /// The target stream /// The file contents in bytes to be zipped /// The archive mode /// The desired compression level /// True to leave the taget stream open /// The zipped file as a byte array public static async Task ZipStreamsAsync(Stream target, IEnumerable> data, ZipArchiveMode? mode = null, CompressionLevel? compressionLevel = null, bool leaveStreamOpen = false) { compressionLevel ??= CompressionLevel.SmallestSize; using var archive = new ZipArchive(target, mode ?? ZipArchiveMode.Create, leaveStreamOpen); foreach (var kvp in data) { if (archive.Mode == ZipArchiveMode.Update) { var existingEntry = archive.GetEntry(kvp.Key); existingEntry?.Delete(); } var entry = archive.CreateEntry(kvp.Key, compressionLevel.Value); using var entryStream = entry.Open(); await kvp.Value.CopyToAsync(entryStream).ConfigureAwait(false); } } /// /// Extract .gz files to disk /// /// /// public static string UnGZip(string gzipFileName, string targetDirectory) { // Use a 4K buffer. Any larger is a waste. var dataBuffer = new byte[4096]; var newFileOutput = Path.Combine(targetDirectory, Path.GetFileNameWithoutExtension(gzipFileName)); using (Stream fileStream = new FileStream(gzipFileName, FileMode.Open, FileAccess.Read)) using (var gzipStream = new GZipInputStream(fileStream)) using (var fileOutput = File.Create(newFileOutput)) { StreamUtils.Copy(gzipStream, fileOutput, dataBuffer); } return newFileOutput; } /// /// Compress a given file and delete the original file. Automatically rename the file to name.zip. /// /// Path of the original file /// The name of the entry inside the zip file /// Boolean flag to delete the original file after completion /// String path for the new zip file public static string Zip(string textPath, string zipEntryName, bool deleteOriginal = true) { var zipPath = textPath.Replace(".csv", ".zip").Replace(".txt", ".zip"); Zip(textPath, zipPath, zipEntryName, deleteOriginal); return zipPath; } /// /// Compresses the specified source file. /// /// The source file to be compressed /// The destination zip file path /// The zip entry name for the file /// True to delete the source file upon completion public static void Zip(string source, string destination, string zipEntryName, bool deleteOriginal) { try { var buffer = new byte[4096]; using (var stream = new ZipOutputStream(File.Create(destination))) { //Zip the text file. var entry = new ZipEntry(zipEntryName); stream.PutNextEntry(entry); using (var fs = File.OpenRead(source)) { int sourceBytes; do { sourceBytes = fs.Read(buffer, 0, buffer.Length); stream.Write(buffer, 0, sourceBytes); } while (sourceBytes > 0); } } //Delete the old text file: if (deleteOriginal) { File.Delete(source); } } catch (Exception err) { Log.Error(err); } } /// /// Compress a given file and delete the original file. Automatically rename the file to name.zip. /// /// Path of the original file /// Boolean flag to delete the original file after completion /// String path for the new zip file public static string Zip(string textPath, bool deleteOriginal = true) { return Zip(textPath, Path.GetFileName(textPath), deleteOriginal); } /// /// Compress given data to the path given /// /// Data to write to zip /// Path to write to /// Entry to save the data as public static void Zip(string data, string zipPath, string zipEntry) { using (var stream = new ZipOutputStream(File.Create(zipPath))) { var entry = new ZipEntry(zipEntry); stream.PutNextEntry(entry); var buffer = new byte[4096]; using (var dataReader = new MemoryStream(Encoding.Default.GetBytes(data))) { int sourceBytes; do { sourceBytes = dataReader.Read(buffer, 0, buffer.Length); stream.Write(buffer, 0, sourceBytes); } while (sourceBytes > 0); } } } /// /// Zips the specified directory, preserving folder structure /// /// The directory to be zipped /// The output zip file destination /// True to include the root 'directory' in the zip, false otherwise /// True on a successful zip, false otherwise public static bool ZipDirectory(string directory, string destination, bool includeRootInZip = true) { try { if (File.Exists(destination)) File.Delete(destination); System.IO.Compression.ZipFile.CreateFromDirectory(directory, destination, CompressionLevel.Fastest, includeRootInZip, new PathEncoder()); return true; } catch (Exception err) { Log.Error(err); return false; } } /// /// Encode the paths as linux format for cross platform compatibility /// private class PathEncoder : UTF8Encoding { public override byte[] GetBytes(string s) { s = s.Replace("\\", "/"); return base.GetBytes(s); } } /// /// Unzips the specified zip file to the specified directory /// /// The zip to be unzipped /// The directory to place the unzipped files /// Flag specifying whether or not to overwrite existing files public static bool Unzip(string zip, string directory, bool overwrite = false) { if (!File.Exists(zip)) return false; try { if (!overwrite) { System.IO.Compression.ZipFile.ExtractToDirectory(zip, directory); } else { using (var archive = new ZipArchive(File.OpenRead(zip))) { foreach (var file in archive.Entries) { // skip directories if (string.IsNullOrEmpty(file.Name)) continue; var filepath = Path.Combine(directory, file.FullName); if (IsLinux) filepath = filepath.Replace(@"\", "/"); var outputFile = new FileInfo(filepath); if (!outputFile.Directory.Exists) { outputFile.Directory.Create(); } file.ExtractToFile(outputFile.FullName, true); } } } return true; } catch (Exception err) { Log.Error(err); return false; } } /// /// Zips all files specified to a new zip at the destination path /// public static void ZipFiles(string destination, IEnumerable files) { try { using (var zipStream = new ZipOutputStream(File.Create(destination))) { var buffer = new byte[4096]; foreach (var file in files) { if (!File.Exists(file)) { Log.Trace($"ZipFiles(): File does not exist: {file}"); continue; } var entry = new ZipEntry(Path.GetFileName(file)); zipStream.PutNextEntry(entry); using (var fstream = File.OpenRead(file)) { StreamUtils.Copy(fstream, zipStream, buffer); } } } } catch (Exception err) { Log.Error(err); } } /// /// Streams a local zip file using a streamreader. /// Important: the caller must call Dispose() on the returned ZipFile instance. /// /// Location of the original zip file /// The ZipFile instance to be returned to the caller /// Stream reader of the first file contents in the zip file public static StreamReader Unzip(string filename, out ZipFile zip) { return Unzip(filename, null, out zip); } /// /// Streams a local zip file using a streamreader. /// Important: the caller must call Dispose() on the returned ZipFile instance. /// /// Location of the original zip file /// The zip entry name to open a reader for. Specify null to access the first entry /// The ZipFile instance to be returned to the caller /// Stream reader of the first file contents in the zip file public static StreamReader Unzip(string filename, string zipEntryName, out ZipFile zip) { StreamReader reader = null; zip = null; try { if (File.Exists(filename)) { try { zip = new ZipFile(filename); var entry = zip.FirstOrDefault(x => zipEntryName == null || string.Compare(x.FileName, zipEntryName, StringComparison.OrdinalIgnoreCase) == 0); if (entry == null) { // Unable to locate zip entry return null; } reader = new StreamReader(entry.OpenReader()); } catch (Exception err) { Log.Error(err, "Inner try/catch"); if (zip != null) zip.Dispose(); if (reader != null) reader.Close(); } } else { Log.Error($"Data.UnZip(2): File doesn\'t exist: {filename}"); } } catch (Exception err) { Log.Error(err, "File: " + filename); } return reader; } /// /// Streams the unzipped file as key value pairs of file name to file contents. /// NOTE: When the returned enumerable finishes enumerating, the zip stream will be /// closed rendering all key value pair Value properties unaccessible. Ideally this /// would be enumerated depth first. /// /// /// This method has the potential for a memory leak if each kvp.Value enumerable is not disposed /// /// The zip file to stream /// The stream zip contents public static IEnumerable>> Unzip(string filename) { if (!File.Exists(filename)) { Log.Error($"Compression.Unzip(): File does not exist: {filename}"); return Enumerable.Empty>>(); } try { return ReadLinesImpl(filename); } catch (Exception err) { Log.Error(err); } return Enumerable.Empty>>(); } /// /// Lazily unzips the specified stream /// /// The zipped stream to be read /// An enumerable whose elements are zip entry key value pairs with /// a key of the zip entry name and the value of the zip entry's file lines public static IEnumerable>> Unzip(Stream stream) { using (var zip = ZipFile.Read(stream)) { foreach (var entry in zip) { yield return new KeyValuePair>(entry.FileName, ReadZipEntry(entry)); } } } /// /// Streams each line from the first zip entry in the specified zip file /// /// The zip file path to stream /// An enumerable containing each line from the first unzipped entry public static List ReadLines(string filename) { if (!File.Exists(filename)) { Log.Error($"Compression.ReadFirstZipEntry(): File does not exist: {filename}"); return new List(); } try { return ReadLinesImpl(filename, firstEntryOnly: true).Single().Value; } catch (Exception err) { Log.Error(err); } return new List(); } private static IEnumerable>> ReadLinesImpl(string filename, bool firstEntryOnly = false) { using (var zip = ZipFile.Read(filename)) { for (var i = 0; i < zip.Count; i++) { var entry = zip[i]; yield return new KeyValuePair>(entry.FileName, ReadZipEntry(entry)); if (firstEntryOnly) { yield break; } } } } private static List ReadZipEntry(Ionic.Zip.ZipEntry entry) { var result = new List(); using var entryReader = new StreamReader(entry.OpenReader()); var line = entryReader.ReadLine(); while (line != null) { result.Add(line); line = entryReader.ReadLine(); } return result; } /// /// Unzip a local file and return its contents via streamreader: /// public static StreamReader UnzipStreamToStreamReader(Stream zipstream) { StreamReader reader = null; try { //Initialise: MemoryStream file; //If file exists, open a zip stream for it. using (var zipStream = new ZipInputStream(zipstream)) { //Read the file entry into buffer: var entry = zipStream.GetNextEntry(); var buffer = new byte[entry.Size]; zipStream.Read(buffer, 0, (int)entry.Size); //Load the buffer into a memory stream. file = new MemoryStream(buffer); } //Open the memory stream with a stream reader. reader = new StreamReader(file); } catch (Exception err) { Log.Error(err); } return reader; } // End UnZip /// /// Unzip a stream that represents a zip file and return the first entry as a stream /// public static Stream UnzipStream(Stream zipstream, out ZipFile zipFile, string entryName = null) { zipFile = ZipFile.Read(zipstream); try { Ionic.Zip.ZipEntry entry; if (string.IsNullOrEmpty(entryName)) { //Read the file entry into buffer: entry = zipFile.Entries.FirstOrDefault(); } else { // Attempt to find our specific entry if (!zipFile.ContainsEntry(entryName)) { return null; } entry = zipFile[entryName]; } if (entry != null) { return entry.OpenReader(); } } catch (Exception err) { Log.Error(err); } return null; } // End UnZip /// /// Unzip the given byte array and return the created file names. /// /// A byte array containing the zip /// The target output folder /// List of unzipped file names public static List UnzipToFolder(byte[] zipData, string outputFolder) { var stream = new MemoryStream(zipData); return UnzipToFolder(stream, outputFolder); } /// /// Unzip a local file and return the created file names /// /// Location of the zip on the HD /// List of unzipped file names public static List UnzipToFolder(string zipFile) { var outFolder = Path.GetDirectoryName(zipFile); var stream = File.OpenRead(zipFile); return UnzipToFolder(stream, outFolder); } /// /// Unzip the given data stream into the target output folder and return the created file names /// /// The zip data stream /// The target output folder /// List of unzipped file names private static List UnzipToFolder(Stream dataStream, string outFolder) { //1. Initialize: var files = new List(); if (string.IsNullOrEmpty(outFolder)) { outFolder = Directory.GetCurrentDirectory(); } ICSharpCode.SharpZipLib.Zip.ZipFile zf = null; try { zf = new ICSharpCode.SharpZipLib.Zip.ZipFile(dataStream); foreach (ZipEntry zipEntry in zf) { //Ignore Directories if (!zipEntry.IsFile) continue; var buffer = new byte[4096]; // 4K is optimum var zipStream = zf.GetInputStream(zipEntry); // Manipulate the output filename here as desired. var fullZipToPath = Path.Combine(outFolder, zipEntry.Name); var targetFile = new FileInfo(fullZipToPath); if (targetFile.Directory != null && !targetFile.Directory.Exists) { targetFile.Directory.Create(); } //Save the file name for later: files.Add(fullZipToPath); //Copy the data in buffer chunks using (var streamWriter = File.Create(fullZipToPath)) { StreamUtils.Copy(zipStream, streamWriter, buffer); } } } catch { // lets catch the exception just to log some information about the zip file Log.Error($"Compression.UnzipToFolder(): Failure: outFolder: {outFolder} - files: {string.Join(",", files)}"); throw; } finally { if (zf != null) { zf.IsStreamOwner = true; // Makes close also shut the underlying stream zf.Close(); // Ensure we release resources } } return files; } // End UnZip /// /// Extracts all file from a zip archive and copies them to a destination folder. /// /// The source zip file. /// The destination folder to extract the file to. public static void UnTarFiles(string source, string destination) { var inStream = File.OpenRead(source); var tarArchive = TarArchive.CreateInputTarArchive(inStream); tarArchive.ExtractContents(destination); tarArchive.Close(); inStream.Close(); } /// /// Extract tar.gz files to disk /// /// Tar.gz source file /// Location folder to unzip to public static void UnTarGzFiles(string source, string destination) { var inStream = File.OpenRead(source); var gzipStream = new GZipInputStream(inStream); var tarArchive = TarArchive.CreateInputTarArchive(gzipStream); tarArchive.ExtractContents(destination); tarArchive.Close(); gzipStream.Close(); inStream.Close(); } /// /// Enumerate through the files of a TAR and get a list of KVP names-byte arrays /// /// The input tar stream /// True if the input stream is a .tar.gz or .tgz /// An enumerable containing each tar entry and it's contents public static IEnumerable> UnTar(Stream stream, bool isTarGz) { using (var tar = new TarInputStream(isTarGz ? (Stream)new GZipInputStream(stream) : stream)) { TarEntry entry; while ((entry = tar.GetNextEntry()) != null) { if (entry.IsDirectory) continue; using (var output = new MemoryStream()) { tar.CopyEntryContents(output); yield return new KeyValuePair(entry.Name, output.ToArray()); } } } } /// /// Enumerate through the files of a TAR and get a list of KVP names-byte arrays. /// /// /// public static IEnumerable> UnTar(string source) { //This is a tar.gz file. var gzip = (source.Substring(Math.Max(0, source.Length - 6)) == "tar.gz"); using (var file = File.OpenRead(source)) { var tarIn = new TarInputStream(file); if (gzip) { var gzipStream = new GZipInputStream(file); tarIn = new TarInputStream(gzipStream); } TarEntry tarEntry; while ((tarEntry = tarIn.GetNextEntry()) != null) { if (tarEntry.IsDirectory) continue; using (var stream = new MemoryStream()) { tarIn.CopyEntryContents(stream); yield return new KeyValuePair(tarEntry.Name, stream.ToArray()); } } tarIn.Close(); } } /// /// Validates whether the zip is corrupted or not /// /// Path to the zip file /// true if archive tests ok; false otherwise. public static bool ValidateZip(string path) { using (var zip = new ICSharpCode.SharpZipLib.Zip.ZipFile(path)) { return zip.TestArchive(true); } } /// /// Returns the entry file names contained in a zip file /// /// The zip file name /// An IEnumerable of entry file names public static IEnumerable GetZipEntryFileNames(string zipFileName) { using (var zip = ZipFile.Read(zipFileName)) { return zip.EntryFileNames; } } /// /// Return the entry file names contained in a zip file /// /// Stream to the file /// IEnumerable of entry file names public static IEnumerable GetZipEntryFileNames(Stream zipFileStream) { using (var zip = ZipFile.Read(zipFileStream)) { return zip.EntryFileNames; } } /// /// Extracts a 7-zip archive to disk, using the 7-zip CLI utility /// /// Path to the 7z file /// Directory to output contents of 7z /// Timeout in seconds for how long we should wait for the extraction to complete /// The extraction failed because of a timeout or the exit code was not 0 public static void Extract7ZipArchive(string inputFile, string outputDirectory, int execTimeout = 60000) { var zipper = IsLinux ? "7z" : "C:/Program Files/7-Zip/7z.exe"; var psi = new ProcessStartInfo(zipper, " e " + inputFile + " -o" + outputDirectory) { CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden, UseShellExecute = false, RedirectStandardOutput = false }; var process = new Process(); process.StartInfo = psi; process.Start(); if (!process.WaitForExit(execTimeout)) { throw new TimeoutException($"Timed out extracting 7Zip archive: {inputFile} ({execTimeout} seconds)"); } if (process.ExitCode > 0) { throw new Exception($"Compression.Extract7ZipArchive(): 7Zip exited unsuccessfully (code {process.ExitCode})"); } } } }