AbstractReader.cs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using SharpCompress.Common;
  6. namespace SharpCompress.Readers
  7. {
  8. /// <summary>
  9. /// A generic push reader that reads unseekable comrpessed streams.
  10. /// </summary>
  11. public abstract class AbstractReader<TEntry, TVolume> : IReader, IReaderExtractionListener
  12. where TEntry : Entry
  13. where TVolume : Volume
  14. {
  15. private bool completed;
  16. private IEnumerator<TEntry> entriesForCurrentReadStream;
  17. private bool wroteCurrentEntry;
  18. public event EventHandler<ReaderExtractionEventArgs<IEntry>> EntryExtractionProgress;
  19. public event EventHandler<CompressedBytesReadEventArgs> CompressedBytesRead;
  20. public event EventHandler<FilePartExtractionBeginEventArgs> FilePartExtractionBegin;
  21. internal AbstractReader(ReaderOptions options, ArchiveType archiveType)
  22. {
  23. ArchiveType = archiveType;
  24. Options = options;
  25. }
  26. internal ReaderOptions Options { get; }
  27. public ArchiveType ArchiveType { get; }
  28. /// <summary>
  29. /// Current volume that the current entry resides in
  30. /// </summary>
  31. public abstract TVolume Volume { get; }
  32. /// <summary>
  33. /// Current file entry
  34. /// </summary>
  35. public TEntry Entry => entriesForCurrentReadStream.Current;
  36. #region IDisposable Members
  37. public void Dispose()
  38. {
  39. entriesForCurrentReadStream?.Dispose();
  40. Volume?.Dispose();
  41. }
  42. #endregion
  43. public bool Cancelled { get; private set; }
  44. /// <summary>
  45. /// Indicates that the remaining entries are not required.
  46. /// On dispose of an EntryStream, the stream will not skip to the end of the entry.
  47. /// An attempt to move to the next entry will throw an exception, as the compressed stream is not positioned at an entry boundary.
  48. /// </summary>
  49. public void Cancel()
  50. {
  51. if (!completed)
  52. {
  53. Cancelled = true;
  54. }
  55. }
  56. public bool MoveToNextEntry()
  57. {
  58. if (completed)
  59. {
  60. return false;
  61. }
  62. if (Cancelled)
  63. {
  64. throw new InvalidOperationException("Reader has been cancelled.");
  65. }
  66. if (entriesForCurrentReadStream == null)
  67. {
  68. return LoadStreamForReading(RequestInitialStream());
  69. }
  70. if (!wroteCurrentEntry)
  71. {
  72. SkipEntry();
  73. }
  74. wroteCurrentEntry = false;
  75. if (NextEntryForCurrentStream())
  76. {
  77. return true;
  78. }
  79. completed = true;
  80. return false;
  81. }
  82. protected bool LoadStreamForReading(Stream stream)
  83. {
  84. entriesForCurrentReadStream?.Dispose();
  85. if ((stream == null) || (!stream.CanRead))
  86. {
  87. throw new MultipartStreamRequiredException("File is split into multiple archives: '"
  88. + Entry.Key +
  89. "'. A new readable stream is required. Use Cancel if it was intended.");
  90. }
  91. entriesForCurrentReadStream = GetEntries(stream).GetEnumerator();
  92. return entriesForCurrentReadStream.MoveNext();
  93. }
  94. protected virtual Stream RequestInitialStream()
  95. {
  96. return Volume.Stream;
  97. }
  98. internal virtual bool NextEntryForCurrentStream()
  99. {
  100. return entriesForCurrentReadStream.MoveNext();
  101. }
  102. protected abstract IEnumerable<TEntry> GetEntries(Stream stream);
  103. #region Entry Skip/Write
  104. private void SkipEntry()
  105. {
  106. if (!Entry.IsDirectory)
  107. {
  108. Skip();
  109. }
  110. }
  111. private void Skip()
  112. {
  113. if (ArchiveType != ArchiveType.Rar
  114. && !Entry.IsSolid
  115. && Entry.CompressedSize > 0)
  116. {
  117. //not solid and has a known compressed size then we can skip raw bytes.
  118. var part = Entry.Parts.First();
  119. var rawStream = part.GetRawStream();
  120. if (rawStream != null)
  121. {
  122. var bytesToAdvance = Entry.CompressedSize;
  123. rawStream.Skip(bytesToAdvance);
  124. part.Skipped = true;
  125. return;
  126. }
  127. }
  128. //don't know the size so we have to try to decompress to skip
  129. using (var s = OpenEntryStream())
  130. {
  131. s.Skip();
  132. }
  133. }
  134. public void WriteEntryTo(Stream writableStream)
  135. {
  136. if (wroteCurrentEntry)
  137. {
  138. throw new ArgumentException("WriteEntryTo or OpenEntryStream can only be called once.");
  139. }
  140. if ((writableStream == null) || (!writableStream.CanWrite))
  141. {
  142. throw new ArgumentNullException("A writable Stream was required. Use Cancel if that was intended.");
  143. }
  144. Write(writableStream);
  145. wroteCurrentEntry = true;
  146. }
  147. internal void Write(Stream writeStream)
  148. {
  149. var streamListener = this as IReaderExtractionListener;
  150. using (Stream s = OpenEntryStream())
  151. {
  152. s.TransferTo(writeStream, Entry, streamListener);
  153. }
  154. }
  155. public EntryStream OpenEntryStream()
  156. {
  157. if (wroteCurrentEntry)
  158. {
  159. throw new ArgumentException("WriteEntryTo or OpenEntryStream can only be called once.");
  160. }
  161. var stream = GetEntryStream();
  162. wroteCurrentEntry = true;
  163. return stream;
  164. }
  165. /// <summary>
  166. /// Retains a reference to the entry stream, so we can check whether it completed later.
  167. /// </summary>
  168. protected EntryStream CreateEntryStream(Stream decompressed)
  169. {
  170. return new EntryStream(this, decompressed);
  171. }
  172. protected virtual EntryStream GetEntryStream()
  173. {
  174. return CreateEntryStream(Entry.Parts.First().GetCompressedStream());
  175. }
  176. #endregion
  177. IEntry IReader.Entry => Entry;
  178. void IExtractionListener.FireCompressedBytesRead(long currentPartCompressedBytes, long compressedReadBytes)
  179. {
  180. CompressedBytesRead?.Invoke(this, new CompressedBytesReadEventArgs
  181. {
  182. CurrentFilePartCompressedBytesRead = currentPartCompressedBytes,
  183. CompressedBytesRead = compressedReadBytes
  184. });
  185. }
  186. void IExtractionListener.FireFilePartExtractionBegin(string name, long size, long compressedSize)
  187. {
  188. FilePartExtractionBegin?.Invoke(this, new FilePartExtractionBeginEventArgs
  189. {
  190. CompressedSize = compressedSize,
  191. Size = size,
  192. Name = name
  193. });
  194. }
  195. void IReaderExtractionListener.FireEntryExtractionProgress(Entry entry, long bytesTransferred, int iterations)
  196. {
  197. EntryExtractionProgress?.Invoke(this, new ReaderExtractionEventArgs<IEntry>(entry, new ReaderProgress(entry, bytesTransferred, iterations)));
  198. }
  199. }
  200. }