AbstractWritableArchive.cs 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using SharpCompress.Common;
  6. using SharpCompress.Readers;
  7. using SharpCompress.Writers;
  8. namespace SharpCompress.Archives
  9. {
  10. public abstract class AbstractWritableArchive<TEntry, TVolume> : AbstractArchive<TEntry, TVolume>, IWritableArchive
  11. where TEntry : IArchiveEntry
  12. where TVolume : IVolume
  13. {
  14. private readonly List<TEntry> newEntries = new List<TEntry>();
  15. private readonly List<TEntry> removedEntries = new List<TEntry>();
  16. private readonly List<TEntry> modifiedEntries = new List<TEntry>();
  17. private bool hasModifications;
  18. internal AbstractWritableArchive(ArchiveType type)
  19. : base(type)
  20. {
  21. }
  22. internal AbstractWritableArchive(ArchiveType type, Stream stream, ReaderOptions readerFactoryOptions)
  23. : base(type, stream.AsEnumerable(), readerFactoryOptions)
  24. {
  25. }
  26. #if !NO_FILE
  27. internal AbstractWritableArchive(ArchiveType type, FileInfo fileInfo, ReaderOptions readerFactoryOptions)
  28. : base(type, fileInfo, readerFactoryOptions)
  29. {
  30. }
  31. #endif
  32. public override ICollection<TEntry> Entries
  33. {
  34. get
  35. {
  36. if (hasModifications)
  37. {
  38. return modifiedEntries;
  39. }
  40. return base.Entries;
  41. }
  42. }
  43. private void RebuildModifiedCollection()
  44. {
  45. hasModifications = true;
  46. newEntries.RemoveAll(v => removedEntries.Contains(v));
  47. modifiedEntries.Clear();
  48. modifiedEntries.AddRange(OldEntries.Concat(newEntries));
  49. }
  50. private IEnumerable<TEntry> OldEntries { get { return base.Entries.Where(x => !removedEntries.Contains(x)); } }
  51. public void RemoveEntry(TEntry entry)
  52. {
  53. if (!removedEntries.Contains(entry))
  54. {
  55. removedEntries.Add(entry);
  56. RebuildModifiedCollection();
  57. }
  58. }
  59. void IWritableArchive.RemoveEntry(IArchiveEntry entry)
  60. {
  61. RemoveEntry((TEntry)entry);
  62. }
  63. public TEntry AddEntry(string key, Stream source,
  64. long size = 0, DateTime? modified = null)
  65. {
  66. return AddEntry(key, source, false, size, modified);
  67. }
  68. IArchiveEntry IWritableArchive.AddEntry(string key, Stream source, bool closeStream, long size, DateTime? modified)
  69. {
  70. return AddEntry(key, source, closeStream, size, modified);
  71. }
  72. public TEntry AddEntry(string key, Stream source, bool closeStream,
  73. long size = 0, DateTime? modified = null)
  74. {
  75. if (key.StartsWith("/")
  76. || key.StartsWith("\\"))
  77. {
  78. key = key.Substring(1);
  79. }
  80. if (DoesKeyMatchExisting(key))
  81. {
  82. throw new ArchiveException("Cannot add entry with duplicate key: " + key);
  83. }
  84. var entry = CreateEntry(key, source, size, modified, closeStream);
  85. newEntries.Add(entry);
  86. RebuildModifiedCollection();
  87. return entry;
  88. }
  89. private bool DoesKeyMatchExisting(string key)
  90. {
  91. foreach (var path in Entries.Select(x => x.Key))
  92. {
  93. var p = path.Replace('/', '\\');
  94. if (p.StartsWith("\\"))
  95. {
  96. p = p.Substring(1);
  97. }
  98. return string.Equals(p, key, StringComparison.OrdinalIgnoreCase);
  99. }
  100. return false;
  101. }
  102. public void SaveTo(Stream stream, WriterOptions options)
  103. {
  104. //reset streams of new entries
  105. newEntries.Cast<IWritableArchiveEntry>().ForEach(x => x.Stream.Seek(0, SeekOrigin.Begin));
  106. SaveTo(stream, options, OldEntries, newEntries);
  107. }
  108. protected TEntry CreateEntry(string key, Stream source, long size, DateTime? modified,
  109. bool closeStream)
  110. {
  111. if (!source.CanRead || !source.CanSeek)
  112. {
  113. throw new ArgumentException("Streams must be readable and seekable to use the Writing Archive API");
  114. }
  115. return CreateEntryInternal(key, source, size, modified, closeStream);
  116. }
  117. protected abstract TEntry CreateEntryInternal(string key, Stream source, long size, DateTime? modified,
  118. bool closeStream);
  119. protected abstract void SaveTo(Stream stream, WriterOptions options, IEnumerable<TEntry> oldEntries, IEnumerable<TEntry> newEntries);
  120. public override void Dispose()
  121. {
  122. base.Dispose();
  123. newEntries.Cast<Entry>().ForEach(x => x.Close());
  124. removedEntries.Cast<Entry>().ForEach(x => x.Close());
  125. modifiedEntries.Cast<Entry>().ForEach(x => x.Close());
  126. }
  127. }
  128. }