StreamWrappers.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. namespace SevenZip
  2. {
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Globalization;
  6. using System.IO;
  7. using System.Runtime.InteropServices;
  8. #if UNMANAGED
  9. /// <summary>
  10. /// A class that has DisposeStream property.
  11. /// </summary>
  12. internal class DisposeVariableWrapper
  13. {
  14. public bool DisposeStream { protected get; set; }
  15. protected DisposeVariableWrapper(bool disposeStream) { DisposeStream = disposeStream; }
  16. }
  17. /// <summary>
  18. /// Stream wrapper used in InStreamWrapper
  19. /// </summary>
  20. internal class StreamWrapper : DisposeVariableWrapper, IDisposable
  21. {
  22. /// <summary>
  23. /// File name associated with the stream (for date fix)
  24. /// </summary>
  25. private readonly string _fileName;
  26. private readonly DateTime _fileTime;
  27. /// <summary>
  28. /// Worker stream for reading, writing and seeking.
  29. /// </summary>
  30. private Stream _baseStream;
  31. /// <summary>
  32. /// Initializes a new instance of the StreamWrapper class
  33. /// </summary>
  34. /// <param name="baseStream">Worker stream for reading, writing and seeking</param>
  35. /// <param name="fileName">File name associated with the stream (for attributes fix)</param>
  36. /// <param name="time">File last write time (for attributes fix)</param>
  37. /// <param name="disposeStream">Indicates whether to dispose the baseStream</param>
  38. protected StreamWrapper(Stream baseStream, string fileName, DateTime time, bool disposeStream)
  39. : base(disposeStream)
  40. {
  41. _baseStream = baseStream;
  42. _fileName = fileName;
  43. _fileTime = time;
  44. }
  45. /// <summary>
  46. /// Initializes a new instance of the StreamWrapper class
  47. /// </summary>
  48. /// <param name="baseStream">Worker stream for reading, writing and seeking</param>
  49. /// <param name="disposeStream">Indicates whether to dispose the baseStream</param>
  50. protected StreamWrapper(Stream baseStream, bool disposeStream)
  51. : base(disposeStream)
  52. {
  53. _baseStream = baseStream;
  54. }
  55. /// <summary>
  56. /// Gets the worker stream for reading, writing and seeking.
  57. /// </summary>
  58. protected Stream BaseStream => _baseStream;
  59. #region IDisposable Members
  60. /// <summary>
  61. /// Cleans up any resources used and fixes file attributes.
  62. /// </summary>
  63. public void Dispose()
  64. {
  65. if (_baseStream != null && DisposeStream)
  66. {
  67. try
  68. {
  69. _baseStream.Dispose();
  70. }
  71. catch (ObjectDisposedException) { }
  72. _baseStream = null;
  73. }
  74. if (!string.IsNullOrEmpty(_fileName) && File.Exists(_fileName))
  75. {
  76. try
  77. {
  78. File.SetLastWriteTime(_fileName, _fileTime);
  79. File.SetLastAccessTime(_fileName, _fileTime);
  80. File.SetCreationTime(_fileName, _fileTime);
  81. }
  82. catch (ArgumentOutOfRangeException) {}
  83. }
  84. GC.SuppressFinalize(this);
  85. }
  86. #endregion
  87. public virtual void Seek(long offset, SeekOrigin seekOrigin, IntPtr newPosition)
  88. {
  89. if (BaseStream != null)
  90. {
  91. long position = BaseStream.Seek(offset, seekOrigin);
  92. if (newPosition != IntPtr.Zero)
  93. {
  94. Marshal.WriteInt64(newPosition, position);
  95. }
  96. }
  97. }
  98. }
  99. /// <summary>
  100. /// IInStream wrapper used in stream read operations.
  101. /// </summary>
  102. internal sealed class InStreamWrapper : StreamWrapper, ISequentialInStream, IInStream
  103. {
  104. /// <summary>
  105. /// Initializes a new instance of the InStreamWrapper class.
  106. /// </summary>
  107. /// <param name="baseStream">Stream for writing data</param>
  108. /// <param name="disposeStream">Indicates whether to dispose the baseStream</param>
  109. public InStreamWrapper(Stream baseStream, bool disposeStream) : base(baseStream, disposeStream) { }
  110. #region ISequentialInStream Members
  111. /// <summary>
  112. /// Reads data from the stream.
  113. /// </summary>
  114. /// <param name="data">A data array.</param>
  115. /// <param name="size">The array size.</param>
  116. /// <returns>The read bytes count.</returns>
  117. public int Read(byte[] data, uint size)
  118. {
  119. int readCount = 0;
  120. if (BaseStream != null)
  121. {
  122. readCount = BaseStream.Read(data, 0, (int) size);
  123. if (readCount > 0)
  124. {
  125. OnBytesRead(new IntEventArgs(readCount));
  126. }
  127. }
  128. return readCount;
  129. }
  130. #endregion
  131. /// <summary>
  132. /// Occurs when IntEventArgs.Value bytes were read from the source.
  133. /// </summary>
  134. public event EventHandler<IntEventArgs> BytesRead;
  135. private void OnBytesRead(IntEventArgs e)
  136. {
  137. BytesRead?.Invoke(this, e);
  138. }
  139. }
  140. /// <summary>
  141. /// IOutStream wrapper used in stream write operations.
  142. /// </summary>
  143. internal sealed class OutStreamWrapper : StreamWrapper, ISequentialOutStream, IOutStream
  144. {
  145. /// <summary>
  146. /// Initializes a new instance of the OutStreamWrapper class
  147. /// </summary>
  148. /// <param name="baseStream">Stream for writing data</param>
  149. /// <param name="fileName">File name (for attributes fix)</param>
  150. /// <param name="time">Time of the file creation (for attributes fix)</param>
  151. /// <param name="disposeStream">Indicates whether to dispose the baseStream</param>
  152. public OutStreamWrapper(Stream baseStream, string fileName, DateTime time, bool disposeStream) :
  153. base(baseStream, fileName, time, disposeStream) {}
  154. /// <summary>
  155. /// Initializes a new instance of the OutStreamWrapper class
  156. /// </summary>
  157. /// <param name="baseStream">Stream for writing data</param>
  158. /// <param name="disposeStream">Indicates whether to dispose the baseStream</param>
  159. public OutStreamWrapper(Stream baseStream, bool disposeStream) :
  160. base(baseStream, disposeStream) {}
  161. #region IOutStream Members
  162. public int SetSize(long newSize)
  163. {
  164. BaseStream.SetLength(newSize);
  165. return 0;
  166. }
  167. #endregion
  168. #region ISequentialOutStream Members
  169. /// <summary>
  170. /// Writes data to the stream
  171. /// </summary>
  172. /// <param name="data">Data array</param>
  173. /// <param name="size">Array size</param>
  174. /// <param name="processedSize">Count of written bytes</param>
  175. /// <returns>Zero if Ok</returns>
  176. public int Write(byte[] data, uint size, IntPtr processedSize)
  177. {
  178. BaseStream.Write(data, 0, (int) size);
  179. if (processedSize != IntPtr.Zero)
  180. {
  181. Marshal.WriteInt32(processedSize, (int) size);
  182. }
  183. OnBytesWritten(new IntEventArgs((int) size));
  184. return 0;
  185. }
  186. #endregion
  187. /// <summary>
  188. /// Occurs when IntEventArgs.Value bytes were written.
  189. /// </summary>
  190. public event EventHandler<IntEventArgs> BytesWritten;
  191. private void OnBytesWritten(IntEventArgs e)
  192. {
  193. BytesWritten?.Invoke(this, e);
  194. }
  195. }
  196. /// <summary>
  197. /// Base multi volume stream wrapper class.
  198. /// </summary>
  199. internal class MultiStreamWrapper : DisposeVariableWrapper, IDisposable
  200. {
  201. protected readonly Dictionary<int, KeyValuePair<long, long>> StreamOffsets = new Dictionary<int, KeyValuePair<long, long>>();
  202. protected readonly List<Stream> Streams = new List<Stream>();
  203. protected int CurrentStream;
  204. protected long Position;
  205. protected long StreamLength;
  206. /// <summary>
  207. /// Initializes a new instance of the MultiStreamWrapper class.
  208. /// </summary>
  209. /// <param name="dispose">Perform Dispose() if requested to.</param>
  210. protected MultiStreamWrapper(bool dispose) : base(dispose) {}
  211. /// <summary>
  212. /// Gets the total length of input data.
  213. /// </summary>
  214. public long Length => StreamLength;
  215. #region IDisposable Members
  216. /// <summary>
  217. /// Cleans up any resources used and fixes file attributes.
  218. /// </summary>
  219. public virtual void Dispose()
  220. {
  221. if (DisposeStream)
  222. {
  223. foreach (Stream stream in Streams)
  224. {
  225. try
  226. {
  227. stream.Dispose();
  228. }
  229. catch (ObjectDisposedException) {}
  230. }
  231. Streams.Clear();
  232. }
  233. GC.SuppressFinalize(this);
  234. }
  235. #endregion
  236. protected static string VolumeNumber(int num)
  237. {
  238. string prefix;
  239. if (num < 10)
  240. {
  241. prefix = ".00";
  242. }
  243. else if (num < 100)
  244. {
  245. prefix = ".0";
  246. }
  247. else
  248. {
  249. prefix = ".";
  250. }
  251. return prefix + num.ToString(CultureInfo.InvariantCulture);
  252. }
  253. private int StreamNumberByOffset(long offset)
  254. {
  255. foreach (int number in StreamOffsets.Keys)
  256. {
  257. if (StreamOffsets[number].Key <= offset &&
  258. StreamOffsets[number].Value >= offset)
  259. {
  260. return number;
  261. }
  262. }
  263. return -1;
  264. }
  265. public void Seek(long offset, SeekOrigin seekOrigin, IntPtr newPosition)
  266. {
  267. long absolutePosition;
  268. switch (seekOrigin) {
  269. case SeekOrigin.Begin:
  270. absolutePosition = offset;
  271. break;
  272. case SeekOrigin.Current:
  273. absolutePosition = Position + offset;
  274. break;
  275. case SeekOrigin.End:
  276. absolutePosition = Length + offset;
  277. break;
  278. default:
  279. throw new ArgumentOutOfRangeException(nameof(seekOrigin));
  280. }
  281. CurrentStream = StreamNumberByOffset(absolutePosition);
  282. long delta = Streams[CurrentStream].Seek(
  283. absolutePosition - StreamOffsets[CurrentStream].Key, SeekOrigin.Begin);
  284. Position = StreamOffsets[CurrentStream].Key + delta;
  285. if (newPosition != IntPtr.Zero)
  286. {
  287. Marshal.WriteInt64(newPosition, Position);
  288. }
  289. }
  290. }
  291. /// <summary>
  292. /// IInStream wrapper used in stream multi volume read operations.
  293. /// </summary>
  294. internal sealed class InMultiStreamWrapper : MultiStreamWrapper, ISequentialInStream, IInStream
  295. {
  296. /// <summary>
  297. /// Initializes a new instance of the InMultiStreamWrapper class.
  298. /// </summary>
  299. /// <param name="fileName">The archive file name.</param>
  300. /// <param name="dispose">Perform Dispose() if requested to.</param>
  301. public InMultiStreamWrapper(string fileName, bool dispose) :
  302. base(dispose)
  303. {
  304. string baseName = fileName.Substring(0, fileName.Length - 4);
  305. int i = 0;
  306. while (File.Exists(fileName))
  307. {
  308. Streams.Add(new FileStream(fileName, FileMode.Open, FileAccess.Read));
  309. long length = Streams[i].Length;
  310. StreamOffsets.Add(i++, new KeyValuePair<long, long>(StreamLength, StreamLength + length));
  311. StreamLength += length;
  312. fileName = baseName + VolumeNumber(i + 1);
  313. }
  314. }
  315. #region ISequentialInStream Members
  316. /// <summary>
  317. /// Reads data from the stream.
  318. /// </summary>
  319. /// <param name="data">A data array.</param>
  320. /// <param name="size">The array size.</param>
  321. /// <returns>The read bytes count.</returns>
  322. public int Read(byte[] data, uint size)
  323. {
  324. var readSize = (int) size;
  325. int readCount = Streams[CurrentStream].Read(data, 0, readSize);
  326. readSize -= readCount;
  327. Position += readCount;
  328. while (readCount < (int) size)
  329. {
  330. if (CurrentStream == Streams.Count - 1)
  331. {
  332. return readCount;
  333. }
  334. CurrentStream++;
  335. Streams[CurrentStream].Seek(0, SeekOrigin.Begin);
  336. int count = Streams[CurrentStream].Read(data, readCount, readSize);
  337. readCount += count;
  338. readSize -= count;
  339. Position += count;
  340. }
  341. return readCount;
  342. }
  343. #endregion
  344. }
  345. /// <summary>
  346. /// IOutStream wrapper used in multi volume stream write operations.
  347. /// </summary>
  348. internal sealed class OutMultiStreamWrapper : MultiStreamWrapper, ISequentialOutStream, IOutStream
  349. {
  350. private readonly string _archiveName;
  351. private readonly long _volumeSize;
  352. private long _overallLength;
  353. /// <summary>
  354. /// Initializes a new instance of the OutMultiStreamWrapper class.
  355. /// </summary>
  356. /// <param name="archiveName">The archive name.</param>
  357. /// <param name="volumeSize">The volume size.</param>
  358. public OutMultiStreamWrapper(string archiveName, long volumeSize) :
  359. base(true)
  360. {
  361. _archiveName = archiveName;
  362. _volumeSize = volumeSize;
  363. CurrentStream = -1;
  364. NewVolumeStream();
  365. }
  366. #region IOutStream Members
  367. public int SetSize(long newSize)
  368. {
  369. return 0;
  370. }
  371. #endregion
  372. #region ISequentialOutStream Members
  373. public int Write(byte[] data, uint size, IntPtr processedSize)
  374. {
  375. int offset = 0;
  376. var originalSize = (int) size;
  377. Position += size;
  378. _overallLength = Math.Max(Position + 1, _overallLength);
  379. while (size > _volumeSize - Streams[CurrentStream].Position)
  380. {
  381. var count = (int) (_volumeSize - Streams[CurrentStream].Position);
  382. Streams[CurrentStream].Write(data, offset, count);
  383. size -= (uint) count;
  384. offset += count;
  385. NewVolumeStream();
  386. }
  387. Streams[CurrentStream].Write(data, offset, (int) size);
  388. if (processedSize != IntPtr.Zero)
  389. {
  390. Marshal.WriteInt32(processedSize, originalSize);
  391. }
  392. return 0;
  393. }
  394. #endregion
  395. public override void Dispose()
  396. {
  397. int lastIndex = Streams.Count - 1;
  398. Streams[lastIndex].SetLength(lastIndex > 0? Streams[lastIndex].Position : _overallLength);
  399. base.Dispose();
  400. }
  401. private void NewVolumeStream()
  402. {
  403. CurrentStream++;
  404. Streams.Add(File.Create(_archiveName + VolumeNumber(CurrentStream + 1)));
  405. Streams[CurrentStream].SetLength(_volumeSize);
  406. StreamOffsets.Add(CurrentStream, new KeyValuePair<long, long>(0, _volumeSize - 1));
  407. }
  408. }
  409. internal sealed class FakeOutStreamWrapper : ISequentialOutStream, IDisposable
  410. {
  411. #region IDisposable Members
  412. public void Dispose()
  413. {
  414. GC.SuppressFinalize(this);
  415. }
  416. #endregion
  417. #region ISequentialOutStream Members
  418. /// <summary>
  419. /// Does nothing except calling the BytesWritten event
  420. /// </summary>
  421. /// <param name="data">Data array</param>
  422. /// <param name="size">Array size</param>
  423. /// <param name="processedSize">Count of written bytes</param>
  424. /// <returns>Zero if Ok</returns>
  425. public int Write(byte[] data, uint size, IntPtr processedSize)
  426. {
  427. OnBytesWritten(new IntEventArgs((int) size));
  428. if (processedSize != IntPtr.Zero)
  429. {
  430. Marshal.WriteInt32(processedSize, (int) size);
  431. }
  432. return 0;
  433. }
  434. #endregion
  435. /// <summary>
  436. /// Occurs when IntEventArgs.Value bytes were written
  437. /// </summary>
  438. public event EventHandler<IntEventArgs> BytesWritten;
  439. private void OnBytesWritten(IntEventArgs e)
  440. {
  441. BytesWritten?.Invoke(this, e);
  442. }
  443. }
  444. #endif
  445. }