/src/CleanZip.Compression/ZipArchive.cs

https://bitbucket.org/jens13/cleanzip · C# · 237 lines · 205 code · 27 blank · 5 comment · 29 complexity · e3b5f9ffcc7df7bd1bd9c6185bd1f599 MD5 · raw file

  1. // Copyright Jens Granlund 2012.
  2. // Distributed under the New BSD License.
  3. // (See accompanying file notice.txt or at
  4. // http://www.opensource.org/licenses/bsd-license.php)
  5. // Source: https://bitbucket.org/jens13/cleanzip
  6. using System;
  7. using System.Collections.Generic;
  8. using System.IO;
  9. using System.Linq;
  10. namespace CleanZip.Compression
  11. {
  12. public abstract class ZipArchive : ZipArchiveBase, IDisposable
  13. {
  14. private readonly bool _isReadOnly;
  15. private readonly List<ZipFile> _mergeArchives = new List<ZipFile>();
  16. protected ZipArchive(bool isReadOnly)
  17. {
  18. _isReadOnly = isReadOnly;
  19. }
  20. public Stream BaseStream { get { return ArchiveStream; } }
  21. public IEnumerable<ZipEntry> Entries { get { return EntryList.ToArray(); } }
  22. public bool IsReadOnly { get { return _isReadOnly; } }
  23. public void Dispose()
  24. {
  25. Close();
  26. }
  27. public abstract void Close();
  28. public ZipEntry Add(string fileName, CompressionType compressionType)
  29. {
  30. string entryName = Path.GetFileName(fileName);
  31. if (entryName == null)
  32. throw new ArgumentException(string.Format("{0} is not a file", fileName), "fileName");
  33. return Add(fileName, entryName, compressionType);
  34. }
  35. public ZipEntry Add(string fileName, string entryName, CompressionType compressionType)
  36. {
  37. Func<string, ZipEntry> createMethod = name => new ZipEntry(fileName, name, compressionType);
  38. Action<ZipEntry> setMethod = zipEntry => zipEntry.SetNewFile(fileName, compressionType);
  39. return ChangeOrCreateZipEntry(entryName, setMethod, createMethod);
  40. }
  41. public ZipEntry Add(string fileName, string entryName, CompressionType compressionType, string password)
  42. {
  43. Func<string, ZipEntry> createMethod = name => new ZipEntry(fileName, name, compressionType, password);
  44. Action<ZipEntry> setMethod = zipEntry => zipEntry.SetNewFile(fileName, compressionType, password);
  45. return ChangeOrCreateZipEntry(entryName, setMethod, createMethod);
  46. }
  47. public ZipEntry Add(Stream fileStream, string entryName, CompressionType compressionType)
  48. {
  49. Func<string, ZipEntry> createMethod = name => new ZipEntry(fileStream, name, compressionType);
  50. Action<ZipEntry> setMethod = zipEntry => zipEntry.SetNewStream(fileStream, compressionType);
  51. return ChangeOrCreateZipEntry(entryName, setMethod, createMethod);
  52. }
  53. public ZipEntry Add(Stream fileStream, string entryName, CompressionType compressionType, string password)
  54. {
  55. Func<string, ZipEntry> createMethod = name => new ZipEntry(fileStream, name, compressionType, password);
  56. Action<ZipEntry> setMethod = zipEntry => zipEntry.SetNewStream(fileStream, compressionType, password);
  57. return ChangeOrCreateZipEntry(entryName, setMethod, createMethod);
  58. }
  59. public void Delete(string entryName)
  60. {
  61. if (_isReadOnly) throw new ZipException("ZipFile is readonly, delete can not be executed");
  62. if (IsNew) throw new ZipException("Can not delete from new archive");
  63. ZipEntry zipEntry = Find(ZipEntry.NormalizeName(entryName));
  64. if (zipEntry != null)
  65. {
  66. EntryList.Remove(zipEntry);
  67. IsChanged = true;
  68. IsDirty = true;
  69. }
  70. }
  71. public void ExtractAll(string folder, bool overwrite)
  72. {
  73. ExtractAll(folder, overwrite, null);
  74. }
  75. public void ExtractAll(string folder, bool overwrite, string password)
  76. {
  77. Extract(EntryList, folder, overwrite, password);
  78. }
  79. public void Extract(IEnumerable<ZipEntry> entries, string folder, bool overwrite)
  80. {
  81. Extract(entries, folder, overwrite, null);
  82. }
  83. public void Extract(IEnumerable<ZipEntry> entries, string folder, bool overwrite, string password)
  84. {
  85. if (Directory.Exists(folder) && !overwrite)
  86. throw new IOException(string.Format("Folder {0} already exists", folder));
  87. foreach (ZipEntry entry in entries)
  88. {
  89. string file = Path.Combine(folder, entry.Name.Replace('/', Path.DirectorySeparatorChar));
  90. Extract(entry, file, overwrite, password);
  91. }
  92. }
  93. public void Extract(ZipEntry entry, string path, bool overwrite)
  94. {
  95. Extract(entry, path, overwrite, null);
  96. }
  97. public void Extract(ZipEntry entry, string path, bool overwrite, string password)
  98. {
  99. if (entry.Name.EndsWith("/"))
  100. {
  101. Directory.CreateDirectory(path);
  102. return;
  103. }
  104. if (File.Exists(path) && !overwrite)
  105. throw new IOException(string.Format("File {0} already exists", path));
  106. string directoryName = Path.GetDirectoryName(path);
  107. if (directoryName != null) Directory.CreateDirectory(directoryName);
  108. using (FileStream stream = File.Create(path))
  109. {
  110. entry.Extract(stream, password);
  111. }
  112. File.SetCreationTime(path, entry.LastModified);
  113. File.SetLastWriteTime(path, entry.LastModified);
  114. }
  115. protected void FindCentralEndRecord()
  116. {
  117. ArchiveStream.Seek(-17, SeekOrigin.End);
  118. uint signature = 0;
  119. while (ArchiveStream.Position > 5 && signature != CentralEndRecordSignature)
  120. {
  121. ArchiveStream.Seek(-5, SeekOrigin.Current);
  122. signature = ArchiveStream.ReadUInt();
  123. }
  124. ReadCentralEndRecord();
  125. }
  126. protected void WriteToStream(Stream stream)
  127. {
  128. foreach (var entry in EntryList)
  129. {
  130. entry.WriteEntry(stream);
  131. }
  132. WriteCentralDirectory(stream);
  133. WriteCentralEndRecord(stream);
  134. }
  135. public void Flush()
  136. {
  137. if (_isReadOnly) throw new ZipException("ZipFile is readonly");
  138. InternalFlush(true);
  139. }
  140. protected abstract void InternalFlush(bool leaveOpen);
  141. protected bool SimpleFlush(bool leaveOpen)
  142. {
  143. if (MergeArchivesExists || !IsNew || IsChanged) return false;
  144. if (IsNew)
  145. {
  146. if (AllowReplacingEntries)
  147. {
  148. ArchiveStream.Position = 0;
  149. foreach (var entry in EntryList)
  150. {
  151. entry.WriteEntry(ArchiveStream);
  152. }
  153. }
  154. }
  155. else
  156. {
  157. ArchiveStream.Seek(CentralDirectoryOffset, SeekOrigin.Begin);
  158. foreach (var entry in EntryList.Where(x => x.IsDirty))
  159. {
  160. entry.WriteEntry(ArchiveStream);
  161. }
  162. }
  163. WriteCentralDirectory(ArchiveStream);
  164. WriteCentralEndRecord(ArchiveStream);
  165. CloseStreamOrLeaveOpen(leaveOpen);
  166. return true;
  167. }
  168. protected void CloseMergeFiles()
  169. {
  170. foreach (ZipFile mergeFile in _mergeArchives)
  171. {
  172. mergeFile.Close();
  173. }
  174. _mergeArchives.Clear();
  175. }
  176. protected bool MergeArchivesExists { get { return _mergeArchives.Count > 0; } }
  177. public void MergeZipFile(string file, bool overwrite)
  178. {
  179. if (_isReadOnly) throw new ZipException("ZipFile is readonly, merge can not be executed");
  180. if (!File.Exists(file)) throw new ZipException(string.Format("File {0} does not exist.", file));
  181. if (_mergeArchives.FirstOrDefault(x => x.FileName.Equals(file, StringComparison.OrdinalIgnoreCase)) !=
  182. null) return;
  183. var zf = ZipFile.OpenRead(file);
  184. _mergeArchives.Add(zf);
  185. MergeZipFile(zf, overwrite);
  186. }
  187. private void MergeZipFile(ZipFile archive, bool overwrite)
  188. {
  189. if (overwrite)
  190. {
  191. EntryList.RemoveAll(
  192. x =>
  193. archive.Entries.FirstOrDefault(y => y.Name.Equals(x.Name, StringComparison.OrdinalIgnoreCase)) !=
  194. null);
  195. EntryList.AddRange(archive.Entries);
  196. IsDirty = archive.EntryList.Count != 0;
  197. }
  198. else
  199. {
  200. var zipEntries =
  201. archive.Entries.Where(
  202. x =>
  203. EntryList.FirstOrDefault(y => y.Name.Equals(x.Name, StringComparison.OrdinalIgnoreCase)) == null)
  204. .ToArray();
  205. IsDirty = zipEntries.Length != 0;
  206. EntryList.AddRange(zipEntries);
  207. }
  208. }
  209. }
  210. }