From 1c45080c64f7ed659b1d54fd8e59c7f5d944e0ba Mon Sep 17 00:00:00 2001 From: daPhie79 <33412188+daPhie79@users.noreply.github.com> Date: Sat, 23 Mar 2019 20:38:17 -0400 Subject: [PATCH] prepare to call it a release (v0.2) add IDisposable interfaces to Extractor and Compressor add a couple sanity checks add a couple custom exceptions add password to command-line app add Fody Costura nuget package to command-line app --- .gitignore | 2 +- README.md | 5 + tiny7z/Archive/Interfaces.cs | 4 +- tiny7z/Archive/SevenZip/SevenZipCompressor.cs | 15 ++ tiny7z/Archive/SevenZip/SevenZipException.cs | 19 ++- tiny7z/Archive/SevenZip/SevenZipExtractor.cs | 8 +- .../SevenZip/SevenZipStreamsExtractor.cs | 2 +- tiny7z/ToDo.txt | 26 ++++ tiny7zTool/FodyWeavers.xml | 4 + tiny7zTool/FodyWeavers.xsd | 111 ++++++++++++++ tiny7zTool/Program.cs | 135 ++++++++++++------ tiny7zTool/tiny7zTool.csproj | 11 ++ 12 files changed, 289 insertions(+), 53 deletions(-) create mode 100644 tiny7z/ToDo.txt create mode 100644 tiny7zTool/FodyWeavers.xml create mode 100644 tiny7zTool/FodyWeavers.xsd diff --git a/.gitignore b/.gitignore index 0944390..2d36a0d 100644 --- a/.gitignore +++ b/.gitignore @@ -261,4 +261,4 @@ __pycache__/ *.pyc # CUSTOM ADDED -ToDo.txt + diff --git a/README.md b/README.md index 196c3fe..b928beb 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,11 @@ tiny7z is a native C# SevenZip 7zip .7z file format archive reader/writer - Support LZMA, LZMA2, PPMd decoders. - Support AES, BCJ and BCJ2 decoder filters. +## Releases + +- v0.1 - First release, unofficial, lots of features missing, incomplete test app +- v0.2 - First official release, command-line test app + ## Current limitations *They are plenty unfortunately, but this library is still a huge step forward for compact .7z support in native C#* diff --git a/tiny7z/Archive/Interfaces.cs b/tiny7z/Archive/Interfaces.cs index ee03903..0a0aca0 100644 --- a/tiny7z/Archive/Interfaces.cs +++ b/tiny7z/Archive/Interfaces.cs @@ -91,7 +91,7 @@ UInt64 TotalSize /// /// Extractor proxy interface /// - public interface IExtractor + public interface IExtractor : IDisposable { /// /// List of files contained in the opened archive. @@ -208,7 +208,7 @@ bool SkipExistingFiles /// /// Compressor proxy interface /// - public interface ICompressor + public interface ICompressor : IDisposable { /// /// List of files in the archive. diff --git a/tiny7z/Archive/SevenZip/SevenZipCompressor.cs b/tiny7z/Archive/SevenZip/SevenZipCompressor.cs index ba5dfc0..f952557 100644 --- a/tiny7z/Archive/SevenZip/SevenZipCompressor.cs +++ b/tiny7z/Archive/SevenZip/SevenZipCompressor.cs @@ -41,6 +41,16 @@ public bool Solid #endregion Public Properties #region Public Methods + public void Dispose() // IDisposable + { + if (this.stream != null && this.header != null) + { + Finalize(); + } + this.stream = null; + this.header = null; + } + public ICompressor AddDirectory(string inputDirectory, string archiveDirectory = null, bool recursive = true) { Trace.TraceInformation($"Adding files from directory `{inputDirectory}`."); @@ -146,6 +156,9 @@ public ICompressor AddFile(Stream stream, string archiveFileName, DateTime? time public ICompressor Finalize() { + if (this.stream == null || this.header == null) + throw new SevenZipException("Compressor object has already been finalized."); + Trace.TraceInformation($"Compressing files."); Trace.Indent(); try @@ -189,6 +202,8 @@ public ICompressor Finalize() Trace.TraceInformation("Done compressing files."); } + this.stream = null; + this.header = null; return this; } #endregion Public Methods diff --git a/tiny7z/Archive/SevenZip/SevenZipException.cs b/tiny7z/Archive/SevenZip/SevenZipException.cs index 37e7125..4a88c40 100644 --- a/tiny7z/Archive/SevenZip/SevenZipException.cs +++ b/tiny7z/Archive/SevenZip/SevenZipException.cs @@ -3,9 +3,6 @@ namespace pdj.tiny7z.Archive { - /// - /// Base exception class for error handling - /// public class SevenZipException : Exception { internal SevenZipException(string message) @@ -13,4 +10,20 @@ internal SevenZipException(string message) { } } + + public class SevenZipFileAlreadyExistsException : SevenZipException + { + internal SevenZipFileAlreadyExistsException(SevenZipArchiveFile file) + : base($"File `{file.Name}` already exists.") + { + } + } + + public class SevenZipPasswordRequiredException : SevenZipException + { + internal SevenZipPasswordRequiredException() + : base("No password provided. Encrypted stream requires password.") + { + } + } } diff --git a/tiny7z/Archive/SevenZip/SevenZipExtractor.cs b/tiny7z/Archive/SevenZip/SevenZipExtractor.cs index 0e9d36c..cb0a420 100644 --- a/tiny7z/Archive/SevenZip/SevenZipExtractor.cs +++ b/tiny7z/Archive/SevenZip/SevenZipExtractor.cs @@ -55,6 +55,11 @@ public bool SkipExistingFiles #endregion Public Properties #region Public Methods + public void Dispose() // IDisposable + { + Finalize(); + } + public void Dump() { // TODO @@ -288,7 +293,6 @@ public IExtractor Finalize() { this.stream = null; this.header = null; - this._Files = null; return this; } #endregion Public Methods @@ -369,7 +373,7 @@ private bool preProcessFile(string outputDirectory, SevenZipArchiveFile file) else { if (!SkipExistingFiles) - throw new IOException($"File `{file.Name}` already exists."); + throw new SevenZipFileAlreadyExistsException(file); result = FeedbackResult.No; } } diff --git a/tiny7z/Archive/SevenZip/SevenZipStreamsExtractor.cs b/tiny7z/Archive/SevenZip/SevenZipStreamsExtractor.cs index f666b2d..84177aa 100644 --- a/tiny7z/Archive/SevenZip/SevenZipStreamsExtractor.cs +++ b/tiny7z/Archive/SevenZip/SevenZipStreamsExtractor.cs @@ -22,7 +22,7 @@ internal SevenZipStreamsExtractor(Stream stream, SevenZipHeader.StreamsInfo stre public string CryptoGetTextPassword() { if (password == null) - throw new SevenZipException("No password provided for encrypted data."); + throw new SevenZipPasswordRequiredException(); return password; } #endregion Public Methods (Interfaces) diff --git a/tiny7z/ToDo.txt b/tiny7z/ToDo.txt new file mode 100644 index 0000000..131f346 --- /dev/null +++ b/tiny7z/ToDo.txt @@ -0,0 +1,26 @@ +# Tiny7z + +## TO DO LIST + +- [ ] Allow per-file (or files group) compression settings (solid/non solid) +- [ ] Allow compressing in different coders (LZMA2, etc.) + +## DONE + +- [x] Replace digests with arrays of CRCs (improved Digests instead) +- [x] When SubStreamsInfo exists, fields have to be properly filled/guessed from other fields +- [x] When writing SubStreamsInfo, I have to undo these guesses to write them +- [x] Add constants for hard-coded values +- [x] Compressed header +- [x] Replace clunky bool vectors with adapted vectors with data used +- [x] Compress one file +- [x] Ensure MultiFileStream.Source cleanup +- [x] Better extract/compress interface +- [x] Add BCJ and BCJ2 filters +- [x] Add multiple coders per folder support for decompression +- [x] Add progress report for compression and decompression +- [x] Add AES password handling to enable decryption +- [x] Fully implement ProgressDelegate calls with file details +- [x] Implement access protection with internal where appropriate +- [x] Ensure no duplicate files when not "preserving directory structure" (handle overwriting per file at least) +- [x] Better tracing diff --git a/tiny7zTool/FodyWeavers.xml b/tiny7zTool/FodyWeavers.xml new file mode 100644 index 0000000..a5dcf04 --- /dev/null +++ b/tiny7zTool/FodyWeavers.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/tiny7zTool/FodyWeavers.xsd b/tiny7zTool/FodyWeavers.xsd new file mode 100644 index 0000000..44a5374 --- /dev/null +++ b/tiny7zTool/FodyWeavers.xsd @@ -0,0 +1,111 @@ + + + + + + + + + + + + A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks + + + + + A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. + + + + + A list of unmanaged 32 bit assembly names to include, delimited with line breaks. + + + + + A list of unmanaged 64 bit assembly names to include, delimited with line breaks. + + + + + The order of preloaded assemblies, delimited with line breaks. + + + + + + This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file. + + + + + Controls if .pdbs for reference assemblies are also embedded. + + + + + Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option. + + + + + As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off. + + + + + Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code. + + + + + Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior. + + + + + A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with | + + + + + A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |. + + + + + A list of unmanaged 32 bit assembly names to include, delimited with |. + + + + + A list of unmanaged 64 bit assembly names to include, delimited with |. + + + + + The order of preloaded assemblies, delimited with |. + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/tiny7zTool/Program.cs b/tiny7zTool/Program.cs index 4ebfdce..a594eee 100644 --- a/tiny7zTool/Program.cs +++ b/tiny7zTool/Program.cs @@ -33,7 +33,9 @@ public enum ArchiveAction } static ArchiveAction archiveAction = ArchiveAction.None; + static bool finished = false; static bool overwrite = false; + static string password = null; static string archiveFileName = string.Empty; static string outputPath = string.Empty; static List fileNames = new List(); @@ -80,12 +82,15 @@ static bool ProgressEvent(Archive.IProgressProvider provider, bool included, int { if (currentFileIndex >= provider.Files.Count) { - string status = (archiveAction == ArchiveAction.Add ? "Compressing" : "Extracting") + ": 100% Done!"; - status = status + new string(' ', Console.BufferWidth - 1 - status.Length); - Console.Write(status); - Console.SetCursorPosition(0, Console.CursorTop); + if (!finished) + { + string status = (archiveAction == ArchiveAction.Add ? "Compressing" : "Extracting") + ": 100% Done!"; + status = status + new string(' ', Console.BufferWidth - 1 - status.Length); + Console.WriteLine(status); + finished = true; + } } - else if (timer == default(DateTime) || DateTime.Now.Subtract(timer).Milliseconds > 250) + else if (timer == default(DateTime) || DateTime.Now.Subtract(timer).Milliseconds > 100) { timer = DateTime.Now; string status; @@ -121,75 +126,104 @@ static void CompressFiles() } using (var archive = new Archive.SevenZipArchive(File.Create(archiveFileName), FileAccess.Write)) + using (var compressor = archive.Compressor()) { - var compressor = archive.Compressor(); compressor.CompressHeader = true; compressor.PreserveDirectoryStructure = true; + compressor.ProgressDelegate = ProgressEvent; compressor.Solid = true; foreach (var fn in fileNames) { - if (fn.IndexOfAny(new[] { '?', '*' }) != -1) + try { - string path = Path.Combine(Directory.GetCurrentDirectory(), Path.GetDirectoryName(fn)); - string pattern = Path.GetFileName(fn); + if (fn.IndexOfAny(new[] { '?', '*' }) != -1) + { + string path = Path.Combine(Directory.GetCurrentDirectory(), Path.GetDirectoryName(fn)); + string pattern = Path.GetFileName(fn); - foreach (var file in Directory.EnumerateFiles(path, pattern, SearchOption.TopDirectoryOnly)) + foreach (var file in Directory.EnumerateFiles(path, pattern, SearchOption.TopDirectoryOnly)) + { + Log.Information("Compressing {File}...", Path.GetFileName(file)); + compressor.AddFile(file); + } + } + else { - Log.Information("Compressing {File}...", Path.GetFileName(file)); - compressor.AddFile(file); + if (File.Exists(fn)) + { + Log.Information("Compressing {File}...", Path.GetFileName(fn)); + compressor.AddFile(fn); + } + else if (Directory.Exists(fn)) + { + var info = new DirectoryInfo(fn); + Log.Information("Compressing contents of {Directory}...", Path.GetFileName(info.Name)); + compressor.AddDirectory(info.FullName); + } } } - else + catch (Exception ex) { - if (File.Exists(fn)) - { - Log.Information("Compressing {File}...", Path.GetFileName(fn)); - compressor.AddFile(fn); - } - else if (Directory.Exists(fn)) - { - var info = new DirectoryInfo(fn); - Log.Information("Compressing contents of {Directory}...", Path.GetFileName(info.Name)); - compressor.AddDirectory(info.FullName); - } + Log.Error(ex, "There was an error while building file list."); } } - var timer = DateTime.Now; - compressor.ProgressDelegate = ProgressEvent; - var now = DateTime.Now; - compressor.Finalize(); - var ela = DateTime.Now.Subtract(now); + try + { + compressor.Finalize(); + Console.WriteLine(); + } + catch (Archive.SevenZipFileAlreadyExistsException ex) + { + Log.Error(ex, "Output archive filename already exists."); + } + catch (Exception ex) + { + Log.Error(ex, "There was an error while compressing files."); + } - Console.WriteLine(); - Log.Information("Compression took {ela}ms.", ela.TotalMilliseconds); + Log.Information("Compression took {ela}ms.", DateTime.Now.Subtract(now).TotalMilliseconds); } } static void ExtractFiles(bool preserveDirectoryStructure) { using (var archive = new Archive.SevenZipArchive(File.OpenRead(archiveFileName), FileAccess.Read)) + using (var extractor = archive.Extractor()) { - var extractor = archive.Extractor(); - extractor.OverwriteExistingFiles = false; + extractor.OverwriteExistingFiles = overwrite; + extractor.Password = password; extractor.PreserveDirectoryStructure = preserveDirectoryStructure; - extractor.SkipExistingFiles = true; + extractor.ProgressDelegate = ProgressEvent; + extractor.SkipExistingFiles = false; if (string.IsNullOrWhiteSpace(outputPath)) { outputPath = Directory.GetCurrentDirectory(); } - var timer = DateTime.Now; - extractor.ProgressDelegate = ProgressEvent; - var now = DateTime.Now; if (!fileNames.Any()) { - Log.Information("Extracting files into \"{Path}\"...", Path.GetFileName(outputPath)); - extractor.ExtractArchive(outputPath); + try + { + Log.Information("Extracting files into \"{Path}\"...", Path.GetFileName(outputPath)); + extractor.ExtractArchive(outputPath); + } + catch (Archive.SevenZipPasswordRequiredException ex) + { + Log.Error(ex.Message); + } + catch (Archive.SevenZipFileAlreadyExistsException ex) + { + Log.Error(ex.Message); + } + catch (Exception ex) + { + Log.Error(ex, "There was an error attempting to extract file."); + } } else { @@ -197,16 +231,15 @@ static void ExtractFiles(bool preserveDirectoryStructure) { Log.Information("Extracting file(s) \"{FileNames}\" into \"{Path}\"...", string.Join(", ", fileNames), Path.GetFileName(outputPath)); extractor.ExtractFiles(fileNames.ToArray(), outputPath); + Console.WriteLine(); } catch (Exception ex) { Log.Error(ex, "There was an error attempting to extract file."); } } - var ela = DateTime.Now.Subtract(now); - Console.WriteLine(); - Log.Information("Decompression took {ela}ms.", ela.TotalMilliseconds); + Log.Information("Decompression took {ela}ms.", DateTime.Now.Subtract(now).TotalMilliseconds); } } @@ -218,7 +251,9 @@ static void PrintHelp() Log.Information(" e extract files from archive (ignoring paths)"); Log.Information(" x extract files from archive (full paths)"); Log.Information(" [options] :"); - Log.Information(" -o \"output path\" : specify output directory for extracted files."); + Log.Information(" -o \"output path\" : specify output directory for extracted files"); + Log.Information(" -p \"password\" : specify password to decrypt password-protected archive"); + Log.Information(" -x : overwrite output files (either archive file, or extracted files)"); Log.Information(" : name of .7z archive file to compress to, or decompress from"); Log.Information(" : space separated list of files to add to or extract from archive"); Console.WriteLine(); @@ -255,8 +290,20 @@ static bool ProcessCommandLine(string[] args) } } break; + case "-p": + if (++i == args.Length) + { + Log.Error("Invalid -p parameter. Missing password."); + error = true; + } + else + { + password = args[i]; + Log.Information("Using \"{Password}\" to decrypt archive.", password); + } + break; case "-x": - Log.Information("-x Overwrite output archive if it already exists."); + Log.Information("-x Overwrite output archive or extracted files if it already exists."); overwrite = true; break; default: diff --git a/tiny7zTool/tiny7zTool.csproj b/tiny7zTool/tiny7zTool.csproj index c311616..8970479 100644 --- a/tiny7zTool/tiny7zTool.csproj +++ b/tiny7zTool/tiny7zTool.csproj @@ -56,6 +56,14 @@ + + 3.3.3 + + + 4.0.2 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + 2.8.0 @@ -66,5 +74,8 @@ 3.1.0 + + + \ No newline at end of file