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