From 54a10a48fed966a0fc3c31d947cc748b9819557a Mon Sep 17 00:00:00 2001 From: Julien Caillon Date: Wed, 17 Oct 2018 14:28:45 +0200 Subject: [PATCH] ready for release --- CabinetManager/CabEventType.cs | 17 +++- CabinetManager/ICabProgressionEventArgs.cs | 1 + CabinetManager/core/CfCabinet.cs | 28 +----- CabinetManager/core/CfData.cs | 17 +--- CabinetManager/core/CfFolder.cs | 11 --- .../internal/CabProgressionEventArgs.cs | 2 +- CabinetManagerTest/Tests/ArchiveTest.cs | 2 +- CabinetManagerTest/Tests/FileInCab.cs | 2 +- CabinetManagerTest/UsageExample.cs | 84 ++++++++++++++++ README.md | 99 +++++++++++++++++++ 10 files changed, 206 insertions(+), 57 deletions(-) diff --git a/CabinetManager/CabEventType.cs b/CabinetManager/CabEventType.cs index 9fa2df4..885cd06 100644 --- a/CabinetManager/CabEventType.cs +++ b/CabinetManager/CabEventType.cs @@ -6,17 +6,26 @@ public enum CabEventType : byte { /// - /// Published when the archive process progresses. + /// Published when the archive process progresses, as the cabinet is written on the disc. /// + /// + /// This event is published for each chunk of data written on the disc when the cabinet is actually saved. + /// The will indicate which file is currently processed. + /// GlobalProgression, /// - /// Published when a file has been completed. + /// Published when a file has been processed, this can be used to determine which files are actually processed. /// - FileCompleted, + /// + /// This event does NOT mean the file is actually stored in the cabinet file on the disc. + /// This is only to inform that the file has been processed and will be saved in the cabinet file. + /// Use the to follow the actual writing on disc. + /// + FileProcessed, /// - /// Published when a cabinet has been completed. + /// Published when a cabinet has been completed and is saved on disc. /// CabinetCompleted } diff --git a/CabinetManager/ICabProgressionEventArgs.cs b/CabinetManager/ICabProgressionEventArgs.cs index 24a3a2e..1e00112 100644 --- a/CabinetManager/ICabProgressionEventArgs.cs +++ b/CabinetManager/ICabProgressionEventArgs.cs @@ -4,6 +4,7 @@ /// Sent through the event. /// public interface ICabProgressionEventArgs { + /// /// The type of event. /// diff --git a/CabinetManager/core/CfCabinet.cs b/CabinetManager/core/CfCabinet.cs index ff0e16c..a8e5a92 100644 --- a/CabinetManager/core/CfCabinet.cs +++ b/CabinetManager/core/CfCabinet.cs @@ -33,7 +33,6 @@ public CfCabinet(string cabPath, CancellationToken? cancelToken) { } public void Dispose() { - NextCabinet?.Dispose(); _reader?.Dispose(); } @@ -235,16 +234,11 @@ private uint HeaderLength { /// public bool Exists => File.Exists(CabPath); - /// - /// The next cabinet instance - /// - internal CfCabinet NextCabinet { get; private set; } - /// /// Returns the complete list of files in this cabinet (and contiguous cabinet if they exist /// /// - public IEnumerable GetFiles() => Folders.SelectMany(f => f.Files).Concat(NextCabinet?.GetFiles() ?? Enumerable.Empty()); + public IEnumerable GetFiles() => Folders.SelectMany(f => f.Files); /// /// Add a new external file to this cabinet file. @@ -352,11 +346,7 @@ public bool DeleteFile(string relativePathInCab) { int nbFileDeleted = 0; // remove existing files with the same name foreach (var folder in Folders) { - if ((nbFileDeleted += folder.Files.RemoveAll(f => f.RelativePathInCab.Equals(relativePathInCab, StringComparison.OrdinalIgnoreCase))) > 0) { - // case of a file split into several cabinets - nbFileDeleted += NextCabinet?.Folders.FirstOrDefault()?.Files.RemoveAll(f => f.RelativePathInCab.Equals(relativePathInCab, StringComparison.OrdinalIgnoreCase)) ?? 0; - break; - } + nbFileDeleted += folder.Files.RemoveAll(f => f.RelativePathInCab.Equals(relativePathInCab, StringComparison.OrdinalIgnoreCase)); } return nbFileDeleted > 0; } @@ -394,7 +384,6 @@ public void Save(CfFolderTypeCompress compressionType) { private void OpenCab() { Folders.Clear(); - NextCabinet = null; _dataHeadersRead = false; if (Exists) { _reader = new BinaryReader(File.OpenRead(CabPath)); @@ -639,19 +628,6 @@ private void ReadDataHeaders(BinaryReader reader) { _dataHeadersRead = true; } - /// - /// Returns the compressed data of the first data block of the cabinet. - /// - /// This is used to read a data block that continues on a next cabinet file. - /// - /// - internal byte[] GetFirstDataBlockCompressedData(out ushort uncompressedDataLength) { - if (Folders.Count == 0) { - throw new CfCabException($"Could not get the first data block compressed data because there are no folders in the cabinet {CabPath}."); - } - return Folders[0].GetFirstDataBlockCompressedData(_reader, out uncompressedDataLength); - } - /// /// Returns a text representation of this cabinet file /// diff --git a/CabinetManager/core/CfData.cs b/CabinetManager/core/CfData.cs index 47a8537..e56d135 100644 --- a/CabinetManager/core/CfData.cs +++ b/CabinetManager/core/CfData.cs @@ -91,23 +91,14 @@ public CfData(CfFolder parent) { public byte[] ReadUncompressedData(BinaryReader reader) { reader.BaseStream.Position = CompressedDataOffset; - byte[] completeCompressedData; - ushort completeUncompressedDataLength; - if (UncompressedDataLength == 0) { // the compressed data in this data block is only partial and should be continued in the next cabinet file - var nextCompressedData = _parent.GetNextCabinetFirstDataBlockCompressedData(out completeUncompressedDataLength); - - completeCompressedData = new byte[CompressedDataLength + nextCompressedData.Length]; - Array.Copy(nextCompressedData, 0, completeCompressedData, CompressedDataLength, nextCompressedData.Length); - - var thisBlockCompressedData = ReadCompressedData(reader); - Array.Copy(thisBlockCompressedData, completeCompressedData, thisBlockCompressedData.Length); - } else { - completeUncompressedDataLength = UncompressedDataLength; - completeCompressedData = ReadCompressedData(reader); + throw new NotImplementedException("An uncompressed length of 0 indicates a partial block data which continues on the next cabinet file but this library does not implement multi-files cabinet yet."); } + var completeUncompressedDataLength = UncompressedDataLength; + var completeCompressedData = ReadCompressedData(reader); + var uncompressedData = _parent.UncompressData(completeCompressedData); if (completeUncompressedDataLength != 0 && completeUncompressedDataLength != uncompressedData.Length) { throw new CfDataCorruptedException($"Corrupted data block, the expected uncompressed data length is {completeUncompressedDataLength} but the actual is {uncompressedData.Length}."); diff --git a/CabinetManager/core/CfFolder.cs b/CabinetManager/core/CfFolder.cs index eec802d..744127e 100644 --- a/CabinetManager/core/CfFolder.cs +++ b/CabinetManager/core/CfFolder.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text; using System.Threading; using CabinetManager.core.Exceptions; @@ -364,16 +363,6 @@ public byte[] UncompressData(byte[] compressedData) { return _dataDecompressor.DecompressData(compressedData); } - /// - /// Returns the compressed data of the first data block of the next cabinet. - /// - /// This is used to read a data block that continues on a next cabinet file. - /// - /// - internal byte[] GetNextCabinetFirstDataBlockCompressedData(out ushort uncompressedDataLength) { - return _parent.NextCabinet.GetFirstDataBlockCompressedData(out uncompressedDataLength); - } - /// /// Returns the compressed data of the first data block of the folder. /// diff --git a/CabinetManager/internal/CabProgressionEventArgs.cs b/CabinetManager/internal/CabProgressionEventArgs.cs index 2f90579..e583c87 100644 --- a/CabinetManager/internal/CabProgressionEventArgs.cs +++ b/CabinetManager/internal/CabProgressionEventArgs.cs @@ -47,7 +47,7 @@ internal class CabProgressionEventArgs : EventArgs, ICabProgressionEventArgs { internal static CabProgressionEventArgs NewCompletedFile(string cabPath, string relativePathInCab) { return new CabProgressionEventArgs { CabPath = cabPath, - EventType = CabEventType.FileCompleted, + EventType = CabEventType.FileProcessed, RelativePathInCab = relativePathInCab }; } diff --git a/CabinetManagerTest/Tests/ArchiveTest.cs b/CabinetManagerTest/Tests/ArchiveTest.cs index a6ade28..c4d1211 100644 --- a/CabinetManagerTest/Tests/ArchiveTest.cs +++ b/CabinetManagerTest/Tests/ArchiveTest.cs @@ -121,7 +121,7 @@ protected void DeleteFilesInArchive(ICabManager cabManager, List list } private void ArchiverOnOnProgress(object sender, ICabProgressionEventArgs e) { - if (e.EventType == CabEventType.FileCompleted) { + if (e.EventType == CabEventType.FileProcessed) { _nbFileFinished++; } else if (e.EventType == CabEventType.CabinetCompleted) { _nbArchiveFinished++; diff --git a/CabinetManagerTest/Tests/FileInCab.cs b/CabinetManagerTest/Tests/FileInCab.cs index d7145a2..41030df 100644 --- a/CabinetManagerTest/Tests/FileInCab.cs +++ b/CabinetManagerTest/Tests/FileInCab.cs @@ -22,7 +22,7 @@ using CabinetManager; namespace CabinetManagerTest.Tests { - public class FileInCab : IFileInCab, IFileInCabToDelete, IFileInCabToExtract, IFileToAddInCab { + public class FileInCab : IFileInCabToDelete, IFileInCabToExtract, IFileToAddInCab { public string CabPath { get; set; } public string RelativePathInCab { get; set; } public ulong SizeInBytes { get; set; } diff --git a/CabinetManagerTest/UsageExample.cs b/CabinetManagerTest/UsageExample.cs index 7be44e0..d8d381a 100644 --- a/CabinetManagerTest/UsageExample.cs +++ b/CabinetManagerTest/UsageExample.cs @@ -20,13 +20,97 @@ #endregion +using System; +using System.Collections.Generic; +using System.Linq; +using CabinetManager; + namespace CabinetManagerTest { + public class UsageExample { + public static int Main_(string[] args) { + var cabManager = CabManager.New(); + + cabManager.SetCompressionLevel(CabCompressionLevel.None); + cabManager.SetCancellationToken(null); + cabManager.OnProgress += CabManagerOnProgress; + + // Add files to a new or existing cabinet + var nbProcessed = cabManager.PackFileSet(new List { + CabFile.NewToPack(@"archive.cab", @"folder\file.txt", @"my_source_file.txt") + }); + + Console.WriteLine($" -> {nbProcessed} files were added to a cabinet."); + + // List all the files in a cabinet + var filesInCab = cabManager.ListFiles(@"archive.cab").ToList(); + + Console.WriteLine("Listing files:"); + foreach (var fileInCab in filesInCab) { + Console.WriteLine($"{fileInCab.RelativePathInCab}: {fileInCab.LastWriteTime}, {fileInCab.SizeInBytes}"); + } + + // Extract files to external paths + nbProcessed = cabManager.ExtractFileSet(new List { + CabFile.NewToExtract(@"archive.cab", @"folder\file.txt", @"extraction_path.txt") + }); + + Console.WriteLine($" -> {nbProcessed} files were extracted from a cabinet."); + + // Delete files in a cabinet + nbProcessed = cabManager.DeleteFileSet(filesInCab.Select(f => CabFile.NewToDelete(f.CabPath, f.RelativePathInCab))); + Console.WriteLine($" -> {nbProcessed} files were deleted from a cabinet."); return 0; } + + private static void CabManagerOnProgress(object sender, ICabProgressionEventArgs e) { + switch (e.EventType) { + case CabEventType.GlobalProgression: + Console.WriteLine($"Global progression : {e.PercentageDone}%, current file is {e.RelativePathInCab}"); + break; + case CabEventType.FileProcessed: + Console.WriteLine($"New file processed : {e.RelativePathInCab}"); + break; + case CabEventType.CabinetCompleted: + Console.WriteLine($"New cabinet completed : {e.CabPath}"); + break; + } + } + + private class CabFile : IFileInCabToDelete, IFileInCabToExtract, IFileToAddInCab { + + public string CabPath { get; private set; } + public string RelativePathInCab { get; private set; } + public string ExtractionPath { get; private set; } + public string SourcePath { get; private set; } + + public static CabFile NewToPack(string cabPath, string relativePathInCab, string sourcePath) { + return new CabFile { + CabPath = cabPath, + RelativePathInCab = relativePathInCab, + SourcePath = sourcePath + }; + } + + public static CabFile NewToExtract(string cabPath, string relativePathInCab, string extractionPath) { + return new CabFile { + CabPath = cabPath, + RelativePathInCab = relativePathInCab, + ExtractionPath = extractionPath + }; + } + + public static CabFile NewToDelete(string cabPath, string relativePathInCab) { + return new CabFile { + CabPath = cabPath, + RelativePathInCab = relativePathInCab + }; + } + + } } } \ No newline at end of file diff --git a/README.md b/README.md index 950e38e..20a8ef3 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,105 @@ The limitations are listed below: - Does not handle compressed data, you can only store/retrieve uncompressed data - Does not compute nor verify checksums +### Usage example + +```csharp +using System; +using System.Collections.Generic; +using System.Linq; +using CabinetManager; + +namespace CabinetManagerTest { + + public class Program { + + public static int Main(string[] args) { + + var cabManager = CabManager.New(); + + cabManager.SetCompressionLevel(CabCompressionLevel.None); + cabManager.SetCancellationToken(null); + cabManager.OnProgress += CabManagerOnProgress; + + // Add files to a new or existing cabinet + var nbProcessed = cabManager.PackFileSet(new List { + CabFile.NewToPack(@"archive.cab", @"folder\file.txt", @"my_source_file.txt") + }); + + Console.WriteLine($" -> {nbProcessed} files were added to a cabinet."); + + // List all the files in a cabinet + var filesInCab = cabManager.ListFiles(@"archive.cab").ToList(); + + Console.WriteLine("Listing files:"); + foreach (var fileInCab in filesInCab) { + Console.WriteLine($"{fileInCab.RelativePathInCab}: {fileInCab.LastWriteTime}, {fileInCab.SizeInBytes}"); + } + + // Extract files to external paths + nbProcessed = cabManager.ExtractFileSet(new List { + CabFile.NewToExtract(@"archive.cab", @"folder\file.txt", @"extraction_path.txt") + }); + + Console.WriteLine($" -> {nbProcessed} files were extracted from a cabinet."); + + // Delete files in a cabinet + nbProcessed = cabManager.DeleteFileSet(filesInCab.Select(f => CabFile.NewToDelete(f.CabPath, f.RelativePathInCab))); + + Console.WriteLine($" -> {nbProcessed} files were deleted from a cabinet."); + + return 0; + } + + private static void CabManagerOnProgress(object sender, ICabProgressionEventArgs e) { + switch (e.EventType) { + case CabEventType.GlobalProgression: + Console.WriteLine($"Global progression : {e.PercentageDone}%, current file is {e.RelativePathInCab}"); + break; + case CabEventType.FileProcessed: + Console.WriteLine($"New file processed : {e.RelativePathInCab}"); + break; + case CabEventType.CabinetCompleted: + Console.WriteLine($"New cabinet completed : {e.CabPath}"); + break; + } + } + + private class CabFile : IFileInCabToDelete, IFileInCabToExtract, IFileToAddInCab { + + public string CabPath { get; private set; } + public string RelativePathInCab { get; private set; } + public string ExtractionPath { get; private set; } + public string SourcePath { get; private set; } + + public static CabFile NewToPack(string cabPath, string relativePathInCab, string sourcePath) { + return new CabFile { + CabPath = cabPath, + RelativePathInCab = relativePathInCab, + SourcePath = sourcePath + }; + } + + public static CabFile NewToExtract(string cabPath, string relativePathInCab, string extractionPath) { + return new CabFile { + CabPath = cabPath, + RelativePathInCab = relativePathInCab, + ExtractionPath = extractionPath + }; + } + + public static CabFile NewToDelete(string cabPath, string relativePathInCab) { + return new CabFile { + CabPath = cabPath, + RelativePathInCab = relativePathInCab + }; + } + + } + } +} +``` + ## Thanks This project was developped using an opensource license of the [rider by jetbrains](https://www.jetbrains.com/).