ZipSegmentedStream.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. // ZipSegmentedStream.cs
  2. // ------------------------------------------------------------------
  3. //
  4. // Copyright (c) 2009-2011 Dino Chiesa.
  5. // All rights reserved.
  6. //
  7. // This code module is part of DotNetZip, a zipfile class library.
  8. //
  9. // ------------------------------------------------------------------
  10. //
  11. // This code is licensed under the Microsoft Public License.
  12. // See the file License.txt for the license details.
  13. // More info on: http://dotnetzip.codeplex.com
  14. //
  15. // ------------------------------------------------------------------
  16. //
  17. // last saved (in emacs):
  18. // Time-stamp: <2011-July-13 22:25:45>
  19. //
  20. // ------------------------------------------------------------------
  21. //
  22. // This module defines logic for zip streams that span disk files.
  23. //
  24. // ------------------------------------------------------------------
  25. using System;
  26. using System.Collections.Generic;
  27. using System.IO;
  28. namespace Ionic.Zip
  29. {
  30. internal class ZipSegmentedStream : System.IO.Stream
  31. {
  32. enum RwMode
  33. {
  34. None = 0,
  35. ReadOnly = 1,
  36. Write = 2,
  37. //Update = 3
  38. }
  39. private RwMode rwMode;
  40. private bool _exceptionPending; // **see note below
  41. private string _baseName;
  42. private string _baseDir;
  43. //private bool _isDisposed;
  44. private string _currentName;
  45. private string _currentTempName;
  46. private uint _currentDiskNumber;
  47. private uint _maxDiskNumber;
  48. private int _maxSegmentSize;
  49. private System.IO.Stream _innerStream;
  50. // **Note regarding exceptions:
  51. //
  52. // When ZipSegmentedStream is employed within a using clause,
  53. // which is the typical scenario, and an exception is thrown
  54. // within the scope of the using, Dispose() is invoked
  55. // implicitly before processing the initial exception. If that
  56. // happens, this class sets _exceptionPending to true, and then
  57. // within the Dispose(bool), takes special action as
  58. // appropriate. Need to be careful: any additional exceptions
  59. // will mask the original one.
  60. private ZipSegmentedStream() : base()
  61. {
  62. _exceptionPending = false;
  63. }
  64. public static ZipSegmentedStream ForReading(string name,
  65. uint initialDiskNumber,
  66. uint maxDiskNumber)
  67. {
  68. ZipSegmentedStream zss = new ZipSegmentedStream()
  69. {
  70. rwMode = RwMode.ReadOnly,
  71. CurrentSegment = initialDiskNumber,
  72. _maxDiskNumber = maxDiskNumber,
  73. _baseName = name,
  74. };
  75. // Console.WriteLine("ZSS: ForReading ({0})",
  76. // Path.GetFileName(zss.CurrentName));
  77. zss._SetReadStream();
  78. return zss;
  79. }
  80. public static ZipSegmentedStream ForWriting(string name, int maxSegmentSize)
  81. {
  82. ZipSegmentedStream zss = new ZipSegmentedStream()
  83. {
  84. rwMode = RwMode.Write,
  85. CurrentSegment = 0,
  86. _baseName = name,
  87. _maxSegmentSize = maxSegmentSize,
  88. _baseDir = Path.GetDirectoryName(name)
  89. };
  90. // workitem 9522
  91. if (zss._baseDir=="") zss._baseDir=".";
  92. zss._SetWriteStream(0);
  93. // Console.WriteLine("ZSS: ForWriting ({0})",
  94. // Path.GetFileName(zss.CurrentName));
  95. return zss;
  96. }
  97. /// <summary>
  98. /// Sort-of like a factory method, ForUpdate is used only when
  99. /// the application needs to update the zip entry metadata for
  100. /// a segmented zip file, when the starting segment is earlier
  101. /// than the ending segment, for a particular entry.
  102. /// </summary>
  103. /// <remarks>
  104. /// <para>
  105. /// The update is always contiguous, never rolls over. As a
  106. /// result, this method doesn't need to return a ZSS; it can
  107. /// simply return a FileStream. That's why it's "sort of"
  108. /// like a Factory method.
  109. /// </para>
  110. /// <para>
  111. /// Caller must Close/Dispose the stream object returned by
  112. /// this method.
  113. /// </para>
  114. /// </remarks>
  115. public static Stream ForUpdate(string name, uint diskNumber)
  116. {
  117. string fname =
  118. String.Format("{0}.z{1:D2}",
  119. Path.Combine(Path.GetDirectoryName(name),
  120. Path.GetFileNameWithoutExtension(name)),
  121. diskNumber + 1);
  122. // Console.WriteLine("ZSS: ForUpdate ({0})",
  123. // Path.GetFileName(fname));
  124. // This class assumes that the update will not expand the
  125. // size of the segment. Update is used only for an in-place
  126. // update of zip metadata. It never will try to write beyond
  127. // the end of a segment.
  128. return File.Open(fname,
  129. FileMode.Open,
  130. FileAccess.ReadWrite,
  131. FileShare.None);
  132. }
  133. public bool ContiguousWrite
  134. {
  135. get;
  136. set;
  137. }
  138. public UInt32 CurrentSegment
  139. {
  140. get
  141. {
  142. return _currentDiskNumber;
  143. }
  144. private set
  145. {
  146. _currentDiskNumber = value;
  147. _currentName = null; // it will get updated next time referenced
  148. }
  149. }
  150. /// <summary>
  151. /// Name of the filesystem file corresponding to the current segment.
  152. /// </summary>
  153. /// <remarks>
  154. /// <para>
  155. /// The name is not always the name currently being used in the
  156. /// filesystem. When rwMode is RwMode.Write, the filesystem file has a
  157. /// temporary name until the stream is closed or until the next segment is
  158. /// started.
  159. /// </para>
  160. /// </remarks>
  161. public String CurrentName
  162. {
  163. get
  164. {
  165. if (_currentName==null)
  166. _currentName = _NameForSegment(CurrentSegment);
  167. return _currentName;
  168. }
  169. }
  170. public String CurrentTempName
  171. {
  172. get
  173. {
  174. return _currentTempName;
  175. }
  176. }
  177. private string _NameForSegment(uint diskNumber)
  178. {
  179. return String.Format("{0}.z{1:D2}",
  180. Path.Combine(Path.GetDirectoryName(_baseName),
  181. Path.GetFileNameWithoutExtension(_baseName)),
  182. diskNumber + 1);
  183. }
  184. // Returns the segment that WILL be current if writing
  185. // a block of the given length.
  186. // This isn't exactly true. It could roll over beyond
  187. // this number.
  188. public UInt32 ComputeSegment(int length)
  189. {
  190. if (_innerStream.Position + length > _maxSegmentSize)
  191. // the block will go AT LEAST into the next segment
  192. return CurrentSegment + 1;
  193. // it will fit in the current segment
  194. return CurrentSegment;
  195. }
  196. public override String ToString()
  197. {
  198. return String.Format("{0}[{1}][{2}], pos=0x{3:X})",
  199. "ZipSegmentedStream", CurrentName,
  200. rwMode.ToString(),
  201. this.Position);
  202. }
  203. private void _SetReadStream()
  204. {
  205. if (_innerStream != null)
  206. {
  207. #if NETCF
  208. _innerStream.Close();
  209. #else
  210. _innerStream.Dispose();
  211. #endif
  212. }
  213. if (CurrentSegment + 1 == _maxDiskNumber)
  214. _currentName = _baseName;
  215. // Console.WriteLine("ZSS: SRS ({0})",
  216. // Path.GetFileName(CurrentName));
  217. _innerStream = File.OpenRead(CurrentName);
  218. }
  219. /// <summary>
  220. /// Read from the stream
  221. /// </summary>
  222. /// <param name="buffer">the buffer to read</param>
  223. /// <param name="offset">the offset at which to start</param>
  224. /// <param name="count">the number of bytes to read</param>
  225. /// <returns>the number of bytes actually read</returns>
  226. public override int Read(byte[] buffer, int offset, int count)
  227. {
  228. if (rwMode != RwMode.ReadOnly)
  229. {
  230. _exceptionPending = true;
  231. throw new InvalidOperationException("Stream Error: Cannot Read.");
  232. }
  233. int r = _innerStream.Read(buffer, offset, count);
  234. int r1 = r;
  235. while (r1 != count)
  236. {
  237. if (_innerStream.Position != _innerStream.Length)
  238. {
  239. _exceptionPending = true;
  240. throw new ZipException(String.Format("Read error in file {0}", CurrentName));
  241. }
  242. if (CurrentSegment + 1 == _maxDiskNumber)
  243. return r; // no more to read
  244. CurrentSegment++;
  245. _SetReadStream();
  246. offset += r1;
  247. count -= r1;
  248. r1 = _innerStream.Read(buffer, offset, count);
  249. r += r1;
  250. }
  251. return r;
  252. }
  253. private void _SetWriteStream(uint increment)
  254. {
  255. if (_innerStream != null)
  256. {
  257. #if NETCF
  258. _innerStream.Close();
  259. #else
  260. _innerStream.Dispose();
  261. #endif
  262. if (File.Exists(CurrentName))
  263. File.Delete(CurrentName);
  264. File.Move(_currentTempName, CurrentName);
  265. // Console.WriteLine("ZSS: SWS close ({0})",
  266. // Path.GetFileName(CurrentName));
  267. }
  268. if (increment > 0)
  269. CurrentSegment += increment;
  270. SharedUtilities.CreateAndOpenUniqueTempFile(_baseDir,
  271. out _innerStream,
  272. out _currentTempName);
  273. // Console.WriteLine("ZSS: SWS open ({0})",
  274. // Path.GetFileName(_currentTempName));
  275. if (CurrentSegment == 0)
  276. _innerStream.Write(BitConverter.GetBytes(ZipConstants.SplitArchiveSignature), 0, 4);
  277. }
  278. /// <summary>
  279. /// Write to the stream.
  280. /// </summary>
  281. /// <param name="buffer">the buffer from which to write</param>
  282. /// <param name="offset">the offset at which to start writing</param>
  283. /// <param name="count">the number of bytes to write</param>
  284. public override void Write(byte[] buffer, int offset, int count)
  285. {
  286. if (rwMode != RwMode.Write)
  287. {
  288. _exceptionPending = true;
  289. throw new InvalidOperationException("Stream Error: Cannot Write.");
  290. }
  291. if (ContiguousWrite)
  292. {
  293. // enough space for a contiguous write?
  294. if (_innerStream.Position + count > _maxSegmentSize)
  295. _SetWriteStream(1);
  296. }
  297. else
  298. {
  299. while (_innerStream.Position + count > _maxSegmentSize)
  300. {
  301. int c = unchecked(_maxSegmentSize - (int)_innerStream.Position);
  302. _innerStream.Write(buffer, offset, c);
  303. _SetWriteStream(1);
  304. count -= c;
  305. offset += c;
  306. }
  307. }
  308. _innerStream.Write(buffer, offset, count);
  309. }
  310. public long TruncateBackward(uint diskNumber, long offset)
  311. {
  312. // Console.WriteLine("***ZSS.Trunc to disk {0}", diskNumber);
  313. // Console.WriteLine("***ZSS.Trunc: current disk {0}", CurrentSegment);
  314. if (rwMode != RwMode.Write)
  315. {
  316. _exceptionPending = true;
  317. throw new ZipException("bad state.");
  318. }
  319. // Seek back in the segmented stream to a (maybe) prior segment.
  320. // Check if it is the same segment. If it is, very simple.
  321. if (diskNumber == CurrentSegment)
  322. {
  323. var x =_innerStream.Seek(offset, SeekOrigin.Begin);
  324. // workitem 10178
  325. Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(_innerStream);
  326. return x;
  327. }
  328. // Seeking back to a prior segment.
  329. // The current segment and any intervening segments must be removed.
  330. // First, close the current segment, and then remove it.
  331. if (_innerStream != null)
  332. {
  333. #if NETCF
  334. _innerStream.Close();
  335. #else
  336. _innerStream.Dispose();
  337. #endif
  338. if (File.Exists(_currentTempName))
  339. File.Delete(_currentTempName);
  340. }
  341. // Now, remove intervening segments.
  342. for (uint j= CurrentSegment-1; j > diskNumber; j--)
  343. {
  344. string s = _NameForSegment(j);
  345. // Console.WriteLine("***ZSS.Trunc: removing file {0}", s);
  346. if (File.Exists(s))
  347. File.Delete(s);
  348. }
  349. // now, open the desired segment. It must exist.
  350. CurrentSegment = diskNumber;
  351. // get a new temp file, try 3 times:
  352. for (int i = 0; i < 3; i++)
  353. {
  354. try
  355. {
  356. _currentTempName = Path.Combine(Path.GetDirectoryName(CurrentName),
  357. SharedUtilities.InternalGetTempFileName());
  358. // move the .z0x file back to a temp name
  359. File.Move(CurrentName, _currentTempName);
  360. break; // workitem 12403
  361. }
  362. catch(IOException)
  363. {
  364. if (i == 2) throw;
  365. }
  366. }
  367. // open it
  368. _innerStream = new FileStream(_currentTempName, FileMode.Open);
  369. var r = _innerStream.Seek(offset, SeekOrigin.Begin);
  370. // workitem 10178
  371. Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(_innerStream);
  372. return r;
  373. }
  374. public override bool CanRead
  375. {
  376. get
  377. {
  378. return (rwMode == RwMode.ReadOnly &&
  379. (_innerStream != null) &&
  380. _innerStream.CanRead);
  381. }
  382. }
  383. public override bool CanSeek
  384. {
  385. get
  386. {
  387. return (_innerStream != null) &&
  388. _innerStream.CanSeek;
  389. }
  390. }
  391. public override bool CanWrite
  392. {
  393. get
  394. {
  395. return (rwMode == RwMode.Write) &&
  396. (_innerStream != null) &&
  397. _innerStream.CanWrite;
  398. }
  399. }
  400. public override void Flush()
  401. {
  402. _innerStream.Flush();
  403. }
  404. public override long Length
  405. {
  406. get
  407. {
  408. return _innerStream.Length;
  409. }
  410. }
  411. public override long Position
  412. {
  413. get { return _innerStream.Position; }
  414. set { _innerStream.Position = value; }
  415. }
  416. public override long Seek(long offset, System.IO.SeekOrigin origin)
  417. {
  418. var x = _innerStream.Seek(offset, origin);
  419. // workitem 10178
  420. Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(_innerStream);
  421. return x;
  422. }
  423. public override void SetLength(long value)
  424. {
  425. if (rwMode != RwMode.Write)
  426. {
  427. _exceptionPending = true;
  428. throw new InvalidOperationException();
  429. }
  430. _innerStream.SetLength(value);
  431. }
  432. protected override void Dispose(bool disposing)
  433. {
  434. // this gets called by Stream.Close()
  435. // if (_isDisposed) return;
  436. // _isDisposed = true;
  437. //Console.WriteLine("Dispose (mode={0})\n", rwMode.ToString());
  438. try
  439. {
  440. if (_innerStream != null)
  441. {
  442. #if NETCF
  443. _innerStream.Close();
  444. #else
  445. _innerStream.Dispose();
  446. #endif
  447. //_innerStream = null;
  448. if (rwMode == RwMode.Write)
  449. {
  450. if (_exceptionPending)
  451. {
  452. // possibly could try to clean up all the
  453. // temp files created so far...
  454. }
  455. else
  456. {
  457. // // move the final temp file to the .zNN name
  458. // if (File.Exists(CurrentName))
  459. // File.Delete(CurrentName);
  460. // if (File.Exists(_currentTempName))
  461. // File.Move(_currentTempName, CurrentName);
  462. }
  463. }
  464. }
  465. }
  466. finally
  467. {
  468. base.Dispose(disposing);
  469. }
  470. }
  471. }
  472. }