From afff386622402efcd815184f46010e19502c82d8 Mon Sep 17 00:00:00 2001 From: Paul Newman Date: Wed, 15 Jul 2015 13:44:20 +0100 Subject: [PATCH 1/2] Skip entry stream on dispose Until now the caller had to completely consume each entry stream, or call SkipEntry(), before disposing the stream. If not, exception was thrown: "EntryStream has not been fully consumed". Hugely inconvenient; a user-thrown exception inside a "using (EntryStream)" block would be discarded. Now automatically skips the entry on dispose. Added method EntryStream.Cancel(). Call this if entry stream is unfinished, and no further entries are required. Helps with efficiency, as it avoids reading data that is not needed. --- .../Archive/SevenZip/SevenZipArchive.cs | 2 +- SharpCompress/Common/EntryStream.cs | 21 ++++++++++++++++--- SharpCompress/Reader/AbstractReader.cs | 18 +++++++++++++++- SharpCompress/Reader/Rar/RarReader.cs | 2 +- 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/SharpCompress/Archive/SevenZip/SevenZipArchive.cs b/SharpCompress/Archive/SevenZip/SevenZipArchive.cs index 14792f67..3af441e0 100644 --- a/SharpCompress/Archive/SevenZip/SevenZipArchive.cs +++ b/SharpCompress/Archive/SevenZip/SevenZipArchive.cs @@ -236,7 +236,7 @@ namespace SharpCompress.Archive.SevenZip protected override EntryStream GetEntryStream() { - return new EntryStream(new ReadOnlySubStream(currentStream, currentItem.Size)); + return CreateEntryStream(new ReadOnlySubStream(currentStream, currentItem.Size)); } } } diff --git a/SharpCompress/Common/EntryStream.cs b/SharpCompress/Common/EntryStream.cs index 606bb160..a14bbbbb 100644 --- a/SharpCompress/Common/EntryStream.cs +++ b/SharpCompress/Common/EntryStream.cs @@ -26,12 +26,27 @@ namespace SharpCompress.Common completed = true; } + public bool Cancelled { get; private set; } + + /// + /// Indicates that the remainder of the stream is not required. + /// On dispose, the entry will not be skipped, so it helps with efficiency. + /// The downside is that subsequent entries are not usable, as the compressed stream is not positioned at an entry boundary. + /// + public void Cancel() + { + if (!completed) + { + Cancelled = true; + stream.Close(); + } + } + protected override void Dispose(bool disposing) { - if (!completed) + if (!(completed || Cancelled)) { - throw new InvalidOperationException( - "EntryStream has not been fully consumed. Read the entire stream or use SkipEntry."); + SkipEntry(); } if (isDisposed) { diff --git a/SharpCompress/Reader/AbstractReader.cs b/SharpCompress/Reader/AbstractReader.cs index 0c98e032..bd940ce8 100644 --- a/SharpCompress/Reader/AbstractReader.cs +++ b/SharpCompress/Reader/AbstractReader.cs @@ -77,6 +77,12 @@ namespace SharpCompress.Reader { return LoadStreamForReading(RequestInitialStream()); } + + if (currentEntryStream != null && currentEntryStream.Cancelled) + { + throw new InvalidOperationException("EntryStream has not been fully consumed. Read the entire stream or use SkipEntry."); + } + if (!wroteCurrentEntry) { SkipEntry(); @@ -197,9 +203,19 @@ namespace SharpCompress.Reader return stream; } + private EntryStream currentEntryStream; + + /// + /// Retains a reference to the entry stream, so we can check whether it completed later. + /// + protected EntryStream CreateEntryStream(Stream decompressed) + { + return currentEntryStream = new EntryStream(decompressed); + } + protected virtual EntryStream GetEntryStream() { - return new EntryStream(Entry.Parts.First().GetCompressedStream()); + return CreateEntryStream(Entry.Parts.First().GetCompressedStream()); } #endregion diff --git a/SharpCompress/Reader/Rar/RarReader.cs b/SharpCompress/Reader/Rar/RarReader.cs index 68ee8b2f..58a1be35 100644 --- a/SharpCompress/Reader/Rar/RarReader.cs +++ b/SharpCompress/Reader/Rar/RarReader.cs @@ -73,7 +73,7 @@ namespace SharpCompress.Reader.Rar protected override EntryStream GetEntryStream() { - return new EntryStream(new RarStream(pack, Entry.FileHeader, + return CreateEntryStream(new RarStream(pack, Entry.FileHeader, new MultiVolumeReadOnlyStream( CreateFilePartEnumerableForCurrentEntry().Cast(), this))); } From 8faebc78d0f22d2b63122dbfd6da698a628820b8 Mon Sep 17 00:00:00 2001 From: Paul Newman Date: Wed, 15 Jul 2015 18:13:46 +0100 Subject: [PATCH 2/2] Cancel moved from EntryStream to Reader Relates to previous commit. Following discussion with Adam, moved the Cancel() to the reader. Example: while (reader.MoveToNextEntry()) { using (var data = new StreamReader(reader.OpenEntryStream())) { try { DoSomething(data.ReadLine()); } catch { reader.Cancel(); throw; } } } --- SharpCompress/Common/EntryStream.cs | 23 +++++--------------- SharpCompress/Reader/AbstractReader.cs | 30 ++++++++++++++++++-------- SharpCompress/Reader/IReader.cs | 3 +++ 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/SharpCompress/Common/EntryStream.cs b/SharpCompress/Common/EntryStream.cs index a14bbbbb..86e50978 100644 --- a/SharpCompress/Common/EntryStream.cs +++ b/SharpCompress/Common/EntryStream.cs @@ -1,16 +1,19 @@ using System; using System.IO; +using SharpCompress.Reader; namespace SharpCompress.Common { public class EntryStream : Stream { + public IReader Reader { get; private set; } private Stream stream; private bool completed; private bool isDisposed; - internal EntryStream(Stream stream) + internal EntryStream(IReader reader, Stream stream) { + this.Reader = reader; this.stream = stream; } @@ -26,25 +29,9 @@ namespace SharpCompress.Common completed = true; } - public bool Cancelled { get; private set; } - - /// - /// Indicates that the remainder of the stream is not required. - /// On dispose, the entry will not be skipped, so it helps with efficiency. - /// The downside is that subsequent entries are not usable, as the compressed stream is not positioned at an entry boundary. - /// - public void Cancel() - { - if (!completed) - { - Cancelled = true; - stream.Close(); - } - } - protected override void Dispose(bool disposing) { - if (!(completed || Cancelled)) + if (!(completed || Reader.Cancelled)) { SkipEntry(); } diff --git a/SharpCompress/Reader/AbstractReader.cs b/SharpCompress/Reader/AbstractReader.cs index bd940ce8..699770a3 100644 --- a/SharpCompress/Reader/AbstractReader.cs +++ b/SharpCompress/Reader/AbstractReader.cs @@ -67,22 +67,36 @@ namespace SharpCompress.Reader #endregion + + public bool Cancelled { get; private set; } + + /// + /// Indicates that the remaining entries are not required. + /// On dispose of an EntryStream, the stream will not skip to the end of the entry. + /// An attempt to move to the next entry will throw an exception, as the compressed stream is not positioned at an entry boundary. + /// + public void Cancel() + { + if (!completed) + { + Cancelled = true; + } + } + public bool MoveToNextEntry() { if (completed) { return false; } + if (Cancelled) + { + throw new InvalidOperationException("Reader has been cancelled."); + } if (entriesForCurrentReadStream == null) { return LoadStreamForReading(RequestInitialStream()); } - - if (currentEntryStream != null && currentEntryStream.Cancelled) - { - throw new InvalidOperationException("EntryStream has not been fully consumed. Read the entire stream or use SkipEntry."); - } - if (!wroteCurrentEntry) { SkipEntry(); @@ -203,14 +217,12 @@ namespace SharpCompress.Reader return stream; } - private EntryStream currentEntryStream; - /// /// Retains a reference to the entry stream, so we can check whether it completed later. /// protected EntryStream CreateEntryStream(Stream decompressed) { - return currentEntryStream = new EntryStream(decompressed); + return new EntryStream(this, decompressed); } protected virtual EntryStream GetEntryStream() diff --git a/SharpCompress/Reader/IReader.cs b/SharpCompress/Reader/IReader.cs index 769c1741..cf26ea16 100644 --- a/SharpCompress/Reader/IReader.cs +++ b/SharpCompress/Reader/IReader.cs @@ -22,6 +22,9 @@ namespace SharpCompress.Reader /// void WriteEntryTo(Stream writableStream); + bool Cancelled { get; } + void Cancel(); + /// /// Moves to the next entry by reading more data from the underlying stream. This skips if data has not been read. ///