Merge pull request #3213 from henricm/fix-for-win-securestring-to-bstr
[mono.git] / mcs / class / System.IO.Compression / SharpCompress / Archive / AbstractWritableArchive.cs
1 using System;
2 using System.Collections.Generic;
3 using System.IO;
4 using System.Linq;
5 using System.Text;
6 using SharpCompress.Common;
7
8 namespace SharpCompress.Archive
9 {
10     internal abstract class AbstractWritableArchive<TEntry, TVolume> : AbstractArchive<TEntry, TVolume>
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
17         private readonly List<TEntry> modifiedEntries = new List<TEntry>();
18         private bool hasModifications;
19
20         internal AbstractWritableArchive(ArchiveType type)
21             : base(type)
22         {
23         }
24
25         internal AbstractWritableArchive(ArchiveType type, Stream stream, Options options)
26             : base(type, stream.AsEnumerable(), options, null)
27         {
28         }
29
30 #if !PORTABLE && !NETFX_CORE
31         internal AbstractWritableArchive(ArchiveType type, FileInfo fileInfo, Options options)
32             : base(type, fileInfo, options, null)
33         {
34         }
35 #endif
36
37         public override ICollection<TEntry> Entries
38         {
39             get
40             {
41                 if (hasModifications)
42                 {
43                     return modifiedEntries;
44                 }
45                 return base.Entries;
46             }
47         }
48
49         private void RebuildModifiedCollection()
50         {
51             hasModifications = true;
52             newEntries.RemoveAll(v => removedEntries.Contains(v));
53             modifiedEntries.Clear();
54             modifiedEntries.AddRange(OldEntries.Concat(newEntries));
55         }
56
57         private IEnumerable<TEntry> OldEntries
58         {
59             get { return base.Entries.Where(x => !removedEntries.Contains(x)); }
60         }
61
62         public void RemoveEntry(TEntry entry)
63         {
64             if (!removedEntries.Contains(entry))
65             {
66                 removedEntries.Add(entry);
67                 RebuildModifiedCollection();
68             }
69         }
70
71         public TEntry AddEntry(string key, Stream source,
72                              long size = 0, DateTime? modified = null)
73         {
74             return AddEntry(key, source, false, size, modified);
75         }
76
77         public TEntry AddEntry(string key, Stream source, bool closeStream,
78                              long size = 0, DateTime? modified = null)
79         {
80             if (key.StartsWith("/")
81                 || key.StartsWith("\\"))
82             {
83                 key = key.Substring(1);
84             }
85             // .NET allows duplicate entries when saving and loading Zip files.
86             // The following lines are disabled from upstream SharpCompress to allow this.
87 #if ZIP_ALLOW_DUPLICATE_KEYS
88             if (DoesKeyMatchExisting(key))
89             {
90                 throw new ArchiveException("Cannot add entry with duplicate key: " + key);
91             }
92 #endif
93             var entry = CreateEntry(key, source, size, modified, closeStream);
94             newEntries.Add(entry);
95             RebuildModifiedCollection();
96             return entry;
97         }
98
99         private bool DoesKeyMatchExisting(string key)
100         {
101             foreach (var path in Entries.Select(x => x.Key))
102             {
103                 var p = path.Replace('/','\\');
104                 if (p.StartsWith("\\"))
105                 {
106                     p = p.Substring(1);
107                 }
108                 if (string.Equals(p, key, StringComparison.OrdinalIgnoreCase))
109                     return true;
110             }
111             return false;
112         }
113
114         public void SaveTo(Stream stream, CompressionInfo compressionType, Encoding encoding = null)
115         {
116             //reset streams of new entries
117             newEntries.Cast<IWritableArchiveEntry>().ForEach(x => x.Stream.Seek(0, SeekOrigin.Begin));
118             SaveTo(stream, compressionType, encoding ?? ArchiveEncoding.Default, OldEntries, newEntries);
119         }
120
121         protected TEntry CreateEntry(string key, Stream source, long size, DateTime? modified,
122             bool closeStream)
123         {
124             if (!source.CanRead || !source.CanSeek)
125             {
126                 throw new ArgumentException("Streams must be readable and seekable to use the Writing Archive API");
127             }
128             return CreateEntryInternal(key, source, size, modified, closeStream);
129         }
130
131         protected abstract TEntry CreateEntryInternal(string key, Stream source, long size, DateTime? modified,
132                                               bool closeStream);
133
134         protected abstract void SaveTo(Stream stream, CompressionInfo compressionType, Encoding encoding,
135                                        IEnumerable<TEntry> oldEntries, IEnumerable<TEntry> newEntries);
136
137         public override void Dispose()
138         {
139             base.Dispose();
140             newEntries.Cast<Entry>().ForEach(x => x.Close());
141             removedEntries.Cast<Entry>().ForEach(x => x.Close());
142             modifiedEntries.Cast<Entry>().ForEach(x => x.Close());
143         }
144     }
145 }