using System; using System.Collections.Generic; using System.IO; using System.Linq; using SharpCompress.Common; namespace SharpCompress.Readers { /// /// A generic push reader that reads unseekable comrpessed streams. /// public abstract class AbstractReader : IReader, IReaderExtractionListener where TEntry : Entry where TVolume : Volume { private bool completed; private IEnumerator entriesForCurrentReadStream; private bool wroteCurrentEntry; public event EventHandler> EntryExtractionProgress; public event EventHandler CompressedBytesRead; public event EventHandler FilePartExtractionBegin; internal AbstractReader(ReaderOptions options, ArchiveType archiveType) { ArchiveType = archiveType; Options = options; } internal ReaderOptions Options { get; } public ArchiveType ArchiveType { get; } /// /// Current volume that the current entry resides in /// public abstract TVolume Volume { get; } /// /// Current file entry /// public TEntry Entry => entriesForCurrentReadStream.Current; #region IDisposable Members public void Dispose() { entriesForCurrentReadStream?.Dispose(); Volume?.Dispose(); } #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 (!wroteCurrentEntry) { SkipEntry(); } wroteCurrentEntry = false; if (NextEntryForCurrentStream()) { return true; } completed = true; return false; } protected bool LoadStreamForReading(Stream stream) { entriesForCurrentReadStream?.Dispose(); if ((stream == null) || (!stream.CanRead)) { throw new MultipartStreamRequiredException("File is split into multiple archives: '" + Entry.Key + "'. A new readable stream is required. Use Cancel if it was intended."); } entriesForCurrentReadStream = GetEntries(stream).GetEnumerator(); return entriesForCurrentReadStream.MoveNext(); } protected virtual Stream RequestInitialStream() { return Volume.Stream; } internal virtual bool NextEntryForCurrentStream() { return entriesForCurrentReadStream.MoveNext(); } protected abstract IEnumerable GetEntries(Stream stream); #region Entry Skip/Write private void SkipEntry() { if (!Entry.IsDirectory) { Skip(); } } private void Skip() { if (ArchiveType != ArchiveType.Rar && !Entry.IsSolid && Entry.CompressedSize > 0) { //not solid and has a known compressed size then we can skip raw bytes. var part = Entry.Parts.First(); var rawStream = part.GetRawStream(); if (rawStream != null) { var bytesToAdvance = Entry.CompressedSize; rawStream.Skip(bytesToAdvance); part.Skipped = true; return; } } //don't know the size so we have to try to decompress to skip using (var s = OpenEntryStream()) { s.Skip(); } } public void WriteEntryTo(Stream writableStream) { if (wroteCurrentEntry) { throw new ArgumentException("WriteEntryTo or OpenEntryStream can only be called once."); } if ((writableStream == null) || (!writableStream.CanWrite)) { throw new ArgumentNullException("A writable Stream was required. Use Cancel if that was intended."); } Write(writableStream); wroteCurrentEntry = true; } internal void Write(Stream writeStream) { var streamListener = this as IReaderExtractionListener; using (Stream s = OpenEntryStream()) { s.TransferTo(writeStream, Entry, streamListener); } } public EntryStream OpenEntryStream() { if (wroteCurrentEntry) { throw new ArgumentException("WriteEntryTo or OpenEntryStream can only be called once."); } var stream = GetEntryStream(); wroteCurrentEntry = true; return stream; } /// /// Retains a reference to the entry stream, so we can check whether it completed later. /// protected EntryStream CreateEntryStream(Stream decompressed) { return new EntryStream(this, decompressed); } protected virtual EntryStream GetEntryStream() { return CreateEntryStream(Entry.Parts.First().GetCompressedStream()); } #endregion IEntry IReader.Entry => Entry; void IExtractionListener.FireCompressedBytesRead(long currentPartCompressedBytes, long compressedReadBytes) { CompressedBytesRead?.Invoke(this, new CompressedBytesReadEventArgs { CurrentFilePartCompressedBytesRead = currentPartCompressedBytes, CompressedBytesRead = compressedReadBytes }); } void IExtractionListener.FireFilePartExtractionBegin(string name, long size, long compressedSize) { FilePartExtractionBegin?.Invoke(this, new FilePartExtractionBeginEventArgs { CompressedSize = compressedSize, Size = size, Name = name }); } void IReaderExtractionListener.FireEntryExtractionProgress(Entry entry, long bytesTransferred, int iterations) { EntryExtractionProgress?.Invoke(this, new ReaderExtractionEventArgs(entry, new ReaderProgress(entry, bytesTransferred, iterations))); } } }