diff options
Diffstat (limited to 'src/dtf/WixToolset.Dtf.Compression')
17 files changed, 3816 insertions, 0 deletions
diff --git a/src/dtf/WixToolset.Dtf.Compression/ArchiveException.cs b/src/dtf/WixToolset.Dtf.Compression/ArchiveException.cs new file mode 100644 index 00000000..a53e862c --- /dev/null +++ b/src/dtf/WixToolset.Dtf.Compression/ArchiveException.cs | |||
@@ -0,0 +1,57 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Dtf.Compression | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Runtime.Serialization; | ||
8 | |||
9 | /// <summary> | ||
10 | /// Base exception class for compression operations. Compression libraries should | ||
11 | /// derive subclass exceptions with more specific error information relevent to the | ||
12 | /// file format. | ||
13 | /// </summary> | ||
14 | [Serializable] | ||
15 | public class ArchiveException : IOException | ||
16 | { | ||
17 | /// <summary> | ||
18 | /// Creates a new ArchiveException with a specified error message and a reference to the | ||
19 | /// inner exception that is the cause of this exception. | ||
20 | /// </summary> | ||
21 | /// <param name="message">The message that describes the error.</param> | ||
22 | /// <param name="innerException">The exception that is the cause of the current exception. If the | ||
23 | /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception | ||
24 | /// is raised in a catch block that handles the inner exception.</param> | ||
25 | public ArchiveException(string message, Exception innerException) | ||
26 | : base(message, innerException) | ||
27 | { | ||
28 | } | ||
29 | |||
30 | /// <summary> | ||
31 | /// Creates a new ArchiveException with a specified error message. | ||
32 | /// </summary> | ||
33 | /// <param name="message">The message that describes the error.</param> | ||
34 | public ArchiveException(string message) | ||
35 | : this(message, null) | ||
36 | { | ||
37 | } | ||
38 | |||
39 | /// <summary> | ||
40 | /// Creates a new ArchiveException. | ||
41 | /// </summary> | ||
42 | public ArchiveException() | ||
43 | : this(null, null) | ||
44 | { | ||
45 | } | ||
46 | |||
47 | /// <summary> | ||
48 | /// Initializes a new instance of the ArchiveException class with serialized data. | ||
49 | /// </summary> | ||
50 | /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param> | ||
51 | /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param> | ||
52 | protected ArchiveException(SerializationInfo info, StreamingContext context) | ||
53 | : base(info, context) | ||
54 | { | ||
55 | } | ||
56 | } | ||
57 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.Compression/ArchiveFileInfo.cs b/src/dtf/WixToolset.Dtf.Compression/ArchiveFileInfo.cs new file mode 100644 index 00000000..adcae3ec --- /dev/null +++ b/src/dtf/WixToolset.Dtf.Compression/ArchiveFileInfo.cs | |||
@@ -0,0 +1,430 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Dtf.Compression | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Runtime.Serialization; | ||
8 | using System.Diagnostics.CodeAnalysis; | ||
9 | |||
10 | /// <summary> | ||
11 | /// Abstract object representing a compressed file within an archive; | ||
12 | /// provides operations for getting the file properties and unpacking | ||
13 | /// the file. | ||
14 | /// </summary> | ||
15 | [Serializable] | ||
16 | public abstract class ArchiveFileInfo : FileSystemInfo | ||
17 | { | ||
18 | private ArchiveInfo archiveInfo; | ||
19 | private string name; | ||
20 | private string path; | ||
21 | |||
22 | private bool initialized; | ||
23 | private bool exists; | ||
24 | private int archiveNumber; | ||
25 | private FileAttributes attributes; | ||
26 | private DateTime lastWriteTime; | ||
27 | private long length; | ||
28 | |||
29 | /// <summary> | ||
30 | /// Creates a new ArchiveFileInfo object representing a file within | ||
31 | /// an archive in a specified path. | ||
32 | /// </summary> | ||
33 | /// <param name="archiveInfo">An object representing the archive | ||
34 | /// containing the file.</param> | ||
35 | /// <param name="filePath">The path to the file within the archive. | ||
36 | /// Usually, this is a simple file name, but if the archive contains | ||
37 | /// a directory structure this may include the directory.</param> | ||
38 | protected ArchiveFileInfo(ArchiveInfo archiveInfo, string filePath) | ||
39 | : base() | ||
40 | { | ||
41 | if (filePath == null) | ||
42 | { | ||
43 | throw new ArgumentNullException("filePath"); | ||
44 | } | ||
45 | |||
46 | this.Archive = archiveInfo; | ||
47 | |||
48 | this.name = System.IO.Path.GetFileName(filePath); | ||
49 | this.path = System.IO.Path.GetDirectoryName(filePath); | ||
50 | |||
51 | this.attributes = FileAttributes.Normal; | ||
52 | this.lastWriteTime = DateTime.MinValue; | ||
53 | } | ||
54 | |||
55 | /// <summary> | ||
56 | /// Creates a new ArchiveFileInfo object with all parameters specified; | ||
57 | /// used by subclasses when reading the metadata out of an archive. | ||
58 | /// </summary> | ||
59 | /// <param name="filePath">The internal path and name of the file in | ||
60 | /// the archive.</param> | ||
61 | /// <param name="archiveNumber">The archive number where the file | ||
62 | /// starts.</param> | ||
63 | /// <param name="attributes">The stored attributes of the file.</param> | ||
64 | /// <param name="lastWriteTime">The stored last write time of the | ||
65 | /// file.</param> | ||
66 | /// <param name="length">The uncompressed size of the file.</param> | ||
67 | protected ArchiveFileInfo( | ||
68 | string filePath, | ||
69 | int archiveNumber, | ||
70 | FileAttributes attributes, | ||
71 | DateTime lastWriteTime, | ||
72 | long length) | ||
73 | : this(null, filePath) | ||
74 | { | ||
75 | this.exists = true; | ||
76 | this.archiveNumber = archiveNumber; | ||
77 | this.attributes = attributes; | ||
78 | this.lastWriteTime = lastWriteTime; | ||
79 | this.length = length; | ||
80 | this.initialized = true; | ||
81 | } | ||
82 | |||
83 | /// <summary> | ||
84 | /// Initializes a new instance of the ArchiveFileInfo class with | ||
85 | /// serialized data. | ||
86 | /// </summary> | ||
87 | /// <param name="info">The SerializationInfo that holds the serialized | ||
88 | /// object data about the exception being thrown.</param> | ||
89 | /// <param name="context">The StreamingContext that contains contextual | ||
90 | /// information about the source or destination.</param> | ||
91 | protected ArchiveFileInfo(SerializationInfo info, StreamingContext context) | ||
92 | : base(info, context) | ||
93 | { | ||
94 | this.archiveInfo = (ArchiveInfo) info.GetValue( | ||
95 | "archiveInfo", typeof(ArchiveInfo)); | ||
96 | this.name = info.GetString("name"); | ||
97 | this.path = info.GetString("path"); | ||
98 | this.initialized = info.GetBoolean("initialized"); | ||
99 | this.exists = info.GetBoolean("exists"); | ||
100 | this.archiveNumber = info.GetInt32("archiveNumber"); | ||
101 | this.attributes = (FileAttributes) info.GetValue( | ||
102 | "attributes", typeof(FileAttributes)); | ||
103 | this.lastWriteTime = info.GetDateTime("lastWriteTime"); | ||
104 | this.length = info.GetInt64("length"); | ||
105 | } | ||
106 | |||
107 | /// <summary> | ||
108 | /// Gets the name of the file. | ||
109 | /// </summary> | ||
110 | /// <value>The name of the file, not including any path.</value> | ||
111 | public override string Name | ||
112 | { | ||
113 | get | ||
114 | { | ||
115 | return this.name; | ||
116 | } | ||
117 | } | ||
118 | |||
119 | /// <summary> | ||
120 | /// Gets the internal path of the file in the archive. | ||
121 | /// </summary> | ||
122 | /// <value>The internal path of the file in the archive, not including | ||
123 | /// the file name.</value> | ||
124 | public string Path | ||
125 | { | ||
126 | get | ||
127 | { | ||
128 | return this.path; | ||
129 | } | ||
130 | } | ||
131 | |||
132 | /// <summary> | ||
133 | /// Gets the full path to the file. | ||
134 | /// </summary> | ||
135 | /// <value>The full path to the file, including the full path to the | ||
136 | /// archive, the internal path in the archive, and the file name.</value> | ||
137 | /// <remarks> | ||
138 | /// For example, the path <c>"C:\archive.cab\file.txt"</c> refers to | ||
139 | /// a file "file.txt" inside the archive "archive.cab". | ||
140 | /// </remarks> | ||
141 | public override string FullName | ||
142 | { | ||
143 | get | ||
144 | { | ||
145 | string fullName = System.IO.Path.Combine(this.Path, this.Name); | ||
146 | |||
147 | if (this.Archive != null) | ||
148 | { | ||
149 | fullName = System.IO.Path.Combine(this.ArchiveName, fullName); | ||
150 | } | ||
151 | |||
152 | return fullName; | ||
153 | } | ||
154 | } | ||
155 | |||
156 | /// <summary> | ||
157 | /// Gets or sets the archive that contains this file. | ||
158 | /// </summary> | ||
159 | /// <value> | ||
160 | /// The ArchiveInfo instance that retrieved this file information -- this | ||
161 | /// may be null if the ArchiveFileInfo object was returned directly from | ||
162 | /// a stream. | ||
163 | /// </value> | ||
164 | public ArchiveInfo Archive | ||
165 | { | ||
166 | get | ||
167 | { | ||
168 | return (ArchiveInfo) this.archiveInfo; | ||
169 | } | ||
170 | |||
171 | internal set | ||
172 | { | ||
173 | this.archiveInfo = value; | ||
174 | |||
175 | // protected instance members inherited from FileSystemInfo: | ||
176 | this.OriginalPath = (value != null ? value.FullName : null); | ||
177 | this.FullPath = this.OriginalPath; | ||
178 | } | ||
179 | } | ||
180 | |||
181 | /// <summary> | ||
182 | /// Gets the full path of the archive that contains this file. | ||
183 | /// </summary> | ||
184 | /// <value>The full path of the archive that contains this file.</value> | ||
185 | public string ArchiveName | ||
186 | { | ||
187 | get | ||
188 | { | ||
189 | return this.Archive != null ? this.Archive.FullName : null; | ||
190 | } | ||
191 | } | ||
192 | |||
193 | /// <summary> | ||
194 | /// Gets the number of the archive where this file starts. | ||
195 | /// </summary> | ||
196 | /// <value>The number of the archive where this file starts.</value> | ||
197 | /// <remarks>A single archive or the first archive in a chain is | ||
198 | /// numbered 0.</remarks> | ||
199 | public int ArchiveNumber | ||
200 | { | ||
201 | get | ||
202 | { | ||
203 | return this.archiveNumber; | ||
204 | } | ||
205 | } | ||
206 | |||
207 | /// <summary> | ||
208 | /// Checks if the file exists within the archive. | ||
209 | /// </summary> | ||
210 | /// <value>True if the file exists, false otherwise.</value> | ||
211 | public override bool Exists | ||
212 | { | ||
213 | get | ||
214 | { | ||
215 | if (!this.initialized) | ||
216 | { | ||
217 | this.Refresh(); | ||
218 | } | ||
219 | |||
220 | return this.exists; | ||
221 | } | ||
222 | } | ||
223 | |||
224 | /// <summary> | ||
225 | /// Gets the uncompressed size of the file. | ||
226 | /// </summary> | ||
227 | /// <value>The uncompressed size of the file in bytes.</value> | ||
228 | public long Length | ||
229 | { | ||
230 | get | ||
231 | { | ||
232 | if (!this.initialized) | ||
233 | { | ||
234 | this.Refresh(); | ||
235 | } | ||
236 | |||
237 | return this.length; | ||
238 | } | ||
239 | } | ||
240 | |||
241 | /// <summary> | ||
242 | /// Gets the attributes of the file. | ||
243 | /// </summary> | ||
244 | /// <value>The attributes of the file as stored in the archive.</value> | ||
245 | public new FileAttributes Attributes | ||
246 | { | ||
247 | get | ||
248 | { | ||
249 | if (!this.initialized) | ||
250 | { | ||
251 | this.Refresh(); | ||
252 | } | ||
253 | |||
254 | return this.attributes; | ||
255 | } | ||
256 | } | ||
257 | |||
258 | /// <summary> | ||
259 | /// Gets the last modification time of the file. | ||
260 | /// </summary> | ||
261 | /// <value>The last modification time of the file as stored in the | ||
262 | /// archive.</value> | ||
263 | public new DateTime LastWriteTime | ||
264 | { | ||
265 | get | ||
266 | { | ||
267 | if (!this.initialized) | ||
268 | { | ||
269 | this.Refresh(); | ||
270 | } | ||
271 | |||
272 | return this.lastWriteTime; | ||
273 | } | ||
274 | } | ||
275 | |||
276 | /// <summary> | ||
277 | /// Sets the SerializationInfo with information about the archive. | ||
278 | /// </summary> | ||
279 | /// <param name="info">The SerializationInfo that holds the serialized | ||
280 | /// object data.</param> | ||
281 | /// <param name="context">The StreamingContext that contains contextual | ||
282 | /// information about the source or destination.</param> | ||
283 | public override void GetObjectData( | ||
284 | SerializationInfo info, StreamingContext context) | ||
285 | { | ||
286 | base.GetObjectData(info, context); | ||
287 | info.AddValue("archiveInfo", this.archiveInfo); | ||
288 | info.AddValue("name", this.name); | ||
289 | info.AddValue("path", this.path); | ||
290 | info.AddValue("initialized", this.initialized); | ||
291 | info.AddValue("exists", this.exists); | ||
292 | info.AddValue("archiveNumber", this.archiveNumber); | ||
293 | info.AddValue("attributes", this.attributes); | ||
294 | info.AddValue("lastWriteTime", this.lastWriteTime); | ||
295 | info.AddValue("length", this.length); | ||
296 | } | ||
297 | |||
298 | /// <summary> | ||
299 | /// Gets the full path to the file. | ||
300 | /// </summary> | ||
301 | /// <returns>The same as <see cref="FullName"/></returns> | ||
302 | public override string ToString() | ||
303 | { | ||
304 | return this.FullName; | ||
305 | } | ||
306 | |||
307 | /// <summary> | ||
308 | /// Deletes the file. NOT SUPPORTED. | ||
309 | /// </summary> | ||
310 | /// <exception cref="NotSupportedException">Files cannot be deleted | ||
311 | /// from an existing archive.</exception> | ||
312 | public override void Delete() | ||
313 | { | ||
314 | throw new NotSupportedException(); | ||
315 | } | ||
316 | |||
317 | /// <summary> | ||
318 | /// Refreshes the attributes and other cached information about the file, | ||
319 | /// by re-reading the information from the archive. | ||
320 | /// </summary> | ||
321 | public new void Refresh() | ||
322 | { | ||
323 | base.Refresh(); | ||
324 | |||
325 | if (this.Archive != null) | ||
326 | { | ||
327 | string filePath = System.IO.Path.Combine(this.Path, this.Name); | ||
328 | ArchiveFileInfo updatedFile = this.Archive.GetFile(filePath); | ||
329 | if (updatedFile == null) | ||
330 | { | ||
331 | throw new FileNotFoundException( | ||
332 | "File not found in archive.", filePath); | ||
333 | } | ||
334 | |||
335 | this.Refresh(updatedFile); | ||
336 | } | ||
337 | } | ||
338 | |||
339 | /// <summary> | ||
340 | /// Extracts the file. | ||
341 | /// </summary> | ||
342 | /// <param name="destFileName">The destination path where the file | ||
343 | /// will be extracted.</param> | ||
344 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
345 | public void CopyTo(string destFileName) | ||
346 | { | ||
347 | this.CopyTo(destFileName, false); | ||
348 | } | ||
349 | |||
350 | /// <summary> | ||
351 | /// Extracts the file, optionally overwriting any existing file. | ||
352 | /// </summary> | ||
353 | /// <param name="destFileName">The destination path where the file | ||
354 | /// will be extracted.</param> | ||
355 | /// <param name="overwrite">If true, <paramref name="destFileName"/> | ||
356 | /// will be overwritten if it exists.</param> | ||
357 | /// <exception cref="IOException"><paramref name="overwrite"/> is false | ||
358 | /// and <paramref name="destFileName"/> exists.</exception> | ||
359 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
360 | public void CopyTo(string destFileName, bool overwrite) | ||
361 | { | ||
362 | if (destFileName == null) | ||
363 | { | ||
364 | throw new ArgumentNullException("destFileName"); | ||
365 | } | ||
366 | |||
367 | if (!overwrite && File.Exists(destFileName)) | ||
368 | { | ||
369 | throw new IOException(); | ||
370 | } | ||
371 | |||
372 | if (this.Archive == null) | ||
373 | { | ||
374 | throw new InvalidOperationException(); | ||
375 | } | ||
376 | |||
377 | this.Archive.UnpackFile( | ||
378 | System.IO.Path.Combine(this.Path, this.Name), destFileName); | ||
379 | } | ||
380 | |||
381 | /// <summary> | ||
382 | /// Opens the archive file for reading without actually extracting the | ||
383 | /// file to disk. | ||
384 | /// </summary> | ||
385 | /// <returns> | ||
386 | /// A stream for reading directly from the packed file. Like any stream | ||
387 | /// this should be closed/disposed as soon as it is no longer needed. | ||
388 | /// </returns> | ||
389 | public Stream OpenRead() | ||
390 | { | ||
391 | return this.Archive.OpenRead(System.IO.Path.Combine(this.Path, this.Name)); | ||
392 | } | ||
393 | |||
394 | /// <summary> | ||
395 | /// Opens the archive file reading text with UTF-8 encoding without | ||
396 | /// actually extracting the file to disk. | ||
397 | /// </summary> | ||
398 | /// <returns> | ||
399 | /// A reader for reading text directly from the packed file. Like any reader | ||
400 | /// this should be closed/disposed as soon as it is no longer needed. | ||
401 | /// </returns> | ||
402 | /// <remarks> | ||
403 | /// To open an archived text file with different encoding, use the | ||
404 | /// <see cref="OpenRead" /> method and pass the returned stream to one of | ||
405 | /// the <see cref="StreamReader" /> constructor overloads. | ||
406 | /// </remarks> | ||
407 | public StreamReader OpenText() | ||
408 | { | ||
409 | return this.Archive.OpenText(System.IO.Path.Combine(this.Path, this.Name)); | ||
410 | } | ||
411 | |||
412 | /// <summary> | ||
413 | /// Refreshes the information in this object with new data retrieved | ||
414 | /// from an archive. | ||
415 | /// </summary> | ||
416 | /// <param name="newFileInfo">Fresh instance for the same file just | ||
417 | /// read from the archive.</param> | ||
418 | /// <remarks> | ||
419 | /// Subclasses may override this method to refresh sublcass fields. | ||
420 | /// However they should always call the base implementation first. | ||
421 | /// </remarks> | ||
422 | protected virtual void Refresh(ArchiveFileInfo newFileInfo) | ||
423 | { | ||
424 | this.exists = newFileInfo.exists; | ||
425 | this.length = newFileInfo.length; | ||
426 | this.attributes = newFileInfo.attributes; | ||
427 | this.lastWriteTime = newFileInfo.lastWriteTime; | ||
428 | } | ||
429 | } | ||
430 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.Compression/ArchiveFileStreamContext.cs b/src/dtf/WixToolset.Dtf.Compression/ArchiveFileStreamContext.cs new file mode 100644 index 00000000..8be3bff4 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.Compression/ArchiveFileStreamContext.cs | |||
@@ -0,0 +1,664 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Dtf.Compression | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Collections.Generic; | ||
8 | |||
9 | /// <summary> | ||
10 | /// Provides a basic implementation of the archive pack and unpack stream context | ||
11 | /// interfaces, based on a list of archive files, a default directory, and an | ||
12 | /// optional mapping from internal to external file paths. | ||
13 | /// </summary> | ||
14 | /// <remarks> | ||
15 | /// This class can also handle creating or extracting chained archive packages. | ||
16 | /// </remarks> | ||
17 | public class ArchiveFileStreamContext | ||
18 | : IPackStreamContext, IUnpackStreamContext | ||
19 | { | ||
20 | private IList<string> archiveFiles; | ||
21 | private string directory; | ||
22 | private IDictionary<string, string> files; | ||
23 | private bool extractOnlyNewerFiles; | ||
24 | private bool enableOffsetOpen; | ||
25 | |||
26 | #region Constructors | ||
27 | |||
28 | /// <summary> | ||
29 | /// Creates a new ArchiveFileStreamContext with a archive file and | ||
30 | /// no default directory or file mapping. | ||
31 | /// </summary> | ||
32 | /// <param name="archiveFile">The path to a archive file that will be | ||
33 | /// created or extracted.</param> | ||
34 | public ArchiveFileStreamContext(string archiveFile) | ||
35 | : this(archiveFile, null, null) | ||
36 | { | ||
37 | } | ||
38 | |||
39 | /// <summary> | ||
40 | /// Creates a new ArchiveFileStreamContext with a archive file, default | ||
41 | /// directory and mapping from internal to external file paths. | ||
42 | /// </summary> | ||
43 | /// <param name="archiveFile">The path to a archive file that will be | ||
44 | /// created or extracted.</param> | ||
45 | /// <param name="directory">The default root directory where files will be | ||
46 | /// located, optional.</param> | ||
47 | /// <param name="files">A mapping from internal file paths to external file | ||
48 | /// paths, optional.</param> | ||
49 | /// <remarks> | ||
50 | /// If the mapping is not null and a file is not included in the mapping, | ||
51 | /// the file will be skipped. | ||
52 | /// <para>If the external path in the mapping is a simple file name or | ||
53 | /// relative file path, it will be concatenated onto the default directory, | ||
54 | /// if one was specified.</para> | ||
55 | /// <para>For more about how the default directory and files mapping are | ||
56 | /// used, see <see cref="OpenFileReadStream"/> and | ||
57 | /// <see cref="OpenFileWriteStream"/>.</para> | ||
58 | /// </remarks> | ||
59 | public ArchiveFileStreamContext( | ||
60 | string archiveFile, | ||
61 | string directory, | ||
62 | IDictionary<string, string> files) | ||
63 | : this(new string[] { archiveFile }, directory, files) | ||
64 | { | ||
65 | if (archiveFile == null) | ||
66 | { | ||
67 | throw new ArgumentNullException("archiveFile"); | ||
68 | } | ||
69 | } | ||
70 | |||
71 | /// <summary> | ||
72 | /// Creates a new ArchiveFileStreamContext with a list of archive files, | ||
73 | /// a default directory and a mapping from internal to external file paths. | ||
74 | /// </summary> | ||
75 | /// <param name="archiveFiles">A list of paths to archive files that will be | ||
76 | /// created or extracted.</param> | ||
77 | /// <param name="directory">The default root directory where files will be | ||
78 | /// located, optional.</param> | ||
79 | /// <param name="files">A mapping from internal file paths to external file | ||
80 | /// paths, optional.</param> | ||
81 | /// <remarks> | ||
82 | /// When creating chained archives, the <paramref name="archiveFiles"/> list | ||
83 | /// should include at least enough archives to handle the entire set of | ||
84 | /// input files, based on the maximum archive size that is passed to the | ||
85 | /// <see cref="CompressionEngine"/>.<see | ||
86 | /// cref="CompressionEngine.Pack(IPackStreamContext,IEnumerable<string>,long)"/>. | ||
87 | /// <para>If the mapping is not null and a file is not included in the mapping, | ||
88 | /// the file will be skipped.</para> | ||
89 | /// <para>If the external path in the mapping is a simple file name or | ||
90 | /// relative file path, it will be concatenated onto the default directory, | ||
91 | /// if one was specified.</para> | ||
92 | /// <para>For more about how the default directory and files mapping are used, | ||
93 | /// see <see cref="OpenFileReadStream"/> and | ||
94 | /// <see cref="OpenFileWriteStream"/>.</para> | ||
95 | /// </remarks> | ||
96 | public ArchiveFileStreamContext( | ||
97 | IList<string> archiveFiles, | ||
98 | string directory, | ||
99 | IDictionary<string, string> files) | ||
100 | { | ||
101 | if (archiveFiles == null || archiveFiles.Count == 0) | ||
102 | { | ||
103 | throw new ArgumentNullException("archiveFiles"); | ||
104 | } | ||
105 | |||
106 | this.archiveFiles = archiveFiles; | ||
107 | this.directory = directory; | ||
108 | this.files = files; | ||
109 | } | ||
110 | |||
111 | #endregion | ||
112 | |||
113 | #region Properties | ||
114 | |||
115 | /// <summary> | ||
116 | /// Gets or sets the list of archive files that are created or extracted. | ||
117 | /// </summary> | ||
118 | /// <value>The list of archive files that are created or extracted.</value> | ||
119 | public IList<string> ArchiveFiles | ||
120 | { | ||
121 | get | ||
122 | { | ||
123 | return this.archiveFiles; | ||
124 | } | ||
125 | } | ||
126 | |||
127 | /// <summary> | ||
128 | /// Gets or sets the default root directory where files are located. | ||
129 | /// </summary> | ||
130 | /// <value>The default root directory where files are located.</value> | ||
131 | /// <remarks> | ||
132 | /// For details about how the default directory is used, | ||
133 | /// see <see cref="OpenFileReadStream"/> and <see cref="OpenFileWriteStream"/>. | ||
134 | /// </remarks> | ||
135 | public string Directory | ||
136 | { | ||
137 | get | ||
138 | { | ||
139 | return this.directory; | ||
140 | } | ||
141 | } | ||
142 | |||
143 | /// <summary> | ||
144 | /// Gets or sets the mapping from internal file paths to external file paths. | ||
145 | /// </summary> | ||
146 | /// <value>A mapping from internal file paths to external file paths.</value> | ||
147 | /// <remarks> | ||
148 | /// For details about how the files mapping is used, | ||
149 | /// see <see cref="OpenFileReadStream"/> and <see cref="OpenFileWriteStream"/>. | ||
150 | /// </remarks> | ||
151 | public IDictionary<string, string> Files | ||
152 | { | ||
153 | get | ||
154 | { | ||
155 | return this.files; | ||
156 | } | ||
157 | } | ||
158 | |||
159 | /// <summary> | ||
160 | /// Gets or sets a flag that can prevent extracted files from overwriting | ||
161 | /// newer files that already exist. | ||
162 | /// </summary> | ||
163 | /// <value>True to prevent overwriting newer files that already exist | ||
164 | /// during extraction; false to always extract from the archive regardless | ||
165 | /// of existing files.</value> | ||
166 | public bool ExtractOnlyNewerFiles | ||
167 | { | ||
168 | get | ||
169 | { | ||
170 | return this.extractOnlyNewerFiles; | ||
171 | } | ||
172 | |||
173 | set | ||
174 | { | ||
175 | this.extractOnlyNewerFiles = value; | ||
176 | } | ||
177 | } | ||
178 | |||
179 | /// <summary> | ||
180 | /// Gets or sets a flag that enables creating or extracting an archive | ||
181 | /// at an offset within an existing file. (This is typically used to open | ||
182 | /// archive-based self-extracting packages.) | ||
183 | /// </summary> | ||
184 | /// <value>True to search an existing package file for an archive offset | ||
185 | /// or the end of the file;/ false to always create or open a plain | ||
186 | /// archive file.</value> | ||
187 | public bool EnableOffsetOpen | ||
188 | { | ||
189 | get | ||
190 | { | ||
191 | return this.enableOffsetOpen; | ||
192 | } | ||
193 | |||
194 | set | ||
195 | { | ||
196 | this.enableOffsetOpen = value; | ||
197 | } | ||
198 | } | ||
199 | |||
200 | #endregion | ||
201 | |||
202 | #region IPackStreamContext Members | ||
203 | |||
204 | /// <summary> | ||
205 | /// Gets the name of the archive with a specified number. | ||
206 | /// </summary> | ||
207 | /// <param name="archiveNumber">The 0-based index of the archive within | ||
208 | /// the chain.</param> | ||
209 | /// <returns>The name of the requested archive. May be an empty string | ||
210 | /// for non-chained archives, but may never be null.</returns> | ||
211 | /// <remarks>This method returns the file name of the archive from the | ||
212 | /// <see cref="archiveFiles"/> list with the specified index, or an empty | ||
213 | /// string if the archive number is outside the bounds of the list. The | ||
214 | /// file name should not include any directory path.</remarks> | ||
215 | public virtual string GetArchiveName(int archiveNumber) | ||
216 | { | ||
217 | if (archiveNumber < this.archiveFiles.Count) | ||
218 | { | ||
219 | return Path.GetFileName(this.archiveFiles[archiveNumber]); | ||
220 | } | ||
221 | |||
222 | return String.Empty; | ||
223 | } | ||
224 | |||
225 | /// <summary> | ||
226 | /// Opens a stream for writing an archive. | ||
227 | /// </summary> | ||
228 | /// <param name="archiveNumber">The 0-based index of the archive within | ||
229 | /// the chain.</param> | ||
230 | /// <param name="archiveName">The name of the archive that was returned | ||
231 | /// by <see cref="GetArchiveName"/>.</param> | ||
232 | /// <param name="truncate">True if the stream should be truncated when | ||
233 | /// opened (if it already exists); false if an existing stream is being | ||
234 | /// re-opened for writing additional data.</param> | ||
235 | /// <param name="compressionEngine">Instance of the compression engine | ||
236 | /// doing the operations.</param> | ||
237 | /// <returns>A writable Stream where the compressed archive bytes will be | ||
238 | /// written, or null to cancel the archive creation.</returns> | ||
239 | /// <remarks> | ||
240 | /// This method opens the file from the <see cref="ArchiveFiles"/> list | ||
241 | /// with the specified index. If the archive number is outside the bounds | ||
242 | /// of the list, this method returns null. | ||
243 | /// <para>If the <see cref="EnableOffsetOpen"/> flag is set, this method | ||
244 | /// will seek to the start of any existing archive in the file, or to the | ||
245 | /// end of the file if the existing file is not an archive.</para> | ||
246 | /// </remarks> | ||
247 | public virtual Stream OpenArchiveWriteStream( | ||
248 | int archiveNumber, | ||
249 | string archiveName, | ||
250 | bool truncate, | ||
251 | CompressionEngine compressionEngine) | ||
252 | { | ||
253 | if (archiveNumber >= this.archiveFiles.Count) | ||
254 | { | ||
255 | return null; | ||
256 | } | ||
257 | |||
258 | if (String.IsNullOrEmpty(archiveName)) | ||
259 | { | ||
260 | throw new ArgumentNullException("archiveName"); | ||
261 | } | ||
262 | |||
263 | // All archives must be in the same directory, | ||
264 | // so always use the directory from the first archive. | ||
265 | string archiveFile = Path.Combine( | ||
266 | Path.GetDirectoryName(this.archiveFiles[0]), archiveName); | ||
267 | Stream stream = File.Open( | ||
268 | archiveFile, | ||
269 | (truncate ? FileMode.OpenOrCreate : FileMode.Open), | ||
270 | FileAccess.ReadWrite); | ||
271 | |||
272 | if (this.enableOffsetOpen) | ||
273 | { | ||
274 | long offset = compressionEngine.FindArchiveOffset( | ||
275 | new DuplicateStream(stream)); | ||
276 | |||
277 | // If this is not an archive file, append the archive to it. | ||
278 | if (offset < 0) | ||
279 | { | ||
280 | offset = stream.Length; | ||
281 | } | ||
282 | |||
283 | if (offset > 0) | ||
284 | { | ||
285 | stream = new OffsetStream(stream, offset); | ||
286 | } | ||
287 | |||
288 | stream.Seek(0, SeekOrigin.Begin); | ||
289 | } | ||
290 | |||
291 | if (truncate) | ||
292 | { | ||
293 | // Truncate the stream, in case a larger old archive starts here. | ||
294 | stream.SetLength(0); | ||
295 | } | ||
296 | |||
297 | return stream; | ||
298 | } | ||
299 | |||
300 | /// <summary> | ||
301 | /// Closes a stream where an archive package was written. | ||
302 | /// </summary> | ||
303 | /// <param name="archiveNumber">The 0-based index of the archive within | ||
304 | /// the chain.</param> | ||
305 | /// <param name="archiveName">The name of the archive that was previously | ||
306 | /// returned by <see cref="GetArchiveName"/>.</param> | ||
307 | /// <param name="stream">A stream that was previously returned by | ||
308 | /// <see cref="OpenArchiveWriteStream"/> and is now ready to be closed.</param> | ||
309 | public virtual void CloseArchiveWriteStream( | ||
310 | int archiveNumber, | ||
311 | string archiveName, | ||
312 | Stream stream) | ||
313 | { | ||
314 | if (stream != null) | ||
315 | { | ||
316 | stream.Close(); | ||
317 | |||
318 | FileStream fileStream = stream as FileStream; | ||
319 | if (fileStream != null) | ||
320 | { | ||
321 | string streamFile = fileStream.Name; | ||
322 | if (!String.IsNullOrEmpty(archiveName) && | ||
323 | archiveName != Path.GetFileName(streamFile)) | ||
324 | { | ||
325 | string archiveFile = Path.Combine( | ||
326 | Path.GetDirectoryName(this.archiveFiles[0]), archiveName); | ||
327 | if (File.Exists(archiveFile)) | ||
328 | { | ||
329 | File.Delete(archiveFile); | ||
330 | } | ||
331 | File.Move(streamFile, archiveFile); | ||
332 | } | ||
333 | } | ||
334 | } | ||
335 | } | ||
336 | |||
337 | /// <summary> | ||
338 | /// Opens a stream to read a file that is to be included in an archive. | ||
339 | /// </summary> | ||
340 | /// <param name="path">The path of the file within the archive.</param> | ||
341 | /// <param name="attributes">The returned attributes of the opened file, | ||
342 | /// to be stored in the archive.</param> | ||
343 | /// <param name="lastWriteTime">The returned last-modified time of the | ||
344 | /// opened file, to be stored in the archive.</param> | ||
345 | /// <returns>A readable Stream where the file bytes will be read from | ||
346 | /// before they are compressed, or null to skip inclusion of the file and | ||
347 | /// continue to the next file.</returns> | ||
348 | /// <remarks> | ||
349 | /// This method opens a file using the following logic: | ||
350 | /// <list> | ||
351 | /// <item>If the <see cref="Directory"/> and the <see cref="Files"/> mapping | ||
352 | /// are both null, the path is treated as relative to the current directory, | ||
353 | /// and that file is opened.</item> | ||
354 | /// <item>If the <see cref="Directory"/> is not null but the <see cref="Files"/> | ||
355 | /// mapping is null, the path is treated as relative to that directory, and | ||
356 | /// that file is opened.</item> | ||
357 | /// <item>If the <see cref="Directory"/> is null but the <see cref="Files"/> | ||
358 | /// mapping is not null, the path parameter is used as a key into the mapping, | ||
359 | /// and the resulting value is the file path that is opened, relative to the | ||
360 | /// current directory (or it may be an absolute path). If no mapping exists, | ||
361 | /// the file is skipped.</item> | ||
362 | /// <item>If both the <see cref="Directory"/> and the <see cref="Files"/> | ||
363 | /// mapping are specified, the path parameter is used as a key into the | ||
364 | /// mapping, and the resulting value is the file path that is opened, relative | ||
365 | /// to the specified directory (or it may be an absolute path). If no mapping | ||
366 | /// exists, the file is skipped.</item> | ||
367 | /// </list> | ||
368 | /// </remarks> | ||
369 | public virtual Stream OpenFileReadStream( | ||
370 | string path, out FileAttributes attributes, out DateTime lastWriteTime) | ||
371 | { | ||
372 | string filePath = this.TranslateFilePath(path); | ||
373 | |||
374 | if (filePath == null) | ||
375 | { | ||
376 | attributes = FileAttributes.Normal; | ||
377 | lastWriteTime = DateTime.Now; | ||
378 | return null; | ||
379 | } | ||
380 | |||
381 | attributes = File.GetAttributes(filePath); | ||
382 | lastWriteTime = File.GetLastWriteTime(filePath); | ||
383 | return File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); | ||
384 | } | ||
385 | |||
386 | /// <summary> | ||
387 | /// Closes a stream that has been used to read a file. | ||
388 | /// </summary> | ||
389 | /// <param name="path">The path of the file within the archive; the same as | ||
390 | /// the path provided when the stream was opened.</param> | ||
391 | /// <param name="stream">A stream that was previously returned by | ||
392 | /// <see cref="OpenFileReadStream"/> and is now ready to be closed.</param> | ||
393 | public virtual void CloseFileReadStream(string path, Stream stream) | ||
394 | { | ||
395 | if (stream != null) | ||
396 | { | ||
397 | stream.Close(); | ||
398 | } | ||
399 | } | ||
400 | |||
401 | /// <summary> | ||
402 | /// Gets extended parameter information specific to the compression format | ||
403 | /// being used. | ||
404 | /// </summary> | ||
405 | /// <param name="optionName">Name of the option being requested.</param> | ||
406 | /// <param name="parameters">Parameters for the option; for per-file options, | ||
407 | /// the first parameter is typically the internal file path.</param> | ||
408 | /// <returns>Option value, or null to use the default behavior.</returns> | ||
409 | /// <remarks> | ||
410 | /// This implementation does not handle any options. Subclasses may override | ||
411 | /// this method to allow for non-default behavior. | ||
412 | /// </remarks> | ||
413 | public virtual object GetOption(string optionName, object[] parameters) | ||
414 | { | ||
415 | return null; | ||
416 | } | ||
417 | |||
418 | #endregion | ||
419 | |||
420 | #region IUnpackStreamContext Members | ||
421 | |||
422 | /// <summary> | ||
423 | /// Opens the archive stream for reading. | ||
424 | /// </summary> | ||
425 | /// <param name="archiveNumber">The zero-based index of the archive to | ||
426 | /// open.</param> | ||
427 | /// <param name="archiveName">The name of the archive being opened.</param> | ||
428 | /// <param name="compressionEngine">Instance of the compression engine | ||
429 | /// doing the operations.</param> | ||
430 | /// <returns>A stream from which archive bytes are read, or null to cancel | ||
431 | /// extraction of the archive.</returns> | ||
432 | /// <remarks> | ||
433 | /// This method opens the file from the <see cref="ArchiveFiles"/> list with | ||
434 | /// the specified index. If the archive number is outside the bounds of the | ||
435 | /// list, this method returns null. | ||
436 | /// <para>If the <see cref="EnableOffsetOpen"/> flag is set, this method will | ||
437 | /// seek to the start of any existing archive in the file, or to the end of | ||
438 | /// the file if the existing file is not an archive.</para> | ||
439 | /// </remarks> | ||
440 | public virtual Stream OpenArchiveReadStream( | ||
441 | int archiveNumber, string archiveName, CompressionEngine compressionEngine) | ||
442 | { | ||
443 | if (archiveNumber >= this.archiveFiles.Count) | ||
444 | { | ||
445 | return null; | ||
446 | } | ||
447 | |||
448 | string archiveFile = this.archiveFiles[archiveNumber]; | ||
449 | Stream stream = File.Open( | ||
450 | archiveFile, FileMode.Open, FileAccess.Read, FileShare.Read); | ||
451 | |||
452 | if (this.enableOffsetOpen) | ||
453 | { | ||
454 | long offset = compressionEngine.FindArchiveOffset( | ||
455 | new DuplicateStream(stream)); | ||
456 | if (offset > 0) | ||
457 | { | ||
458 | stream = new OffsetStream(stream, offset); | ||
459 | } | ||
460 | else | ||
461 | { | ||
462 | stream.Seek(0, SeekOrigin.Begin); | ||
463 | } | ||
464 | } | ||
465 | |||
466 | return stream; | ||
467 | } | ||
468 | |||
469 | /// <summary> | ||
470 | /// Closes a stream where an archive was read. | ||
471 | /// </summary> | ||
472 | /// <param name="archiveNumber">The archive number of the stream | ||
473 | /// to close.</param> | ||
474 | /// <param name="archiveName">The name of the archive being closed.</param> | ||
475 | /// <param name="stream">The stream that was previously returned by | ||
476 | /// <see cref="OpenArchiveReadStream"/> and is now ready to be closed.</param> | ||
477 | public virtual void CloseArchiveReadStream( | ||
478 | int archiveNumber, string archiveName, Stream stream) | ||
479 | { | ||
480 | if (stream != null) | ||
481 | { | ||
482 | stream.Close(); | ||
483 | } | ||
484 | } | ||
485 | |||
486 | /// <summary> | ||
487 | /// Opens a stream for writing extracted file bytes. | ||
488 | /// </summary> | ||
489 | /// <param name="path">The path of the file within the archive.</param> | ||
490 | /// <param name="fileSize">The uncompressed size of the file to be | ||
491 | /// extracted.</param> | ||
492 | /// <param name="lastWriteTime">The last write time of the file to be | ||
493 | /// extracted.</param> | ||
494 | /// <returns>A stream where extracted file bytes are to be written, or null | ||
495 | /// to skip extraction of the file and continue to the next file.</returns> | ||
496 | /// <remarks> | ||
497 | /// This method opens a file using the following logic: | ||
498 | /// <list> | ||
499 | /// <item>If the <see cref="Directory"/> and the <see cref="Files"/> mapping | ||
500 | /// are both null, the path is treated as relative to the current directory, | ||
501 | /// and that file is opened.</item> | ||
502 | /// <item>If the <see cref="Directory"/> is not null but the <see cref="Files"/> | ||
503 | /// mapping is null, the path is treated as relative to that directory, and | ||
504 | /// that file is opened.</item> | ||
505 | /// <item>If the <see cref="Directory"/> is null but the <see cref="Files"/> | ||
506 | /// mapping is not null, the path parameter is used as a key into the mapping, | ||
507 | /// and the resulting value is the file path that is opened, relative to the | ||
508 | /// current directory (or it may be an absolute path). If no mapping exists, | ||
509 | /// the file is skipped.</item> | ||
510 | /// <item>If both the <see cref="Directory"/> and the <see cref="Files"/> | ||
511 | /// mapping are specified, the path parameter is used as a key into the | ||
512 | /// mapping, and the resulting value is the file path that is opened, | ||
513 | /// relative to the specified directory (or it may be an absolute path). | ||
514 | /// If no mapping exists, the file is skipped.</item> | ||
515 | /// </list> | ||
516 | /// <para>If the <see cref="ExtractOnlyNewerFiles"/> flag is set, the file | ||
517 | /// is skipped if a file currently exists in the same path with an equal | ||
518 | /// or newer write time.</para> | ||
519 | /// </remarks> | ||
520 | public virtual Stream OpenFileWriteStream( | ||
521 | string path, | ||
522 | long fileSize, | ||
523 | DateTime lastWriteTime) | ||
524 | { | ||
525 | string filePath = this.TranslateFilePath(path); | ||
526 | |||
527 | if (filePath == null) | ||
528 | { | ||
529 | return null; | ||
530 | } | ||
531 | |||
532 | FileInfo file = new FileInfo(filePath); | ||
533 | if (file.Exists) | ||
534 | { | ||
535 | if (this.extractOnlyNewerFiles && lastWriteTime != DateTime.MinValue) | ||
536 | { | ||
537 | if (file.LastWriteTime >= lastWriteTime) | ||
538 | { | ||
539 | return null; | ||
540 | } | ||
541 | } | ||
542 | |||
543 | // Clear attributes that will prevent overwriting the file. | ||
544 | // (The final attributes will be set after the file is unpacked.) | ||
545 | FileAttributes attributesToClear = | ||
546 | FileAttributes.ReadOnly | FileAttributes.Hidden | FileAttributes.System; | ||
547 | if ((file.Attributes & attributesToClear) != 0) | ||
548 | { | ||
549 | file.Attributes &= ~attributesToClear; | ||
550 | } | ||
551 | } | ||
552 | |||
553 | if (!file.Directory.Exists) | ||
554 | { | ||
555 | file.Directory.Create(); | ||
556 | } | ||
557 | |||
558 | return File.Open( | ||
559 | filePath, FileMode.Create, FileAccess.Write, FileShare.None); | ||
560 | } | ||
561 | |||
562 | /// <summary> | ||
563 | /// Closes a stream where an extracted file was written. | ||
564 | /// </summary> | ||
565 | /// <param name="path">The path of the file within the archive.</param> | ||
566 | /// <param name="stream">The stream that was previously returned by | ||
567 | /// <see cref="OpenFileWriteStream"/> and is now ready to be closed.</param> | ||
568 | /// <param name="attributes">The attributes of the extracted file.</param> | ||
569 | /// <param name="lastWriteTime">The last write time of the file.</param> | ||
570 | /// <remarks> | ||
571 | /// After closing the extracted file stream, this method applies the date | ||
572 | /// and attributes to that file. | ||
573 | /// </remarks> | ||
574 | public virtual void CloseFileWriteStream( | ||
575 | string path, | ||
576 | Stream stream, | ||
577 | FileAttributes attributes, | ||
578 | DateTime lastWriteTime) | ||
579 | { | ||
580 | if (stream != null) | ||
581 | { | ||
582 | stream.Close(); | ||
583 | } | ||
584 | |||
585 | string filePath = this.TranslateFilePath(path); | ||
586 | if (filePath != null) | ||
587 | { | ||
588 | FileInfo file = new FileInfo(filePath); | ||
589 | |||
590 | if (lastWriteTime != DateTime.MinValue) | ||
591 | { | ||
592 | try | ||
593 | { | ||
594 | file.LastWriteTime = lastWriteTime; | ||
595 | } | ||
596 | catch (ArgumentException) | ||
597 | { | ||
598 | } | ||
599 | catch (IOException) | ||
600 | { | ||
601 | } | ||
602 | } | ||
603 | |||
604 | try | ||
605 | { | ||
606 | file.Attributes = attributes; | ||
607 | } | ||
608 | catch (IOException) | ||
609 | { | ||
610 | } | ||
611 | } | ||
612 | } | ||
613 | |||
614 | #endregion | ||
615 | |||
616 | #region Private utility methods | ||
617 | |||
618 | /// <summary> | ||
619 | /// Translates an internal file path to an external file path using the | ||
620 | /// <see cref="Directory"/> and the <see cref="Files"/> mapping, according to | ||
621 | /// rules documented in <see cref="OpenFileReadStream"/> and | ||
622 | /// <see cref="OpenFileWriteStream"/>. | ||
623 | /// </summary> | ||
624 | /// <param name="path">The path of the file with the archive.</param> | ||
625 | /// <returns>The external path of the file, or null if there is no | ||
626 | /// valid translation.</returns> | ||
627 | private string TranslateFilePath(string path) | ||
628 | { | ||
629 | string filePath; | ||
630 | if (this.files != null) | ||
631 | { | ||
632 | filePath = this.files[path]; | ||
633 | } | ||
634 | else | ||
635 | { | ||
636 | this.ValidateArchivePath(path); | ||
637 | |||
638 | filePath = path; | ||
639 | } | ||
640 | |||
641 | if (filePath != null) | ||
642 | { | ||
643 | if (this.directory != null) | ||
644 | { | ||
645 | filePath = Path.Combine(this.directory, filePath); | ||
646 | } | ||
647 | } | ||
648 | |||
649 | return filePath; | ||
650 | } | ||
651 | |||
652 | private void ValidateArchivePath(string filePath) | ||
653 | { | ||
654 | string basePath = Path.GetFullPath(String.IsNullOrEmpty(this.directory) ? Environment.CurrentDirectory : this.directory); | ||
655 | string path = Path.GetFullPath(Path.Combine(basePath, filePath)); | ||
656 | if (!path.StartsWith(basePath, StringComparison.InvariantCultureIgnoreCase)) | ||
657 | { | ||
658 | throw new InvalidDataException("Archive cannot contain files with absolute or traversal paths."); | ||
659 | } | ||
660 | } | ||
661 | |||
662 | #endregion | ||
663 | } | ||
664 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.Compression/ArchiveInfo.cs b/src/dtf/WixToolset.Dtf.Compression/ArchiveInfo.cs new file mode 100644 index 00000000..b5da4ea8 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.Compression/ArchiveInfo.cs | |||
@@ -0,0 +1,781 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Dtf.Compression | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Globalization; | ||
9 | using System.Text; | ||
10 | using System.Text.RegularExpressions; | ||
11 | using System.Runtime.Serialization; | ||
12 | using System.Diagnostics.CodeAnalysis; | ||
13 | |||
14 | /// <summary> | ||
15 | /// Abstract object representing a compressed archive on disk; | ||
16 | /// provides access to file-based operations on the archive. | ||
17 | /// </summary> | ||
18 | [Serializable] | ||
19 | public abstract class ArchiveInfo : FileSystemInfo | ||
20 | { | ||
21 | /// <summary> | ||
22 | /// Creates a new ArchiveInfo object representing an archive in a | ||
23 | /// specified path. | ||
24 | /// </summary> | ||
25 | /// <param name="path">The path to the archive. When creating an archive, | ||
26 | /// this file does not necessarily exist yet.</param> | ||
27 | protected ArchiveInfo(string path) : base() | ||
28 | { | ||
29 | if (path == null) | ||
30 | { | ||
31 | throw new ArgumentNullException("path"); | ||
32 | } | ||
33 | |||
34 | // protected instance members inherited from FileSystemInfo: | ||
35 | this.OriginalPath = path; | ||
36 | this.FullPath = Path.GetFullPath(path); | ||
37 | } | ||
38 | |||
39 | /// <summary> | ||
40 | /// Initializes a new instance of the ArchiveInfo class with serialized data. | ||
41 | /// </summary> | ||
42 | /// <param name="info">The SerializationInfo that holds the serialized object | ||
43 | /// data about the exception being thrown.</param> | ||
44 | /// <param name="context">The StreamingContext that contains contextual | ||
45 | /// information about the source or destination.</param> | ||
46 | protected ArchiveInfo(SerializationInfo info, StreamingContext context) | ||
47 | : base(info, context) | ||
48 | { | ||
49 | } | ||
50 | |||
51 | /// <summary> | ||
52 | /// Gets the directory that contains the archive. | ||
53 | /// </summary> | ||
54 | /// <value>A DirectoryInfo object representing the parent directory of the | ||
55 | /// archive.</value> | ||
56 | public DirectoryInfo Directory | ||
57 | { | ||
58 | get | ||
59 | { | ||
60 | return new DirectoryInfo(Path.GetDirectoryName(this.FullName)); | ||
61 | } | ||
62 | } | ||
63 | |||
64 | /// <summary> | ||
65 | /// Gets the full path of the directory that contains the archive. | ||
66 | /// </summary> | ||
67 | /// <value>The full path of the directory that contains the archive.</value> | ||
68 | public string DirectoryName | ||
69 | { | ||
70 | get | ||
71 | { | ||
72 | return Path.GetDirectoryName(this.FullName); | ||
73 | } | ||
74 | } | ||
75 | |||
76 | /// <summary> | ||
77 | /// Gets the size of the archive. | ||
78 | /// </summary> | ||
79 | /// <value>The size of the archive in bytes.</value> | ||
80 | public long Length | ||
81 | { | ||
82 | get | ||
83 | { | ||
84 | return new FileInfo(this.FullName).Length; | ||
85 | } | ||
86 | } | ||
87 | |||
88 | /// <summary> | ||
89 | /// Gets the file name of the archive. | ||
90 | /// </summary> | ||
91 | /// <value>The file name of the archive, not including any path.</value> | ||
92 | public override string Name | ||
93 | { | ||
94 | get | ||
95 | { | ||
96 | return Path.GetFileName(this.FullName); | ||
97 | } | ||
98 | } | ||
99 | |||
100 | /// <summary> | ||
101 | /// Checks if the archive exists. | ||
102 | /// </summary> | ||
103 | /// <value>True if the archive exists; else false.</value> | ||
104 | public override bool Exists | ||
105 | { | ||
106 | get | ||
107 | { | ||
108 | return File.Exists(this.FullName); | ||
109 | } | ||
110 | } | ||
111 | |||
112 | /// <summary> | ||
113 | /// Gets the full path of the archive. | ||
114 | /// </summary> | ||
115 | /// <returns>The full path of the archive.</returns> | ||
116 | public override string ToString() | ||
117 | { | ||
118 | return this.FullName; | ||
119 | } | ||
120 | |||
121 | /// <summary> | ||
122 | /// Deletes the archive. | ||
123 | /// </summary> | ||
124 | public override void Delete() | ||
125 | { | ||
126 | File.Delete(this.FullName); | ||
127 | } | ||
128 | |||
129 | /// <summary> | ||
130 | /// Copies an existing archive to another location. | ||
131 | /// </summary> | ||
132 | /// <param name="destFileName">The destination file path.</param> | ||
133 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
134 | public void CopyTo(string destFileName) | ||
135 | { | ||
136 | File.Copy(this.FullName, destFileName); | ||
137 | } | ||
138 | |||
139 | /// <summary> | ||
140 | /// Copies an existing archive to another location, optionally | ||
141 | /// overwriting the destination file. | ||
142 | /// </summary> | ||
143 | /// <param name="destFileName">The destination file path.</param> | ||
144 | /// <param name="overwrite">If true, the destination file will be | ||
145 | /// overwritten if it exists.</param> | ||
146 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
147 | public void CopyTo(string destFileName, bool overwrite) | ||
148 | { | ||
149 | File.Copy(this.FullName, destFileName, overwrite); | ||
150 | } | ||
151 | |||
152 | /// <summary> | ||
153 | /// Moves an existing archive to another location. | ||
154 | /// </summary> | ||
155 | /// <param name="destFileName">The destination file path.</param> | ||
156 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
157 | public void MoveTo(string destFileName) | ||
158 | { | ||
159 | File.Move(this.FullName, destFileName); | ||
160 | this.FullPath = Path.GetFullPath(destFileName); | ||
161 | } | ||
162 | |||
163 | /// <summary> | ||
164 | /// Checks if the archive contains a valid archive header. | ||
165 | /// </summary> | ||
166 | /// <returns>True if the file is a valid archive; false otherwise.</returns> | ||
167 | public bool IsValid() | ||
168 | { | ||
169 | using (Stream stream = File.OpenRead(this.FullName)) | ||
170 | { | ||
171 | using (CompressionEngine compressionEngine = this.CreateCompressionEngine()) | ||
172 | { | ||
173 | return compressionEngine.FindArchiveOffset(stream) >= 0; | ||
174 | } | ||
175 | } | ||
176 | } | ||
177 | |||
178 | /// <summary> | ||
179 | /// Gets information about the files contained in the archive. | ||
180 | /// </summary> | ||
181 | /// <returns>A list of <see cref="ArchiveFileInfo"/> objects, each | ||
182 | /// containing information about a file in the archive.</returns> | ||
183 | public IList<ArchiveFileInfo> GetFiles() | ||
184 | { | ||
185 | return this.InternalGetFiles((Predicate<string>) null); | ||
186 | } | ||
187 | |||
188 | /// <summary> | ||
189 | /// Gets information about the certain files contained in the archive file. | ||
190 | /// </summary> | ||
191 | /// <param name="searchPattern">The search string, such as | ||
192 | /// "*.txt".</param> | ||
193 | /// <returns>A list of <see cref="ArchiveFileInfo"/> objects, each containing | ||
194 | /// information about a file in the archive.</returns> | ||
195 | public IList<ArchiveFileInfo> GetFiles(string searchPattern) | ||
196 | { | ||
197 | if (searchPattern == null) | ||
198 | { | ||
199 | throw new ArgumentNullException("searchPattern"); | ||
200 | } | ||
201 | |||
202 | string regexPattern = String.Format( | ||
203 | CultureInfo.InvariantCulture, | ||
204 | "^{0}$", | ||
205 | Regex.Escape(searchPattern).Replace("\\*", ".*").Replace("\\?", ".")); | ||
206 | Regex regex = new Regex( | ||
207 | regexPattern, | ||
208 | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); | ||
209 | |||
210 | return this.InternalGetFiles( | ||
211 | delegate(string match) | ||
212 | { | ||
213 | return regex.IsMatch(match); | ||
214 | }); | ||
215 | } | ||
216 | |||
217 | /// <summary> | ||
218 | /// Extracts all files from an archive to a destination directory. | ||
219 | /// </summary> | ||
220 | /// <param name="destDirectory">Directory where the files are to be | ||
221 | /// extracted.</param> | ||
222 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
223 | public void Unpack(string destDirectory) | ||
224 | { | ||
225 | this.Unpack(destDirectory, null); | ||
226 | } | ||
227 | |||
228 | /// <summary> | ||
229 | /// Extracts all files from an archive to a destination directory, | ||
230 | /// optionally extracting only newer files. | ||
231 | /// </summary> | ||
232 | /// <param name="destDirectory">Directory where the files are to be | ||
233 | /// extracted.</param> | ||
234 | /// <param name="progressHandler">Handler for receiving progress | ||
235 | /// information; this may be null if progress is not desired.</param> | ||
236 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
237 | public void Unpack( | ||
238 | string destDirectory, | ||
239 | EventHandler<ArchiveProgressEventArgs> progressHandler) | ||
240 | { | ||
241 | using (CompressionEngine compressionEngine = this.CreateCompressionEngine()) | ||
242 | { | ||
243 | compressionEngine.Progress += progressHandler; | ||
244 | ArchiveFileStreamContext streamContext = | ||
245 | new ArchiveFileStreamContext(this.FullName, destDirectory, null); | ||
246 | streamContext.EnableOffsetOpen = true; | ||
247 | compressionEngine.Unpack(streamContext, null); | ||
248 | } | ||
249 | } | ||
250 | |||
251 | /// <summary> | ||
252 | /// Extracts a single file from the archive. | ||
253 | /// </summary> | ||
254 | /// <param name="fileName">The name of the file in the archive. Also | ||
255 | /// includes the internal path of the file, if any. File name matching | ||
256 | /// is case-insensitive.</param> | ||
257 | /// <param name="destFileName">The path where the file is to be | ||
258 | /// extracted on disk.</param> | ||
259 | /// <remarks>If <paramref name="destFileName"/> already exists, | ||
260 | /// it will be overwritten.</remarks> | ||
261 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
262 | public void UnpackFile(string fileName, string destFileName) | ||
263 | { | ||
264 | if (fileName == null) | ||
265 | { | ||
266 | throw new ArgumentNullException("fileName"); | ||
267 | } | ||
268 | |||
269 | if (destFileName == null) | ||
270 | { | ||
271 | throw new ArgumentNullException("destFileName"); | ||
272 | } | ||
273 | |||
274 | this.UnpackFiles( | ||
275 | new string[] { fileName }, | ||
276 | null, | ||
277 | new string[] { destFileName }); | ||
278 | } | ||
279 | |||
280 | /// <summary> | ||
281 | /// Extracts multiple files from the archive. | ||
282 | /// </summary> | ||
283 | /// <param name="fileNames">The names of the files in the archive. | ||
284 | /// Each name includes the internal path of the file, if any. File name | ||
285 | /// matching is case-insensitive.</param> | ||
286 | /// <param name="destDirectory">This parameter may be null, but if | ||
287 | /// specified it is the root directory for any relative paths in | ||
288 | /// <paramref name="destFileNames"/>.</param> | ||
289 | /// <param name="destFileNames">The paths where the files are to be | ||
290 | /// extracted on disk. If this parameter is null, the files will be | ||
291 | /// extracted with the names from the archive.</param> | ||
292 | /// <remarks> | ||
293 | /// If any extracted files already exist on disk, they will be overwritten. | ||
294 | /// <p>The <paramref name="destDirectory"/> and | ||
295 | /// <paramref name="destFileNames"/> parameters cannot both be null.</p> | ||
296 | /// </remarks> | ||
297 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
298 | public void UnpackFiles( | ||
299 | IList<string> fileNames, | ||
300 | string destDirectory, | ||
301 | IList<string> destFileNames) | ||
302 | { | ||
303 | this.UnpackFiles(fileNames, destDirectory, destFileNames, null); | ||
304 | } | ||
305 | |||
306 | /// <summary> | ||
307 | /// Extracts multiple files from the archive, optionally extracting | ||
308 | /// only newer files. | ||
309 | /// </summary> | ||
310 | /// <param name="fileNames">The names of the files in the archive. | ||
311 | /// Each name includes the internal path of the file, if any. File name | ||
312 | /// matching is case-insensitive.</param> | ||
313 | /// <param name="destDirectory">This parameter may be null, but if | ||
314 | /// specified it is the root directory for any relative paths in | ||
315 | /// <paramref name="destFileNames"/>.</param> | ||
316 | /// <param name="destFileNames">The paths where the files are to be | ||
317 | /// extracted on disk. If this parameter is null, the files will be | ||
318 | /// extracted with the names from the archive.</param> | ||
319 | /// <param name="progressHandler">Handler for receiving progress information; | ||
320 | /// this may be null if progress is not desired.</param> | ||
321 | /// <remarks> | ||
322 | /// If any extracted files already exist on disk, they will be overwritten. | ||
323 | /// <p>The <paramref name="destDirectory"/> and | ||
324 | /// <paramref name="destFileNames"/> parameters cannot both be null.</p> | ||
325 | /// </remarks> | ||
326 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
327 | public void UnpackFiles( | ||
328 | IList<string> fileNames, | ||
329 | string destDirectory, | ||
330 | IList<string> destFileNames, | ||
331 | EventHandler<ArchiveProgressEventArgs> progressHandler) | ||
332 | { | ||
333 | if (fileNames == null) | ||
334 | { | ||
335 | throw new ArgumentNullException("fileNames"); | ||
336 | } | ||
337 | |||
338 | if (destFileNames == null) | ||
339 | { | ||
340 | if (destDirectory == null) | ||
341 | { | ||
342 | throw new ArgumentNullException("destFileNames"); | ||
343 | } | ||
344 | |||
345 | destFileNames = fileNames; | ||
346 | } | ||
347 | |||
348 | if (destFileNames.Count != fileNames.Count) | ||
349 | { | ||
350 | throw new ArgumentOutOfRangeException("destFileNames"); | ||
351 | } | ||
352 | |||
353 | IDictionary<string, string> files = | ||
354 | ArchiveInfo.CreateStringDictionary(fileNames, destFileNames); | ||
355 | this.UnpackFileSet(files, destDirectory, progressHandler); | ||
356 | } | ||
357 | |||
358 | /// <summary> | ||
359 | /// Extracts multiple files from the archive. | ||
360 | /// </summary> | ||
361 | /// <param name="fileNames">A mapping from internal file paths to | ||
362 | /// external file paths. Case-senstivity when matching internal paths | ||
363 | /// depends on the IDictionary implementation.</param> | ||
364 | /// <param name="destDirectory">This parameter may be null, but if | ||
365 | /// specified it is the root directory for any relative external paths | ||
366 | /// in <paramref name="fileNames"/>.</param> | ||
367 | /// <remarks> | ||
368 | /// If any extracted files already exist on disk, they will be overwritten. | ||
369 | /// </remarks> | ||
370 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
371 | public void UnpackFileSet( | ||
372 | IDictionary<string, string> fileNames, | ||
373 | string destDirectory) | ||
374 | { | ||
375 | this.UnpackFileSet(fileNames, destDirectory, null); | ||
376 | } | ||
377 | |||
378 | /// <summary> | ||
379 | /// Extracts multiple files from the archive. | ||
380 | /// </summary> | ||
381 | /// <param name="fileNames">A mapping from internal file paths to | ||
382 | /// external file paths. Case-senstivity when matching internal | ||
383 | /// paths depends on the IDictionary implementation.</param> | ||
384 | /// <param name="destDirectory">This parameter may be null, but if | ||
385 | /// specified it is the root directory for any relative external | ||
386 | /// paths in <paramref name="fileNames"/>.</param> | ||
387 | /// <param name="progressHandler">Handler for receiving progress | ||
388 | /// information; this may be null if progress is not desired.</param> | ||
389 | /// <remarks> | ||
390 | /// If any extracted files already exist on disk, they will be overwritten. | ||
391 | /// </remarks> | ||
392 | [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")] | ||
393 | public void UnpackFileSet( | ||
394 | IDictionary<string, string> fileNames, | ||
395 | string destDirectory, | ||
396 | EventHandler<ArchiveProgressEventArgs> progressHandler) | ||
397 | { | ||
398 | if (fileNames == null) | ||
399 | { | ||
400 | throw new ArgumentNullException("fileNames"); | ||
401 | } | ||
402 | |||
403 | using (CompressionEngine compressionEngine = this.CreateCompressionEngine()) | ||
404 | { | ||
405 | compressionEngine.Progress += progressHandler; | ||
406 | ArchiveFileStreamContext streamContext = | ||
407 | new ArchiveFileStreamContext(this.FullName, destDirectory, fileNames); | ||
408 | streamContext.EnableOffsetOpen = true; | ||
409 | compressionEngine.Unpack( | ||
410 | streamContext, | ||
411 | delegate(string match) | ||
412 | { | ||
413 | return fileNames.ContainsKey(match); | ||
414 | }); | ||
415 | } | ||
416 | } | ||
417 | |||
418 | /// <summary> | ||
419 | /// Opens a file inside the archive for reading without actually | ||
420 | /// extracting the file to disk. | ||
421 | /// </summary> | ||
422 | /// <param name="fileName">The name of the file in the archive. Also | ||
423 | /// includes the internal path of the file, if any. File name matching | ||
424 | /// is case-insensitive.</param> | ||
425 | /// <returns> | ||
426 | /// A stream for reading directly from the packed file. Like any stream | ||
427 | /// this should be closed/disposed as soon as it is no longer needed. | ||
428 | /// </returns> | ||
429 | public Stream OpenRead(string fileName) | ||
430 | { | ||
431 | Stream archiveStream = File.OpenRead(this.FullName); | ||
432 | CompressionEngine compressionEngine = this.CreateCompressionEngine(); | ||
433 | Stream fileStream = compressionEngine.Unpack(archiveStream, fileName); | ||
434 | |||
435 | // Attach the archiveStream and compressionEngine to the | ||
436 | // fileStream so they get disposed when the fileStream is disposed. | ||
437 | return new CargoStream(fileStream, archiveStream, compressionEngine); | ||
438 | } | ||
439 | |||
440 | /// <summary> | ||
441 | /// Opens a file inside the archive for reading text with UTF-8 encoding | ||
442 | /// without actually extracting the file to disk. | ||
443 | /// </summary> | ||
444 | /// <param name="fileName">The name of the file in the archive. Also | ||
445 | /// includes the internal path of the file, if any. File name matching | ||
446 | /// is case-insensitive.</param> | ||
447 | /// <returns> | ||
448 | /// A reader for reading text directly from the packed file. Like any reader | ||
449 | /// this should be closed/disposed as soon as it is no longer needed. | ||
450 | /// </returns> | ||
451 | /// <remarks> | ||
452 | /// To open an archived text file with different encoding, use the | ||
453 | /// <see cref="OpenRead" /> method and pass the returned stream to one of | ||
454 | /// the <see cref="StreamReader" /> constructor overloads. | ||
455 | /// </remarks> | ||
456 | public StreamReader OpenText(string fileName) | ||
457 | { | ||
458 | return new StreamReader(this.OpenRead(fileName)); | ||
459 | } | ||
460 | |||
461 | /// <summary> | ||
462 | /// Compresses all files in a directory into the archive. | ||
463 | /// Does not include subdirectories. | ||
464 | /// </summary> | ||
465 | /// <param name="sourceDirectory">The directory containing the | ||
466 | /// files to be included.</param> | ||
467 | /// <remarks> | ||
468 | /// Uses maximum compression level. | ||
469 | /// </remarks> | ||
470 | public void Pack(string sourceDirectory) | ||
471 | { | ||
472 | this.Pack(sourceDirectory, false, CompressionLevel.Max, null); | ||
473 | } | ||
474 | |||
475 | /// <summary> | ||
476 | /// Compresses all files in a directory into the archive, optionally | ||
477 | /// including subdirectories. | ||
478 | /// </summary> | ||
479 | /// <param name="sourceDirectory">This is the root directory | ||
480 | /// for to pack all files.</param> | ||
481 | /// <param name="includeSubdirectories">If true, recursively include | ||
482 | /// files in subdirectories.</param> | ||
483 | /// <param name="compLevel">The compression level used when creating | ||
484 | /// the archive.</param> | ||
485 | /// <param name="progressHandler">Handler for receiving progress information; | ||
486 | /// this may be null if progress is not desired.</param> | ||
487 | /// <remarks> | ||
488 | /// The files are stored in the archive using their relative file paths in | ||
489 | /// the directory tree, if supported by the archive file format. | ||
490 | /// </remarks> | ||
491 | public void Pack( | ||
492 | string sourceDirectory, | ||
493 | bool includeSubdirectories, | ||
494 | CompressionLevel compLevel, | ||
495 | EventHandler<ArchiveProgressEventArgs> progressHandler) | ||
496 | { | ||
497 | IList<string> files = this.GetRelativeFilePathsInDirectoryTree( | ||
498 | sourceDirectory, includeSubdirectories); | ||
499 | this.PackFiles(sourceDirectory, files, files, compLevel, progressHandler); | ||
500 | } | ||
501 | |||
502 | /// <summary> | ||
503 | /// Compresses files into the archive, specifying the names used to | ||
504 | /// store the files in the archive. | ||
505 | /// </summary> | ||
506 | /// <param name="sourceDirectory">This parameter may be null, but | ||
507 | /// if specified it is the root directory | ||
508 | /// for any relative paths in <paramref name="sourceFileNames"/>.</param> | ||
509 | /// <param name="sourceFileNames">The list of files to be included in | ||
510 | /// the archive.</param> | ||
511 | /// <param name="fileNames">The names of the files as they are stored | ||
512 | /// in the archive. Each name | ||
513 | /// includes the internal path of the file, if any. This parameter may | ||
514 | /// be null, in which case the files are stored in the archive with their | ||
515 | /// source file names and no path information.</param> | ||
516 | /// <remarks> | ||
517 | /// Uses maximum compression level. | ||
518 | /// <p>Duplicate items in the <paramref name="fileNames"/> array will cause | ||
519 | /// an <see cref="ArchiveException"/>.</p> | ||
520 | /// </remarks> | ||
521 | public void PackFiles( | ||
522 | string sourceDirectory, | ||
523 | IList<string> sourceFileNames, | ||
524 | IList<string> fileNames) | ||
525 | { | ||
526 | this.PackFiles( | ||
527 | sourceDirectory, | ||
528 | sourceFileNames, | ||
529 | fileNames, | ||
530 | CompressionLevel.Max, | ||
531 | null); | ||
532 | } | ||
533 | |||
534 | /// <summary> | ||
535 | /// Compresses files into the archive, specifying the names used to | ||
536 | /// store the files in the archive. | ||
537 | /// </summary> | ||
538 | /// <param name="sourceDirectory">This parameter may be null, but if | ||
539 | /// specified it is the root directory | ||
540 | /// for any relative paths in <paramref name="sourceFileNames"/>.</param> | ||
541 | /// <param name="sourceFileNames">The list of files to be included in | ||
542 | /// the archive.</param> | ||
543 | /// <param name="fileNames">The names of the files as they are stored in | ||
544 | /// the archive. Each name includes the internal path of the file, if any. | ||
545 | /// This parameter may be null, in which case the files are stored in the | ||
546 | /// archive with their source file names and no path information.</param> | ||
547 | /// <param name="compLevel">The compression level used when creating the | ||
548 | /// archive.</param> | ||
549 | /// <param name="progressHandler">Handler for receiving progress information; | ||
550 | /// this may be null if progress is not desired.</param> | ||
551 | /// <remarks> | ||
552 | /// Duplicate items in the <paramref name="fileNames"/> array will cause | ||
553 | /// an <see cref="ArchiveException"/>. | ||
554 | /// </remarks> | ||
555 | public void PackFiles( | ||
556 | string sourceDirectory, | ||
557 | IList<string> sourceFileNames, | ||
558 | IList<string> fileNames, | ||
559 | CompressionLevel compLevel, | ||
560 | EventHandler<ArchiveProgressEventArgs> progressHandler) | ||
561 | { | ||
562 | if (sourceFileNames == null) | ||
563 | { | ||
564 | throw new ArgumentNullException("sourceFileNames"); | ||
565 | } | ||
566 | |||
567 | if (fileNames == null) | ||
568 | { | ||
569 | string[] fileNamesArray = new string[sourceFileNames.Count]; | ||
570 | for (int i = 0; i < sourceFileNames.Count; i++) | ||
571 | { | ||
572 | fileNamesArray[i] = Path.GetFileName(sourceFileNames[i]); | ||
573 | } | ||
574 | |||
575 | fileNames = fileNamesArray; | ||
576 | } | ||
577 | else if (fileNames.Count != sourceFileNames.Count) | ||
578 | { | ||
579 | throw new ArgumentOutOfRangeException("fileNames"); | ||
580 | } | ||
581 | |||
582 | using (CompressionEngine compressionEngine = this.CreateCompressionEngine()) | ||
583 | { | ||
584 | compressionEngine.Progress += progressHandler; | ||
585 | IDictionary<string, string> contextFiles = | ||
586 | ArchiveInfo.CreateStringDictionary(fileNames, sourceFileNames); | ||
587 | ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext( | ||
588 | this.FullName, sourceDirectory, contextFiles); | ||
589 | streamContext.EnableOffsetOpen = true; | ||
590 | compressionEngine.CompressionLevel = compLevel; | ||
591 | compressionEngine.Pack(streamContext, fileNames); | ||
592 | } | ||
593 | } | ||
594 | |||
595 | /// <summary> | ||
596 | /// Compresses files into the archive, specifying the names used | ||
597 | /// to store the files in the archive. | ||
598 | /// </summary> | ||
599 | /// <param name="sourceDirectory">This parameter may be null, but if | ||
600 | /// specified it is the root directory | ||
601 | /// for any relative paths in <paramref name="fileNames"/>.</param> | ||
602 | /// <param name="fileNames">A mapping from internal file paths to | ||
603 | /// external file paths.</param> | ||
604 | /// <remarks> | ||
605 | /// Uses maximum compression level. | ||
606 | /// </remarks> | ||
607 | public void PackFileSet( | ||
608 | string sourceDirectory, | ||
609 | IDictionary<string, string> fileNames) | ||
610 | { | ||
611 | this.PackFileSet(sourceDirectory, fileNames, CompressionLevel.Max, null); | ||
612 | } | ||
613 | |||
614 | /// <summary> | ||
615 | /// Compresses files into the archive, specifying the names used to | ||
616 | /// store the files in the archive. | ||
617 | /// </summary> | ||
618 | /// <param name="sourceDirectory">This parameter may be null, but if | ||
619 | /// specified it is the root directory | ||
620 | /// for any relative paths in <paramref name="fileNames"/>.</param> | ||
621 | /// <param name="fileNames">A mapping from internal file paths to | ||
622 | /// external file paths.</param> | ||
623 | /// <param name="compLevel">The compression level used when creating | ||
624 | /// the archive.</param> | ||
625 | /// <param name="progressHandler">Handler for receiving progress information; | ||
626 | /// this may be null if progress is not desired.</param> | ||
627 | public void PackFileSet( | ||
628 | string sourceDirectory, | ||
629 | IDictionary<string, string> fileNames, | ||
630 | CompressionLevel compLevel, | ||
631 | EventHandler<ArchiveProgressEventArgs> progressHandler) | ||
632 | { | ||
633 | if (fileNames == null) | ||
634 | { | ||
635 | throw new ArgumentNullException("fileNames"); | ||
636 | } | ||
637 | |||
638 | string[] fileNamesArray = new string[fileNames.Count]; | ||
639 | fileNames.Keys.CopyTo(fileNamesArray, 0); | ||
640 | |||
641 | using (CompressionEngine compressionEngine = this.CreateCompressionEngine()) | ||
642 | { | ||
643 | compressionEngine.Progress += progressHandler; | ||
644 | ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext( | ||
645 | this.FullName, sourceDirectory, fileNames); | ||
646 | streamContext.EnableOffsetOpen = true; | ||
647 | compressionEngine.CompressionLevel = compLevel; | ||
648 | compressionEngine.Pack(streamContext, fileNamesArray); | ||
649 | } | ||
650 | } | ||
651 | |||
652 | /// <summary> | ||
653 | /// Given a directory, gets the relative paths of all files in the | ||
654 | /// directory, optionally including all subdirectories. | ||
655 | /// </summary> | ||
656 | /// <param name="dir">The directory to search.</param> | ||
657 | /// <param name="includeSubdirectories">True to include subdirectories | ||
658 | /// in the search.</param> | ||
659 | /// <returns>A list of file paths relative to the directory.</returns> | ||
660 | internal IList<string> GetRelativeFilePathsInDirectoryTree( | ||
661 | string dir, bool includeSubdirectories) | ||
662 | { | ||
663 | IList<string> fileList = new List<string>(); | ||
664 | this.RecursiveGetRelativeFilePathsInDirectoryTree( | ||
665 | dir, String.Empty, includeSubdirectories, fileList); | ||
666 | return fileList; | ||
667 | } | ||
668 | |||
669 | /// <summary> | ||
670 | /// Retrieves information about one file from this archive. | ||
671 | /// </summary> | ||
672 | /// <param name="path">Path of the file in the archive.</param> | ||
673 | /// <returns>File information, or null if the file was not found | ||
674 | /// in the archive.</returns> | ||
675 | internal ArchiveFileInfo GetFile(string path) | ||
676 | { | ||
677 | IList<ArchiveFileInfo> files = this.InternalGetFiles( | ||
678 | delegate(string match) | ||
679 | { | ||
680 | return String.Compare( | ||
681 | match, path, true, CultureInfo.InvariantCulture) == 0; | ||
682 | }); | ||
683 | return (files != null && files.Count > 0 ? files[0] : null); | ||
684 | } | ||
685 | |||
686 | /// <summary> | ||
687 | /// Creates a compression engine that does the low-level work for | ||
688 | /// this object. | ||
689 | /// </summary> | ||
690 | /// <returns>A new compression engine instance that matches the specific | ||
691 | /// subclass of archive.</returns> | ||
692 | /// <remarks> | ||
693 | /// Each instance will be <see cref="CompressionEngine.Dispose()"/>d | ||
694 | /// immediately after use. | ||
695 | /// </remarks> | ||
696 | protected abstract CompressionEngine CreateCompressionEngine(); | ||
697 | |||
698 | /// <summary> | ||
699 | /// Creates a case-insensitive dictionary mapping from one list of | ||
700 | /// strings to the other. | ||
701 | /// </summary> | ||
702 | /// <param name="keys">List of keys.</param> | ||
703 | /// <param name="values">List of values that are mapped 1-to-1 to | ||
704 | /// the keys.</param> | ||
705 | /// <returns>A filled dictionary of the strings.</returns> | ||
706 | private static IDictionary<string, string> CreateStringDictionary( | ||
707 | IList<string> keys, IList<string> values) | ||
708 | { | ||
709 | IDictionary<string, string> stringDict = | ||
710 | new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); | ||
711 | for (int i = 0; i < keys.Count; i++) | ||
712 | { | ||
713 | stringDict.Add(keys[i], values[i]); | ||
714 | } | ||
715 | |||
716 | return stringDict; | ||
717 | } | ||
718 | |||
719 | /// <summary> | ||
720 | /// Recursive-descent helper function for | ||
721 | /// GetRelativeFilePathsInDirectoryTree. | ||
722 | /// </summary> | ||
723 | /// <param name="dir">The root directory of the search.</param> | ||
724 | /// <param name="relativeDir">The relative directory to be | ||
725 | /// processed now.</param> | ||
726 | /// <param name="includeSubdirectories">True to descend into | ||
727 | /// subdirectories.</param> | ||
728 | /// <param name="fileList">List of files found so far.</param> | ||
729 | private void RecursiveGetRelativeFilePathsInDirectoryTree( | ||
730 | string dir, | ||
731 | string relativeDir, | ||
732 | bool includeSubdirectories, | ||
733 | IList<string> fileList) | ||
734 | { | ||
735 | foreach (string file in System.IO.Directory.GetFiles(dir)) | ||
736 | { | ||
737 | string fileName = Path.GetFileName(file); | ||
738 | fileList.Add(Path.Combine(relativeDir, fileName)); | ||
739 | } | ||
740 | |||
741 | if (includeSubdirectories) | ||
742 | { | ||
743 | foreach (string subDir in System.IO.Directory.GetDirectories(dir)) | ||
744 | { | ||
745 | string subDirName = Path.GetFileName(subDir); | ||
746 | this.RecursiveGetRelativeFilePathsInDirectoryTree( | ||
747 | Path.Combine(dir, subDirName), | ||
748 | Path.Combine(relativeDir, subDirName), | ||
749 | includeSubdirectories, | ||
750 | fileList); | ||
751 | } | ||
752 | } | ||
753 | } | ||
754 | |||
755 | /// <summary> | ||
756 | /// Uses a CompressionEngine to get ArchiveFileInfo objects from this | ||
757 | /// archive, and then associates them with this ArchiveInfo instance. | ||
758 | /// </summary> | ||
759 | /// <param name="fileFilter">Optional predicate that can determine | ||
760 | /// which files to process.</param> | ||
761 | /// <returns>A list of <see cref="ArchiveFileInfo"/> objects, each | ||
762 | /// containing information about a file in the archive.</returns> | ||
763 | private IList<ArchiveFileInfo> InternalGetFiles(Predicate<string> fileFilter) | ||
764 | { | ||
765 | using (CompressionEngine compressionEngine = this.CreateCompressionEngine()) | ||
766 | { | ||
767 | ArchiveFileStreamContext streamContext = | ||
768 | new ArchiveFileStreamContext(this.FullName, null, null); | ||
769 | streamContext.EnableOffsetOpen = true; | ||
770 | IList<ArchiveFileInfo> files = | ||
771 | compressionEngine.GetFileInfo(streamContext, fileFilter); | ||
772 | for (int i = 0; i < files.Count; i++) | ||
773 | { | ||
774 | files[i].Archive = this; | ||
775 | } | ||
776 | |||
777 | return files; | ||
778 | } | ||
779 | } | ||
780 | } | ||
781 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.Compression/ArchiveProgressEventArgs.cs b/src/dtf/WixToolset.Dtf.Compression/ArchiveProgressEventArgs.cs new file mode 100644 index 00000000..5d96d714 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.Compression/ArchiveProgressEventArgs.cs | |||
@@ -0,0 +1,307 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Dtf.Compression | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Text; | ||
8 | |||
9 | /// <summary> | ||
10 | /// Contains the data reported in an archive progress event. | ||
11 | /// </summary> | ||
12 | public class ArchiveProgressEventArgs : EventArgs | ||
13 | { | ||
14 | private ArchiveProgressType progressType; | ||
15 | |||
16 | private string currentFileName; | ||
17 | private int currentFileNumber; | ||
18 | private int totalFiles; | ||
19 | private long currentFileBytesProcessed; | ||
20 | private long currentFileTotalBytes; | ||
21 | |||
22 | private string currentArchiveName; | ||
23 | private short currentArchiveNumber; | ||
24 | private short totalArchives; | ||
25 | private long currentArchiveBytesProcessed; | ||
26 | private long currentArchiveTotalBytes; | ||
27 | |||
28 | private long fileBytesProcessed; | ||
29 | private long totalFileBytes; | ||
30 | |||
31 | /// <summary> | ||
32 | /// Creates a new ArchiveProgressEventArgs object from specified event parameters. | ||
33 | /// </summary> | ||
34 | /// <param name="progressType">type of status message</param> | ||
35 | /// <param name="currentFileName">name of the file being processed</param> | ||
36 | /// <param name="currentFileNumber">number of the current file being processed</param> | ||
37 | /// <param name="totalFiles">total number of files to be processed</param> | ||
38 | /// <param name="currentFileBytesProcessed">number of bytes processed so far when compressing or extracting a file</param> | ||
39 | /// <param name="currentFileTotalBytes">total number of bytes in the current file</param> | ||
40 | /// <param name="currentArchiveName">name of the current Archive</param> | ||
41 | /// <param name="currentArchiveNumber">current Archive number, when processing a chained set of Archives</param> | ||
42 | /// <param name="totalArchives">total number of Archives in a chained set</param> | ||
43 | /// <param name="currentArchiveBytesProcessed">number of compressed bytes processed so far during an extraction</param> | ||
44 | /// <param name="currentArchiveTotalBytes">total number of compressed bytes to be processed during an extraction</param> | ||
45 | /// <param name="fileBytesProcessed">number of uncompressed file bytes processed so far</param> | ||
46 | /// <param name="totalFileBytes">total number of uncompressed file bytes to be processed</param> | ||
47 | public ArchiveProgressEventArgs( | ||
48 | ArchiveProgressType progressType, | ||
49 | string currentFileName, | ||
50 | int currentFileNumber, | ||
51 | int totalFiles, | ||
52 | long currentFileBytesProcessed, | ||
53 | long currentFileTotalBytes, | ||
54 | string currentArchiveName, | ||
55 | int currentArchiveNumber, | ||
56 | int totalArchives, | ||
57 | long currentArchiveBytesProcessed, | ||
58 | long currentArchiveTotalBytes, | ||
59 | long fileBytesProcessed, | ||
60 | long totalFileBytes) | ||
61 | { | ||
62 | this.progressType = progressType; | ||
63 | this.currentFileName = currentFileName; | ||
64 | this.currentFileNumber = currentFileNumber; | ||
65 | this.totalFiles = totalFiles; | ||
66 | this.currentFileBytesProcessed = currentFileBytesProcessed; | ||
67 | this.currentFileTotalBytes = currentFileTotalBytes; | ||
68 | this.currentArchiveName = currentArchiveName; | ||
69 | this.currentArchiveNumber = (short) currentArchiveNumber; | ||
70 | this.totalArchives = (short) totalArchives; | ||
71 | this.currentArchiveBytesProcessed = currentArchiveBytesProcessed; | ||
72 | this.currentArchiveTotalBytes = currentArchiveTotalBytes; | ||
73 | this.fileBytesProcessed = fileBytesProcessed; | ||
74 | this.totalFileBytes = totalFileBytes; | ||
75 | } | ||
76 | |||
77 | /// <summary> | ||
78 | /// Gets the type of status message. | ||
79 | /// </summary> | ||
80 | /// <value>A <see cref="ArchiveProgressType"/> value indicating what type of progress event occurred.</value> | ||
81 | /// <remarks> | ||
82 | /// The handler may choose to ignore some types of progress events. | ||
83 | /// For example, if the handler will only list each file as it is | ||
84 | /// compressed/extracted, it can ignore events that | ||
85 | /// are not of type <see cref="ArchiveProgressType.FinishFile"/>. | ||
86 | /// </remarks> | ||
87 | public ArchiveProgressType ProgressType | ||
88 | { | ||
89 | get | ||
90 | { | ||
91 | return this.progressType; | ||
92 | } | ||
93 | } | ||
94 | |||
95 | /// <summary> | ||
96 | /// Gets the name of the file being processed. (The name of the file within the Archive; not the external | ||
97 | /// file path.) Also includes the internal path of the file, if any. Valid for | ||
98 | /// <see cref="ArchiveProgressType.StartFile"/>, <see cref="ArchiveProgressType.PartialFile"/>, | ||
99 | /// and <see cref="ArchiveProgressType.FinishFile"/> messages. | ||
100 | /// </summary> | ||
101 | /// <value>The name of the file currently being processed, or null if processing | ||
102 | /// is currently at the stream or archive level.</value> | ||
103 | public string CurrentFileName | ||
104 | { | ||
105 | get | ||
106 | { | ||
107 | return this.currentFileName; | ||
108 | } | ||
109 | } | ||
110 | |||
111 | /// <summary> | ||
112 | /// Gets the number of the current file being processed. The first file is number 0, and the last file | ||
113 | /// is <see cref="TotalFiles"/>-1. Valid for <see cref="ArchiveProgressType.StartFile"/>, | ||
114 | /// <see cref="ArchiveProgressType.PartialFile"/>, and <see cref="ArchiveProgressType.FinishFile"/> messages. | ||
115 | /// </summary> | ||
116 | /// <value>The number of the file currently being processed, or the most recent | ||
117 | /// file processed if processing is currently at the stream or archive level.</value> | ||
118 | public int CurrentFileNumber | ||
119 | { | ||
120 | get | ||
121 | { | ||
122 | return this.currentFileNumber; | ||
123 | } | ||
124 | } | ||
125 | |||
126 | /// <summary> | ||
127 | /// Gets the total number of files to be processed. Valid for all message types. | ||
128 | /// </summary> | ||
129 | /// <value>The total number of files to be processed that are known so far.</value> | ||
130 | public int TotalFiles | ||
131 | { | ||
132 | get | ||
133 | { | ||
134 | return this.totalFiles; | ||
135 | } | ||
136 | } | ||
137 | |||
138 | /// <summary> | ||
139 | /// Gets the number of bytes processed so far when compressing or extracting a file. Valid for | ||
140 | /// <see cref="ArchiveProgressType.StartFile"/>, <see cref="ArchiveProgressType.PartialFile"/>, | ||
141 | /// and <see cref="ArchiveProgressType.FinishFile"/> messages. | ||
142 | /// </summary> | ||
143 | /// <value>The number of uncompressed bytes processed so far for the current file, | ||
144 | /// or 0 if processing is currently at the stream or archive level.</value> | ||
145 | public long CurrentFileBytesProcessed | ||
146 | { | ||
147 | get | ||
148 | { | ||
149 | return this.currentFileBytesProcessed; | ||
150 | } | ||
151 | } | ||
152 | |||
153 | /// <summary> | ||
154 | /// Gets the total number of bytes in the current file. Valid for <see cref="ArchiveProgressType.StartFile"/>, | ||
155 | /// <see cref="ArchiveProgressType.PartialFile"/>, and <see cref="ArchiveProgressType.FinishFile"/> messages. | ||
156 | /// </summary> | ||
157 | /// <value>The uncompressed size of the current file being processed, | ||
158 | /// or 0 if processing is currently at the stream or archive level.</value> | ||
159 | public long CurrentFileTotalBytes | ||
160 | { | ||
161 | get | ||
162 | { | ||
163 | return this.currentFileTotalBytes; | ||
164 | } | ||
165 | } | ||
166 | |||
167 | /// <summary> | ||
168 | /// Gets the name of the current archive. Not necessarily the name of the archive on disk. | ||
169 | /// Valid for all message types. | ||
170 | /// </summary> | ||
171 | /// <value>The name of the current archive, or an empty string if no name was specified.</value> | ||
172 | public string CurrentArchiveName | ||
173 | { | ||
174 | get | ||
175 | { | ||
176 | return this.currentArchiveName; | ||
177 | } | ||
178 | } | ||
179 | |||
180 | /// <summary> | ||
181 | /// Gets the current archive number, when processing a chained set of archives. Valid for all message types. | ||
182 | /// </summary> | ||
183 | /// <value>The number of the current archive.</value> | ||
184 | /// <remarks>The first archive is number 0, and the last archive is | ||
185 | /// <see cref="TotalArchives"/>-1.</remarks> | ||
186 | public int CurrentArchiveNumber | ||
187 | { | ||
188 | get | ||
189 | { | ||
190 | return this.currentArchiveNumber; | ||
191 | } | ||
192 | } | ||
193 | |||
194 | /// <summary> | ||
195 | /// Gets the total number of known archives in a chained set. Valid for all message types. | ||
196 | /// </summary> | ||
197 | /// <value>The total number of known archives in a chained set.</value> | ||
198 | /// <remarks> | ||
199 | /// When using the compression option to auto-split into multiple archives based on data size, | ||
200 | /// this value will not be accurate until the end. | ||
201 | /// </remarks> | ||
202 | public int TotalArchives | ||
203 | { | ||
204 | get | ||
205 | { | ||
206 | return this.totalArchives; | ||
207 | } | ||
208 | } | ||
209 | |||
210 | /// <summary> | ||
211 | /// Gets the number of compressed bytes processed so far during extraction | ||
212 | /// of the current archive. Valid for all extraction messages. | ||
213 | /// </summary> | ||
214 | /// <value>The number of compressed bytes processed so far during extraction | ||
215 | /// of the current archive.</value> | ||
216 | public long CurrentArchiveBytesProcessed | ||
217 | { | ||
218 | get | ||
219 | { | ||
220 | return this.currentArchiveBytesProcessed; | ||
221 | } | ||
222 | } | ||
223 | |||
224 | /// <summary> | ||
225 | /// Gets the total number of compressed bytes to be processed during extraction | ||
226 | /// of the current archive. Valid for all extraction messages. | ||
227 | /// </summary> | ||
228 | /// <value>The total number of compressed bytes to be processed during extraction | ||
229 | /// of the current archive.</value> | ||
230 | public long CurrentArchiveTotalBytes | ||
231 | { | ||
232 | get | ||
233 | { | ||
234 | return this.currentArchiveTotalBytes; | ||
235 | } | ||
236 | } | ||
237 | |||
238 | /// <summary> | ||
239 | /// Gets the number of uncompressed bytes processed so far among all files. Valid for all message types. | ||
240 | /// </summary> | ||
241 | /// <value>The number of uncompressed file bytes processed so far among all files.</value> | ||
242 | /// <remarks> | ||
243 | /// When compared to <see cref="TotalFileBytes"/>, this can be used as a measure of overall progress. | ||
244 | /// </remarks> | ||
245 | public long FileBytesProcessed | ||
246 | { | ||
247 | get | ||
248 | { | ||
249 | return this.fileBytesProcessed; | ||
250 | } | ||
251 | } | ||
252 | |||
253 | /// <summary> | ||
254 | /// Gets the total number of uncompressed file bytes to be processed. Valid for all message types. | ||
255 | /// </summary> | ||
256 | /// <value>The total number of uncompressed bytes to be processed among all files.</value> | ||
257 | public long TotalFileBytes | ||
258 | { | ||
259 | get | ||
260 | { | ||
261 | return this.totalFileBytes; | ||
262 | } | ||
263 | } | ||
264 | |||
265 | #if DEBUG | ||
266 | |||
267 | /// <summary> | ||
268 | /// Creates a string representation of the progress event. | ||
269 | /// </summary> | ||
270 | /// <returns>a listing of all event parameters and values</returns> | ||
271 | public override string ToString() | ||
272 | { | ||
273 | string formatString = | ||
274 | "{0}\n" + | ||
275 | "\t CurrentFileName = {1}\n" + | ||
276 | "\t CurrentFileNumber = {2}\n" + | ||
277 | "\t TotalFiles = {3}\n" + | ||
278 | "\t CurrentFileBytesProcessed = {4}\n" + | ||
279 | "\t CurrentFileTotalBytes = {5}\n" + | ||
280 | "\t CurrentArchiveName = {6}\n" + | ||
281 | "\t CurrentArchiveNumber = {7}\n" + | ||
282 | "\t TotalArchives = {8}\n" + | ||
283 | "\t CurrentArchiveBytesProcessed = {9}\n" + | ||
284 | "\t CurrentArchiveTotalBytes = {10}\n" + | ||
285 | "\t FileBytesProcessed = {11}\n" + | ||
286 | "\t TotalFileBytes = {12}\n"; | ||
287 | return String.Format( | ||
288 | System.Globalization.CultureInfo.InvariantCulture, | ||
289 | formatString, | ||
290 | this.ProgressType, | ||
291 | this.CurrentFileName, | ||
292 | this.CurrentFileNumber, | ||
293 | this.TotalFiles, | ||
294 | this.CurrentFileBytesProcessed, | ||
295 | this.CurrentFileTotalBytes, | ||
296 | this.CurrentArchiveName, | ||
297 | this.CurrentArchiveNumber, | ||
298 | this.TotalArchives, | ||
299 | this.CurrentArchiveBytesProcessed, | ||
300 | this.CurrentArchiveTotalBytes, | ||
301 | this.FileBytesProcessed, | ||
302 | this.TotalFileBytes); | ||
303 | } | ||
304 | |||
305 | #endif | ||
306 | } | ||
307 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.Compression/ArchiveProgressType.cs b/src/dtf/WixToolset.Dtf.Compression/ArchiveProgressType.cs new file mode 100644 index 00000000..2307c28e --- /dev/null +++ b/src/dtf/WixToolset.Dtf.Compression/ArchiveProgressType.cs | |||
@@ -0,0 +1,69 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Dtf.Compression | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Text; | ||
8 | |||
9 | /// <summary> | ||
10 | /// The type of progress event. | ||
11 | /// </summary> | ||
12 | /// <remarks> | ||
13 | /// <p>PACKING EXAMPLE: The following sequence of events might be received when | ||
14 | /// extracting a simple archive file with 2 files.</p> | ||
15 | /// <list type="table"> | ||
16 | /// <listheader><term>Message Type</term><description>Description</description></listheader> | ||
17 | /// <item><term>StartArchive</term> <description>Begin extracting archive</description></item> | ||
18 | /// <item><term>StartFile</term> <description>Begin extracting first file</description></item> | ||
19 | /// <item><term>PartialFile</term> <description>Extracting first file</description></item> | ||
20 | /// <item><term>PartialFile</term> <description>Extracting first file</description></item> | ||
21 | /// <item><term>FinishFile</term> <description>Finished extracting first file</description></item> | ||
22 | /// <item><term>StartFile</term> <description>Begin extracting second file</description></item> | ||
23 | /// <item><term>PartialFile</term> <description>Extracting second file</description></item> | ||
24 | /// <item><term>FinishFile</term> <description>Finished extracting second file</description></item> | ||
25 | /// <item><term>FinishArchive</term><description>Finished extracting archive</description></item> | ||
26 | /// </list> | ||
27 | /// <p></p> | ||
28 | /// <p>UNPACKING EXAMPLE: Packing 3 files into 2 archive chunks, where the second file is | ||
29 | /// continued to the second archive chunk.</p> | ||
30 | /// <list type="table"> | ||
31 | /// <listheader><term>Message Type</term><description>Description</description></listheader> | ||
32 | /// <item><term>StartFile</term> <description>Begin compressing first file</description></item> | ||
33 | /// <item><term>FinishFile</term> <description>Finished compressing first file</description></item> | ||
34 | /// <item><term>StartFile</term> <description>Begin compressing second file</description></item> | ||
35 | /// <item><term>PartialFile</term> <description>Compressing second file</description></item> | ||
36 | /// <item><term>PartialFile</term> <description>Compressing second file</description></item> | ||
37 | /// <item><term>FinishFile</term> <description>Finished compressing second file</description></item> | ||
38 | /// <item><term>StartArchive</term> <description>Begin writing first archive</description></item> | ||
39 | /// <item><term>PartialArchive</term><description>Writing first archive</description></item> | ||
40 | /// <item><term>FinishArchive</term> <description>Finished writing first archive</description></item> | ||
41 | /// <item><term>StartFile</term> <description>Begin compressing third file</description></item> | ||
42 | /// <item><term>PartialFile</term> <description>Compressing third file</description></item> | ||
43 | /// <item><term>FinishFile</term> <description>Finished compressing third file</description></item> | ||
44 | /// <item><term>StartArchive</term> <description>Begin writing second archive</description></item> | ||
45 | /// <item><term>PartialArchive</term><description>Writing second archive</description></item> | ||
46 | /// <item><term>FinishArchive</term> <description>Finished writing second archive</description></item> | ||
47 | /// </list> | ||
48 | /// </remarks> | ||
49 | public enum ArchiveProgressType : int | ||
50 | { | ||
51 | /// <summary>Status message before beginning the packing or unpacking an individual file.</summary> | ||
52 | StartFile, | ||
53 | |||
54 | /// <summary>Status message (possibly reported multiple times) during the process of packing or unpacking a file.</summary> | ||
55 | PartialFile, | ||
56 | |||
57 | /// <summary>Status message after completion of the packing or unpacking an individual file.</summary> | ||
58 | FinishFile, | ||
59 | |||
60 | /// <summary>Status message before beginning the packing or unpacking an archive.</summary> | ||
61 | StartArchive, | ||
62 | |||
63 | /// <summary>Status message (possibly reported multiple times) during the process of packing or unpacking an archiv.</summary> | ||
64 | PartialArchive, | ||
65 | |||
66 | /// <summary>Status message after completion of the packing or unpacking of an archive.</summary> | ||
67 | FinishArchive, | ||
68 | } | ||
69 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.Compression/BasicUnpackStreamContext.cs b/src/dtf/WixToolset.Dtf.Compression/BasicUnpackStreamContext.cs new file mode 100644 index 00000000..94d13b9c --- /dev/null +++ b/src/dtf/WixToolset.Dtf.Compression/BasicUnpackStreamContext.cs | |||
@@ -0,0 +1,90 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Dtf.Compression | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Diagnostics.CodeAnalysis; | ||
8 | |||
9 | /// <summary> | ||
10 | /// Stream context used to extract a single file from an archive into a memory stream. | ||
11 | /// </summary> | ||
12 | [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] | ||
13 | public class BasicUnpackStreamContext : IUnpackStreamContext | ||
14 | { | ||
15 | private Stream archiveStream; | ||
16 | private Stream fileStream; | ||
17 | |||
18 | /// <summary> | ||
19 | /// Creates a new BasicExtractStreamContext that reads from the specified archive stream. | ||
20 | /// </summary> | ||
21 | /// <param name="archiveStream">Archive stream to read from.</param> | ||
22 | public BasicUnpackStreamContext(Stream archiveStream) | ||
23 | { | ||
24 | this.archiveStream = archiveStream; | ||
25 | } | ||
26 | |||
27 | /// <summary> | ||
28 | /// Gets the stream for the extracted file, or null if no file was extracted. | ||
29 | /// </summary> | ||
30 | public Stream FileStream | ||
31 | { | ||
32 | get | ||
33 | { | ||
34 | return this.fileStream; | ||
35 | } | ||
36 | } | ||
37 | |||
38 | /// <summary> | ||
39 | /// Opens the archive stream for reading. Returns a DuplicateStream instance, | ||
40 | /// so the stream may be virtually opened multiple times. | ||
41 | /// </summary> | ||
42 | /// <param name="archiveNumber">The archive number to open (ignored; 0 is assumed).</param> | ||
43 | /// <param name="archiveName">The name of the archive being opened.</param> | ||
44 | /// <param name="compressionEngine">Instance of the compression engine doing the operations.</param> | ||
45 | /// <returns>A stream from which archive bytes are read.</returns> | ||
46 | public Stream OpenArchiveReadStream(int archiveNumber, string archiveName, CompressionEngine compressionEngine) | ||
47 | { | ||
48 | return new DuplicateStream(this.archiveStream); | ||
49 | } | ||
50 | |||
51 | /// <summary> | ||
52 | /// Does *not* close the stream. The archive stream should be managed by | ||
53 | /// the code that invokes the archive extraction. | ||
54 | /// </summary> | ||
55 | /// <param name="archiveNumber">The archive number of the stream to close.</param> | ||
56 | /// <param name="archiveName">The name of the archive being closed.</param> | ||
57 | /// <param name="stream">The stream being closed.</param> | ||
58 | public void CloseArchiveReadStream(int archiveNumber, string archiveName, Stream stream) | ||
59 | { | ||
60 | // Do nothing. | ||
61 | } | ||
62 | |||
63 | /// <summary> | ||
64 | /// Opens a stream for writing extracted file bytes. The returned stream is a MemoryStream | ||
65 | /// instance, so the file is extracted straight into memory. | ||
66 | /// </summary> | ||
67 | /// <param name="path">Path of the file within the archive.</param> | ||
68 | /// <param name="fileSize">The uncompressed size of the file to be extracted.</param> | ||
69 | /// <param name="lastWriteTime">The last write time of the file.</param> | ||
70 | /// <returns>A stream where extracted file bytes are to be written.</returns> | ||
71 | public Stream OpenFileWriteStream(string path, long fileSize, DateTime lastWriteTime) | ||
72 | { | ||
73 | this.fileStream = new MemoryStream(new byte[fileSize], 0, (int) fileSize, true, true); | ||
74 | return this.fileStream; | ||
75 | } | ||
76 | |||
77 | /// <summary> | ||
78 | /// Does *not* close the file stream. The file stream is saved in memory so it can | ||
79 | /// be read later. | ||
80 | /// </summary> | ||
81 | /// <param name="path">Path of the file within the archive.</param> | ||
82 | /// <param name="stream">The file stream to be closed.</param> | ||
83 | /// <param name="attributes">The attributes of the extracted file.</param> | ||
84 | /// <param name="lastWriteTime">The last write time of the file.</param> | ||
85 | public void CloseFileWriteStream(string path, Stream stream, FileAttributes attributes, DateTime lastWriteTime) | ||
86 | { | ||
87 | // Do nothing. | ||
88 | } | ||
89 | } | ||
90 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.Compression/CargoStream.cs b/src/dtf/WixToolset.Dtf.Compression/CargoStream.cs new file mode 100644 index 00000000..78798a35 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.Compression/CargoStream.cs | |||
@@ -0,0 +1,192 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Dtf.Compression | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.IO; | ||
8 | |||
9 | /// <summary> | ||
10 | /// Wraps a source stream and carries additional items that are disposed when the stream is closed. | ||
11 | /// </summary> | ||
12 | public class CargoStream : Stream | ||
13 | { | ||
14 | private Stream source; | ||
15 | private List<IDisposable> cargo; | ||
16 | |||
17 | /// <summary> | ||
18 | /// Creates a new a cargo stream. | ||
19 | /// </summary> | ||
20 | /// <param name="source">source of the stream</param> | ||
21 | /// <param name="cargo">List of additional items that are disposed when the stream is closed. | ||
22 | /// The order of the list is the order in which the items are disposed.</param> | ||
23 | public CargoStream(Stream source, params IDisposable[] cargo) | ||
24 | { | ||
25 | if (source == null) | ||
26 | { | ||
27 | throw new ArgumentNullException("source"); | ||
28 | } | ||
29 | |||
30 | this.source = source; | ||
31 | this.cargo = new List<IDisposable>(cargo); | ||
32 | } | ||
33 | |||
34 | /// <summary> | ||
35 | /// Gets the source stream of the cargo stream. | ||
36 | /// </summary> | ||
37 | public Stream Source | ||
38 | { | ||
39 | get | ||
40 | { | ||
41 | return this.source; | ||
42 | } | ||
43 | } | ||
44 | |||
45 | /// <summary> | ||
46 | /// Gets the list of additional items that are disposed when the stream is closed. | ||
47 | /// The order of the list is the order in which the items are disposed. The contents can be modified any time. | ||
48 | /// </summary> | ||
49 | public IList<IDisposable> Cargo | ||
50 | { | ||
51 | get | ||
52 | { | ||
53 | return this.cargo; | ||
54 | } | ||
55 | } | ||
56 | |||
57 | /// <summary> | ||
58 | /// Gets a value indicating whether the source stream supports reading. | ||
59 | /// </summary> | ||
60 | /// <value>true if the stream supports reading; otherwise, false.</value> | ||
61 | public override bool CanRead | ||
62 | { | ||
63 | get | ||
64 | { | ||
65 | return this.source.CanRead; | ||
66 | } | ||
67 | } | ||
68 | |||
69 | /// <summary> | ||
70 | /// Gets a value indicating whether the source stream supports writing. | ||
71 | /// </summary> | ||
72 | /// <value>true if the stream supports writing; otherwise, false.</value> | ||
73 | public override bool CanWrite | ||
74 | { | ||
75 | get | ||
76 | { | ||
77 | return this.source.CanWrite; | ||
78 | } | ||
79 | } | ||
80 | |||
81 | /// <summary> | ||
82 | /// Gets a value indicating whether the source stream supports seeking. | ||
83 | /// </summary> | ||
84 | /// <value>true if the stream supports seeking; otherwise, false.</value> | ||
85 | public override bool CanSeek | ||
86 | { | ||
87 | get | ||
88 | { | ||
89 | return this.source.CanSeek; | ||
90 | } | ||
91 | } | ||
92 | |||
93 | /// <summary> | ||
94 | /// Gets the length of the source stream. | ||
95 | /// </summary> | ||
96 | public override long Length | ||
97 | { | ||
98 | get | ||
99 | { | ||
100 | return this.source.Length; | ||
101 | } | ||
102 | } | ||
103 | |||
104 | /// <summary> | ||
105 | /// Gets or sets the position of the source stream. | ||
106 | /// </summary> | ||
107 | public override long Position | ||
108 | { | ||
109 | get | ||
110 | { | ||
111 | return this.source.Position; | ||
112 | } | ||
113 | |||
114 | set | ||
115 | { | ||
116 | this.source.Position = value; | ||
117 | } | ||
118 | } | ||
119 | |||
120 | /// <summary> | ||
121 | /// Flushes the source stream. | ||
122 | /// </summary> | ||
123 | public override void Flush() | ||
124 | { | ||
125 | this.source.Flush(); | ||
126 | } | ||
127 | |||
128 | /// <summary> | ||
129 | /// Sets the length of the source stream. | ||
130 | /// </summary> | ||
131 | /// <param name="value">The desired length of the stream in bytes.</param> | ||
132 | public override void SetLength(long value) | ||
133 | { | ||
134 | this.source.SetLength(value); | ||
135 | } | ||
136 | |||
137 | /// <summary> | ||
138 | /// Closes the source stream and also closes the additional objects that are carried. | ||
139 | /// </summary> | ||
140 | public override void Close() | ||
141 | { | ||
142 | this.source.Close(); | ||
143 | |||
144 | foreach (IDisposable cargoObject in this.cargo) | ||
145 | { | ||
146 | cargoObject.Dispose(); | ||
147 | } | ||
148 | } | ||
149 | |||
150 | /// <summary> | ||
151 | /// Reads from the source stream. | ||
152 | /// </summary> | ||
153 | /// <param name="buffer">An array of bytes. When this method returns, the buffer | ||
154 | /// contains the specified byte array with the values between offset and | ||
155 | /// (offset + count - 1) replaced by the bytes read from the source.</param> | ||
156 | /// <param name="offset">The zero-based byte offset in buffer at which to begin | ||
157 | /// storing the data read from the stream.</param> | ||
158 | /// <param name="count">The maximum number of bytes to be read from the stream.</param> | ||
159 | /// <returns>The total number of bytes read into the buffer. This can be less | ||
160 | /// than the number of bytes requested if that many bytes are not currently available, | ||
161 | /// or zero (0) if the end of the stream has been reached.</returns> | ||
162 | public override int Read(byte[] buffer, int offset, int count) | ||
163 | { | ||
164 | return this.source.Read(buffer, offset, count); | ||
165 | } | ||
166 | |||
167 | /// <summary> | ||
168 | /// Writes to the source stream. | ||
169 | /// </summary> | ||
170 | /// <param name="buffer">An array of bytes. This method copies count | ||
171 | /// bytes from buffer to the stream.</param> | ||
172 | /// <param name="offset">The zero-based byte offset in buffer at which | ||
173 | /// to begin copying bytes to the stream.</param> | ||
174 | /// <param name="count">The number of bytes to be written to the stream.</param> | ||
175 | public override void Write(byte[] buffer, int offset, int count) | ||
176 | { | ||
177 | this.source.Write(buffer, offset, count); | ||
178 | } | ||
179 | |||
180 | /// <summary> | ||
181 | /// Changes the position of the source stream. | ||
182 | /// </summary> | ||
183 | /// <param name="offset">A byte offset relative to the origin parameter.</param> | ||
184 | /// <param name="origin">A value of type SeekOrigin indicating the reference | ||
185 | /// point used to obtain the new position.</param> | ||
186 | /// <returns>The new position within the stream.</returns> | ||
187 | public override long Seek(long offset, SeekOrigin origin) | ||
188 | { | ||
189 | return this.source.Seek(offset, origin); | ||
190 | } | ||
191 | } | ||
192 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.Compression/Compression.cd b/src/dtf/WixToolset.Dtf.Compression/Compression.cd new file mode 100644 index 00000000..95012be0 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.Compression/Compression.cd | |||
@@ -0,0 +1,175 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <ClassDiagram MajorVersion="1" MinorVersion="1"> | ||
3 | <Comment CommentText="File-based classes"> | ||
4 | <Position X="2.35" Y="1.442" Height="0.408" Width="0.783" /> | ||
5 | </Comment> | ||
6 | <Comment CommentText="Stream-based classes"> | ||
7 | <Position X="9.649" Y="1.317" Height="0.4" Width="0.996" /> | ||
8 | </Comment> | ||
9 | <Class Name="WixToolset.Dtf.Compression.ArchiveException" Collapsed="true"> | ||
10 | <Position X="3" Y="4.25" Width="2" /> | ||
11 | <TypeIdentifier> | ||
12 | <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode> | ||
13 | <FileName>ArchiveException.cs</FileName> | ||
14 | </TypeIdentifier> | ||
15 | </Class> | ||
16 | <Class Name="WixToolset.Dtf.Compression.ArchiveFileInfo"> | ||
17 | <Position X="3" Y="0.5" Width="2" /> | ||
18 | <Members> | ||
19 | <Method Name="ArchiveFileInfo" Hidden="true" /> | ||
20 | <Field Name="archiveInfo" Hidden="true" /> | ||
21 | <Field Name="archiveNumber" Hidden="true" /> | ||
22 | <Field Name="attributes" Hidden="true" /> | ||
23 | <Field Name="exists" Hidden="true" /> | ||
24 | <Method Name="GetObjectData" Hidden="true" /> | ||
25 | <Field Name="initialized" Hidden="true" /> | ||
26 | <Field Name="lastWriteTime" Hidden="true" /> | ||
27 | <Field Name="length" Hidden="true" /> | ||
28 | <Field Name="name" Hidden="true" /> | ||
29 | <Field Name="path" Hidden="true" /> | ||
30 | </Members> | ||
31 | <TypeIdentifier> | ||
32 | <HashCode>AAAgAAAAIRJAAIMEAEACgARwAAEEEAAAASAAAAEAIAA=</HashCode> | ||
33 | <FileName>ArchiveFileInfo.cs</FileName> | ||
34 | </TypeIdentifier> | ||
35 | </Class> | ||
36 | <Class Name="WixToolset.Dtf.Compression.ArchiveInfo"> | ||
37 | <Position X="0.5" Y="0.5" Width="2.25" /> | ||
38 | <Members> | ||
39 | <Method Name="ArchiveInfo" Hidden="true" /> | ||
40 | <Method Name="CreateStringDictionary" Hidden="true" /> | ||
41 | <Method Name="GetFile" Hidden="true" /> | ||
42 | <Method Name="GetRelativeFilePathsInDirectoryTree" Hidden="true" /> | ||
43 | <Method Name="InternalGetFiles" Hidden="true" /> | ||
44 | <Method Name="RecursiveGetRelativeFilePathsInDirectoryTree" Hidden="true" /> | ||
45 | </Members> | ||
46 | <TypeIdentifier> | ||
47 | <HashCode>AAEAABAAIAAAAgQEAAgBAARAHAEJACAAAABEAAkAMAI=</HashCode> | ||
48 | <FileName>ArchiveInfo.cs</FileName> | ||
49 | </TypeIdentifier> | ||
50 | </Class> | ||
51 | <Class Name="WixToolset.Dtf.Compression.ArchiveFileStreamContext"> | ||
52 | <Position X="12.75" Y="0.75" Width="2.25" /> | ||
53 | <Members> | ||
54 | <Field Name="archiveFiles" Hidden="true" /> | ||
55 | <Method Name="ArchiveFileStreamContext" Hidden="true" /> | ||
56 | <Method Name="CloseArchiveReadStream" Hidden="true" /> | ||
57 | <Method Name="CloseArchiveWriteStream" Hidden="true" /> | ||
58 | <Method Name="CloseFileReadStream" Hidden="true" /> | ||
59 | <Method Name="CloseFileWriteStream" Hidden="true" /> | ||
60 | <Field Name="directory" Hidden="true" /> | ||
61 | <Field Name="enableOffsetOpen" Hidden="true" /> | ||
62 | <Field Name="extractOnlyNewerFiles" Hidden="true" /> | ||
63 | <Field Name="files" Hidden="true" /> | ||
64 | <Method Name="GetArchiveName" Hidden="true" /> | ||
65 | <Method Name="GetOption" Hidden="true" /> | ||
66 | <Method Name="OpenArchiveReadStream" Hidden="true" /> | ||
67 | <Method Name="OpenArchiveWriteStream" Hidden="true" /> | ||
68 | <Method Name="OpenFileReadStream" Hidden="true" /> | ||
69 | <Method Name="OpenFileWriteStream" Hidden="true" /> | ||
70 | <Method Name="TranslateFilePath" Hidden="true" /> | ||
71 | </Members> | ||
72 | <TypeIdentifier> | ||
73 | <HashCode>AEQAABgAAACQAACACACAAgAQAAIgAAAAACAMgAAEAKA=</HashCode> | ||
74 | <FileName>ArchiveFileStreamContext.cs</FileName> | ||
75 | </TypeIdentifier> | ||
76 | <Lollipop Position="0.2" /> | ||
77 | </Class> | ||
78 | <Class Name="WixToolset.Dtf.Compression.ArchiveProgressEventArgs"> | ||
79 | <Position X="5.25" Y="0.5" Width="2.25" /> | ||
80 | <Members> | ||
81 | <Method Name="ArchiveProgressEventArgs" Hidden="true" /> | ||
82 | <Field Name="currentArchiveBytesProcessed" Hidden="true" /> | ||
83 | <Field Name="currentArchiveName" Hidden="true" /> | ||
84 | <Field Name="currentArchiveNumber" Hidden="true" /> | ||
85 | <Field Name="currentArchiveTotalBytes" Hidden="true" /> | ||
86 | <Field Name="currentFileBytesProcessed" Hidden="true" /> | ||
87 | <Field Name="currentFileName" Hidden="true" /> | ||
88 | <Field Name="currentFileNumber" Hidden="true" /> | ||
89 | <Field Name="currentFileTotalBytes" Hidden="true" /> | ||
90 | <Field Name="fileBytesProcessed" Hidden="true" /> | ||
91 | <Field Name="progressType" Hidden="true" /> | ||
92 | <Field Name="totalArchives" Hidden="true" /> | ||
93 | <Field Name="totalFileBytes" Hidden="true" /> | ||
94 | <Field Name="totalFiles" Hidden="true" /> | ||
95 | </Members> | ||
96 | <TypeIdentifier> | ||
97 | <HashCode>AAMCAQASACAAABBBAAASUAAAQBAAAMAAAAGQAAgBEAA=</HashCode> | ||
98 | <FileName>ArchiveProgressEventArgs.cs</FileName> | ||
99 | </TypeIdentifier> | ||
100 | </Class> | ||
101 | <Class Name="WixToolset.Dtf.Compression.BasicUnpackStreamContext"> | ||
102 | <Position X="12.75" Y="3" Width="2.25" /> | ||
103 | <Members> | ||
104 | <Field Name="archiveStream" Hidden="true" /> | ||
105 | <Method Name="BasicUnpackStreamContext" Hidden="true" /> | ||
106 | <Method Name="CloseArchiveReadStream" Hidden="true" /> | ||
107 | <Method Name="CloseFileWriteStream" Hidden="true" /> | ||
108 | <Field Name="fileStream" Hidden="true" /> | ||
109 | <Method Name="OpenArchiveReadStream" Hidden="true" /> | ||
110 | <Method Name="OpenFileWriteStream" Hidden="true" /> | ||
111 | </Members> | ||
112 | <TypeIdentifier> | ||
113 | <HashCode>AAAAAAgAAACEAAAAAAAAAAAAAAAgAAAAIAAMAAAAAAA=</HashCode> | ||
114 | <FileName>BasicUnpackStreamContext.cs</FileName> | ||
115 | </TypeIdentifier> | ||
116 | <Lollipop Position="0.2" /> | ||
117 | </Class> | ||
118 | <Class Name="WixToolset.Dtf.Compression.CompressionEngine"> | ||
119 | <Position X="8" Y="0.5" Width="2.25" /> | ||
120 | <Members> | ||
121 | <Method Name="~CompressionEngine" Hidden="true" /> | ||
122 | <Method Name="CompressionEngine" Hidden="true" /> | ||
123 | <Field Name="compressionLevel" Hidden="true" /> | ||
124 | <Field Name="dontUseTempFiles" Hidden="true" /> | ||
125 | </Members> | ||
126 | <TypeIdentifier> | ||
127 | <HashCode>AAAEAAAABCBAACRgAAAAAAQAAEAAAAAAQAEAAAiAAAI=</HashCode> | ||
128 | <FileName>CompressionEngine.cs</FileName> | ||
129 | </TypeIdentifier> | ||
130 | <Lollipop Position="0.2" /> | ||
131 | </Class> | ||
132 | <Class Name="WixToolset.Dtf.Compression.DuplicateStream" Collapsed="true"> | ||
133 | <Position X="10.5" Y="4.25" Width="2" /> | ||
134 | <TypeIdentifier> | ||
135 | <HashCode>AAAAAEAAAgAAQAIgGAAAIABgAAAAAAAAAAAAAAGIACA=</HashCode> | ||
136 | <FileName>DuplicateStream.cs</FileName> | ||
137 | </TypeIdentifier> | ||
138 | </Class> | ||
139 | <Class Name="WixToolset.Dtf.Compression.OffsetStream" Collapsed="true"> | ||
140 | <Position X="8" Y="4.25" Width="2" /> | ||
141 | <TypeIdentifier> | ||
142 | <HashCode>AAAAAAAAAgAAQAIgGAAAAABgAAAAAEAgAAAAAAGIwCA=</HashCode> | ||
143 | <FileName>OffsetStream.cs</FileName> | ||
144 | </TypeIdentifier> | ||
145 | </Class> | ||
146 | <Interface Name="WixToolset.Dtf.Compression.IPackStreamContext"> | ||
147 | <Position X="10.5" Y="0.5" Width="2" /> | ||
148 | <TypeIdentifier> | ||
149 | <HashCode>AAAAAAAAAAAAAACAAACAAAAQAAAgAAAAACAIAAAAAAA=</HashCode> | ||
150 | <FileName>IPackStreamContext.cs</FileName> | ||
151 | </TypeIdentifier> | ||
152 | </Interface> | ||
153 | <Interface Name="WixToolset.Dtf.Compression.IUnpackStreamContext"> | ||
154 | <Position X="10.5" Y="2.5" Width="2" /> | ||
155 | <TypeIdentifier> | ||
156 | <HashCode>AAAAAAgAAACAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAA=</HashCode> | ||
157 | <FileName>IUnpackStreamContext.cs</FileName> | ||
158 | </TypeIdentifier> | ||
159 | </Interface> | ||
160 | <Enum Name="WixToolset.Dtf.Compression.ArchiveProgressType" Collapsed="true"> | ||
161 | <Position X="5.25" Y="3.75" Width="2" /> | ||
162 | <TypeIdentifier> | ||
163 | <HashCode>QAAAAAAAAAAAAIAAgAAAAAAAAAQAAAAACIAAAAAAAAA=</HashCode> | ||
164 | <FileName>ArchiveProgressType.cs</FileName> | ||
165 | </TypeIdentifier> | ||
166 | </Enum> | ||
167 | <Enum Name="WixToolset.Dtf.Compression.CompressionLevel" Collapsed="true"> | ||
168 | <Position X="5.25" Y="4.5" Width="2" /> | ||
169 | <TypeIdentifier> | ||
170 | <HashCode>AAAAAAAAABAAAAAAEAAAAAAAAAAIAAAAAAAAAAEAAAA=</HashCode> | ||
171 | <FileName>CompressionLevel.cs</FileName> | ||
172 | </TypeIdentifier> | ||
173 | </Enum> | ||
174 | <Font Name="Verdana" Size="8" /> | ||
175 | </ClassDiagram> \ No newline at end of file | ||
diff --git a/src/dtf/WixToolset.Dtf.Compression/CompressionEngine.cs b/src/dtf/WixToolset.Dtf.Compression/CompressionEngine.cs new file mode 100644 index 00000000..7758ea98 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.Compression/CompressionEngine.cs | |||
@@ -0,0 +1,371 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Dtf.Compression | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Globalization; | ||
9 | |||
10 | /// <summary> | ||
11 | /// Base class for an engine capable of packing and unpacking a particular | ||
12 | /// compressed file format. | ||
13 | /// </summary> | ||
14 | public abstract class CompressionEngine : IDisposable | ||
15 | { | ||
16 | private CompressionLevel compressionLevel; | ||
17 | private bool dontUseTempFiles; | ||
18 | |||
19 | /// <summary> | ||
20 | /// Creates a new instance of the compression engine base class. | ||
21 | /// </summary> | ||
22 | protected CompressionEngine() | ||
23 | { | ||
24 | this.compressionLevel = CompressionLevel.Normal; | ||
25 | } | ||
26 | |||
27 | /// <summary> | ||
28 | /// Disposes the compression engine. | ||
29 | /// </summary> | ||
30 | ~CompressionEngine() | ||
31 | { | ||
32 | this.Dispose(false); | ||
33 | } | ||
34 | |||
35 | /// <summary> | ||
36 | /// Occurs when the compression engine reports progress in packing | ||
37 | /// or unpacking an archive. | ||
38 | /// </summary> | ||
39 | /// <seealso cref="ArchiveProgressType"/> | ||
40 | public event EventHandler<ArchiveProgressEventArgs> Progress; | ||
41 | |||
42 | /// <summary> | ||
43 | /// Gets or sets a flag indicating whether temporary files are created | ||
44 | /// and used during compression. | ||
45 | /// </summary> | ||
46 | /// <value>True if temporary files are used; false if compression is done | ||
47 | /// entirely in-memory.</value> | ||
48 | /// <remarks>The value of this property is true by default. Using temporary | ||
49 | /// files can greatly reduce the memory requirement of compression, | ||
50 | /// especially when compressing large archives. However, setting this property | ||
51 | /// to false may yield slightly better performance when creating small | ||
52 | /// archives. Or it may be necessary if the process does not have sufficient | ||
53 | /// privileges to create temporary files.</remarks> | ||
54 | public bool UseTempFiles | ||
55 | { | ||
56 | get | ||
57 | { | ||
58 | return !this.dontUseTempFiles; | ||
59 | } | ||
60 | |||
61 | set | ||
62 | { | ||
63 | this.dontUseTempFiles = !value; | ||
64 | } | ||
65 | } | ||
66 | |||
67 | /// <summary> | ||
68 | /// Compression level to use when compressing files. | ||
69 | /// </summary> | ||
70 | /// <value>A compression level ranging from minimum to maximum compression, | ||
71 | /// or no compression.</value> | ||
72 | public CompressionLevel CompressionLevel | ||
73 | { | ||
74 | get | ||
75 | { | ||
76 | return this.compressionLevel; | ||
77 | } | ||
78 | |||
79 | set | ||
80 | { | ||
81 | this.compressionLevel = value; | ||
82 | } | ||
83 | } | ||
84 | |||
85 | /// <summary> | ||
86 | /// Disposes of resources allocated by the compression engine. | ||
87 | /// </summary> | ||
88 | public void Dispose() | ||
89 | { | ||
90 | this.Dispose(true); | ||
91 | GC.SuppressFinalize(this); | ||
92 | } | ||
93 | |||
94 | /// <summary> | ||
95 | /// Creates an archive. | ||
96 | /// </summary> | ||
97 | /// <param name="streamContext">A context interface to handle opening | ||
98 | /// and closing of archive and file streams.</param> | ||
99 | /// <param name="files">The paths of the files in the archive | ||
100 | /// (not external file paths).</param> | ||
101 | /// <exception cref="ArchiveException">The archive could not be | ||
102 | /// created.</exception> | ||
103 | /// <remarks> | ||
104 | /// The stream context implementation may provide a mapping from the | ||
105 | /// file paths within the archive to the external file paths. | ||
106 | /// </remarks> | ||
107 | public void Pack(IPackStreamContext streamContext, IEnumerable<string> files) | ||
108 | { | ||
109 | if (files == null) | ||
110 | { | ||
111 | throw new ArgumentNullException("files"); | ||
112 | } | ||
113 | |||
114 | this.Pack(streamContext, files, 0); | ||
115 | } | ||
116 | |||
117 | /// <summary> | ||
118 | /// Creates an archive or chain of archives. | ||
119 | /// </summary> | ||
120 | /// <param name="streamContext">A context interface to handle opening | ||
121 | /// and closing of archive and file streams.</param> | ||
122 | /// <param name="files">The paths of the files in the archive (not | ||
123 | /// external file paths).</param> | ||
124 | /// <param name="maxArchiveSize">The maximum number of bytes for one | ||
125 | /// archive before the contents are chained to the next archive, or zero | ||
126 | /// for unlimited archive size.</param> | ||
127 | /// <exception cref="ArchiveException">The archive could not be | ||
128 | /// created.</exception> | ||
129 | /// <remarks> | ||
130 | /// The stream context implementation may provide a mapping from the file | ||
131 | /// paths within the archive to the external file paths. | ||
132 | /// </remarks> | ||
133 | public abstract void Pack( | ||
134 | IPackStreamContext streamContext, | ||
135 | IEnumerable<string> files, | ||
136 | long maxArchiveSize); | ||
137 | |||
138 | /// <summary> | ||
139 | /// Checks whether a Stream begins with a header that indicates | ||
140 | /// it is a valid archive. | ||
141 | /// </summary> | ||
142 | /// <param name="stream">Stream for reading the archive file.</param> | ||
143 | /// <returns>True if the stream is a valid archive | ||
144 | /// (with no offset); false otherwise.</returns> | ||
145 | public abstract bool IsArchive(Stream stream); | ||
146 | |||
147 | /// <summary> | ||
148 | /// Gets the offset of an archive that is positioned 0 or more bytes | ||
149 | /// from the start of the Stream. | ||
150 | /// </summary> | ||
151 | /// <param name="stream">A stream for reading the archive.</param> | ||
152 | /// <returns>The offset in bytes of the archive, | ||
153 | /// or -1 if no archive is found in the Stream.</returns> | ||
154 | /// <remarks>The archive must begin on a 4-byte boundary.</remarks> | ||
155 | public virtual long FindArchiveOffset(Stream stream) | ||
156 | { | ||
157 | if (stream == null) | ||
158 | { | ||
159 | throw new ArgumentNullException("stream"); | ||
160 | } | ||
161 | |||
162 | long sectionSize = 4; | ||
163 | long length = stream.Length; | ||
164 | for (long offset = 0; offset <= length - sectionSize; offset += sectionSize) | ||
165 | { | ||
166 | stream.Seek(offset, SeekOrigin.Begin); | ||
167 | if (this.IsArchive(stream)) | ||
168 | { | ||
169 | return offset; | ||
170 | } | ||
171 | } | ||
172 | |||
173 | return -1; | ||
174 | } | ||
175 | |||
176 | /// <summary> | ||
177 | /// Gets information about all files in an archive stream. | ||
178 | /// </summary> | ||
179 | /// <param name="stream">A stream for reading the archive.</param> | ||
180 | /// <returns>Information about all files in the archive stream.</returns> | ||
181 | /// <exception cref="ArchiveException">The stream is not a valid | ||
182 | /// archive.</exception> | ||
183 | public IList<ArchiveFileInfo> GetFileInfo(Stream stream) | ||
184 | { | ||
185 | return this.GetFileInfo(new BasicUnpackStreamContext(stream), null); | ||
186 | } | ||
187 | |||
188 | /// <summary> | ||
189 | /// Gets information about files in an archive or archive chain. | ||
190 | /// </summary> | ||
191 | /// <param name="streamContext">A context interface to handle opening | ||
192 | /// and closing of archive and file streams.</param> | ||
193 | /// <param name="fileFilter">A predicate that can determine | ||
194 | /// which files to process, optional.</param> | ||
195 | /// <returns>Information about files in the archive stream.</returns> | ||
196 | /// <exception cref="ArchiveException">The archive provided | ||
197 | /// by the stream context is not valid.</exception> | ||
198 | /// <remarks> | ||
199 | /// The <paramref name="fileFilter"/> predicate takes an internal file | ||
200 | /// path and returns true to include the file or false to exclude it. | ||
201 | /// </remarks> | ||
202 | public abstract IList<ArchiveFileInfo> GetFileInfo( | ||
203 | IUnpackStreamContext streamContext, | ||
204 | Predicate<string> fileFilter); | ||
205 | |||
206 | /// <summary> | ||
207 | /// Gets the list of files in an archive Stream. | ||
208 | /// </summary> | ||
209 | /// <param name="stream">A stream for reading the archive.</param> | ||
210 | /// <returns>A list of the paths of all files contained in the | ||
211 | /// archive.</returns> | ||
212 | /// <exception cref="ArchiveException">The stream is not a valid | ||
213 | /// archive.</exception> | ||
214 | public IList<string> GetFiles(Stream stream) | ||
215 | { | ||
216 | return this.GetFiles(new BasicUnpackStreamContext(stream), null); | ||
217 | } | ||
218 | |||
219 | /// <summary> | ||
220 | /// Gets the list of files in an archive or archive chain. | ||
221 | /// </summary> | ||
222 | /// <param name="streamContext">A context interface to handle opening | ||
223 | /// and closing of archive and file streams.</param> | ||
224 | /// <param name="fileFilter">A predicate that can determine | ||
225 | /// which files to process, optional.</param> | ||
226 | /// <returns>An array containing the names of all files contained in | ||
227 | /// the archive or archive chain.</returns> | ||
228 | /// <exception cref="ArchiveException">The archive provided | ||
229 | /// by the stream context is not valid.</exception> | ||
230 | /// <remarks> | ||
231 | /// The <paramref name="fileFilter"/> predicate takes an internal file | ||
232 | /// path and returns true to include the file or false to exclude it. | ||
233 | /// </remarks> | ||
234 | public IList<string> GetFiles( | ||
235 | IUnpackStreamContext streamContext, | ||
236 | Predicate<string> fileFilter) | ||
237 | { | ||
238 | if (streamContext == null) | ||
239 | { | ||
240 | throw new ArgumentNullException("streamContext"); | ||
241 | } | ||
242 | |||
243 | IList<ArchiveFileInfo> files = | ||
244 | this.GetFileInfo(streamContext, fileFilter); | ||
245 | IList<string> fileNames = new List<string>(files.Count); | ||
246 | for (int i = 0; i < files.Count; i++) | ||
247 | { | ||
248 | fileNames.Add(files[i].Name); | ||
249 | } | ||
250 | |||
251 | return fileNames; | ||
252 | } | ||
253 | |||
254 | /// <summary> | ||
255 | /// Reads a single file from an archive stream. | ||
256 | /// </summary> | ||
257 | /// <param name="stream">A stream for reading the archive.</param> | ||
258 | /// <param name="path">The path of the file within the archive | ||
259 | /// (not the external file path).</param> | ||
260 | /// <returns>A stream for reading the extracted file, or null | ||
261 | /// if the file does not exist in the archive.</returns> | ||
262 | /// <exception cref="ArchiveException">The stream is not a valid | ||
263 | /// archive.</exception> | ||
264 | /// <remarks>The entire extracted file is cached in memory, so this | ||
265 | /// method requires enough free memory to hold the file.</remarks> | ||
266 | public Stream Unpack(Stream stream, string path) | ||
267 | { | ||
268 | if (stream == null) | ||
269 | { | ||
270 | throw new ArgumentNullException("stream"); | ||
271 | } | ||
272 | |||
273 | if (path == null) | ||
274 | { | ||
275 | throw new ArgumentNullException("path"); | ||
276 | } | ||
277 | |||
278 | BasicUnpackStreamContext streamContext = | ||
279 | new BasicUnpackStreamContext(stream); | ||
280 | this.Unpack( | ||
281 | streamContext, | ||
282 | delegate(string match) | ||
283 | { | ||
284 | return String.Compare( | ||
285 | match, path, true, CultureInfo.InvariantCulture) == 0; | ||
286 | }); | ||
287 | |||
288 | Stream extractStream = streamContext.FileStream; | ||
289 | if (extractStream != null) | ||
290 | { | ||
291 | extractStream.Position = 0; | ||
292 | } | ||
293 | |||
294 | return extractStream; | ||
295 | } | ||
296 | |||
297 | /// <summary> | ||
298 | /// Extracts files from an archive or archive chain. | ||
299 | /// </summary> | ||
300 | /// <param name="streamContext">A context interface to handle opening | ||
301 | /// and closing of archive and file streams.</param> | ||
302 | /// <param name="fileFilter">An optional predicate that can determine | ||
303 | /// which files to process.</param> | ||
304 | /// <exception cref="ArchiveException">The archive provided | ||
305 | /// by the stream context is not valid.</exception> | ||
306 | /// <remarks> | ||
307 | /// The <paramref name="fileFilter"/> predicate takes an internal file | ||
308 | /// path and returns true to include the file or false to exclude it. | ||
309 | /// </remarks> | ||
310 | public abstract void Unpack( | ||
311 | IUnpackStreamContext streamContext, | ||
312 | Predicate<string> fileFilter); | ||
313 | |||
314 | /// <summary> | ||
315 | /// Called by sublcasses to distribute a packing or unpacking progress | ||
316 | /// event to listeners. | ||
317 | /// </summary> | ||
318 | /// <param name="e">Event details.</param> | ||
319 | protected void OnProgress(ArchiveProgressEventArgs e) | ||
320 | { | ||
321 | if (this.Progress != null) | ||
322 | { | ||
323 | this.Progress(this, e); | ||
324 | } | ||
325 | } | ||
326 | |||
327 | /// <summary> | ||
328 | /// Disposes of resources allocated by the compression engine. | ||
329 | /// </summary> | ||
330 | /// <param name="disposing">If true, the method has been called | ||
331 | /// directly or indirectly by a user's code, so managed and unmanaged | ||
332 | /// resources will be disposed. If false, the method has been called by | ||
333 | /// the runtime from inside the finalizer, and only unmanaged resources | ||
334 | /// will be disposed.</param> | ||
335 | protected virtual void Dispose(bool disposing) | ||
336 | { | ||
337 | } | ||
338 | |||
339 | /// <summary> | ||
340 | /// Compresion utility function for converting old-style | ||
341 | /// date and time values to a DateTime structure. | ||
342 | /// </summary> | ||
343 | public static void DosDateAndTimeToDateTime( | ||
344 | short dosDate, short dosTime, out DateTime dateTime) | ||
345 | { | ||
346 | if (dosDate == 0 && dosTime == 0) | ||
347 | { | ||
348 | dateTime = DateTime.MinValue; | ||
349 | } | ||
350 | else | ||
351 | { | ||
352 | long fileTime; | ||
353 | SafeNativeMethods.DosDateTimeToFileTime(dosDate, dosTime, out fileTime); | ||
354 | dateTime = DateTime.FromFileTimeUtc(fileTime); | ||
355 | dateTime = new DateTime(dateTime.Ticks, DateTimeKind.Local); | ||
356 | } | ||
357 | } | ||
358 | |||
359 | /// <summary> | ||
360 | /// Compresion utility function for converting a DateTime structure | ||
361 | /// to old-style date and time values. | ||
362 | /// </summary> | ||
363 | public static void DateTimeToDosDateAndTime( | ||
364 | DateTime dateTime, out short dosDate, out short dosTime) | ||
365 | { | ||
366 | dateTime = new DateTime(dateTime.Ticks, DateTimeKind.Utc); | ||
367 | long filetime = dateTime.ToFileTimeUtc(); | ||
368 | SafeNativeMethods.FileTimeToDosDateTime(ref filetime, out dosDate, out dosTime); | ||
369 | } | ||
370 | } | ||
371 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.Compression/CompressionLevel.cs b/src/dtf/WixToolset.Dtf.Compression/CompressionLevel.cs new file mode 100644 index 00000000..84ec8fc4 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.Compression/CompressionLevel.cs | |||
@@ -0,0 +1,31 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Dtf.Compression | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Text; | ||
8 | |||
9 | /// <summary> | ||
10 | /// Specifies the compression level ranging from minimum compresion to | ||
11 | /// maximum compression, or no compression at all. | ||
12 | /// </summary> | ||
13 | /// <remarks> | ||
14 | /// Although only four values are enumerated, any integral value between | ||
15 | /// <see cref="CompressionLevel.Min"/> and <see cref="CompressionLevel.Max"/> can also be used. | ||
16 | /// </remarks> | ||
17 | public enum CompressionLevel | ||
18 | { | ||
19 | /// <summary>Do not compress files, only store.</summary> | ||
20 | None = 0, | ||
21 | |||
22 | /// <summary>Minimum compression; fastest.</summary> | ||
23 | Min = 1, | ||
24 | |||
25 | /// <summary>A compromize between speed and compression efficiency.</summary> | ||
26 | Normal = 6, | ||
27 | |||
28 | /// <summary>Maximum compression; slowest.</summary> | ||
29 | Max = 10 | ||
30 | } | ||
31 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.Compression/DuplicateStream.cs b/src/dtf/WixToolset.Dtf.Compression/DuplicateStream.cs new file mode 100644 index 00000000..50e62e73 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.Compression/DuplicateStream.cs | |||
@@ -0,0 +1,212 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Dtf.Compression | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | |||
8 | /// <summary> | ||
9 | /// Duplicates a source stream by maintaining a separate position. | ||
10 | /// </summary> | ||
11 | /// <remarks> | ||
12 | /// WARNING: duplicate streams are not thread-safe with respect to each other or the original stream. | ||
13 | /// If multiple threads use duplicate copies of the same stream, they must synchronize for any operations. | ||
14 | /// </remarks> | ||
15 | public class DuplicateStream : Stream | ||
16 | { | ||
17 | private Stream source; | ||
18 | private long position; | ||
19 | |||
20 | /// <summary> | ||
21 | /// Creates a new duplicate of a stream. | ||
22 | /// </summary> | ||
23 | /// <param name="source">source of the duplicate</param> | ||
24 | public DuplicateStream(Stream source) | ||
25 | { | ||
26 | if (source == null) | ||
27 | { | ||
28 | throw new ArgumentNullException("source"); | ||
29 | } | ||
30 | |||
31 | this.source = DuplicateStream.OriginalStream(source); | ||
32 | } | ||
33 | |||
34 | /// <summary> | ||
35 | /// Gets the original stream that was used to create the duplicate. | ||
36 | /// </summary> | ||
37 | public Stream Source | ||
38 | { | ||
39 | get | ||
40 | { | ||
41 | return this.source; | ||
42 | } | ||
43 | } | ||
44 | |||
45 | /// <summary> | ||
46 | /// Gets a value indicating whether the source stream supports reading. | ||
47 | /// </summary> | ||
48 | /// <value>true if the stream supports reading; otherwise, false.</value> | ||
49 | public override bool CanRead | ||
50 | { | ||
51 | get | ||
52 | { | ||
53 | return this.source.CanRead; | ||
54 | } | ||
55 | } | ||
56 | |||
57 | /// <summary> | ||
58 | /// Gets a value indicating whether the source stream supports writing. | ||
59 | /// </summary> | ||
60 | /// <value>true if the stream supports writing; otherwise, false.</value> | ||
61 | public override bool CanWrite | ||
62 | { | ||
63 | get | ||
64 | { | ||
65 | return this.source.CanWrite; | ||
66 | } | ||
67 | } | ||
68 | |||
69 | /// <summary> | ||
70 | /// Gets a value indicating whether the source stream supports seeking. | ||
71 | /// </summary> | ||
72 | /// <value>true if the stream supports seeking; otherwise, false.</value> | ||
73 | public override bool CanSeek | ||
74 | { | ||
75 | get | ||
76 | { | ||
77 | return this.source.CanSeek; | ||
78 | } | ||
79 | } | ||
80 | |||
81 | /// <summary> | ||
82 | /// Gets the length of the source stream. | ||
83 | /// </summary> | ||
84 | public override long Length | ||
85 | { | ||
86 | get | ||
87 | { | ||
88 | return this.source.Length; | ||
89 | } | ||
90 | } | ||
91 | |||
92 | /// <summary> | ||
93 | /// Gets or sets the position of the current stream, | ||
94 | /// ignoring the position of the source stream. | ||
95 | /// </summary> | ||
96 | public override long Position | ||
97 | { | ||
98 | get | ||
99 | { | ||
100 | return this.position; | ||
101 | } | ||
102 | |||
103 | set | ||
104 | { | ||
105 | this.position = value; | ||
106 | } | ||
107 | } | ||
108 | |||
109 | /// <summary> | ||
110 | /// Retrieves the original stream from a possible duplicate stream. | ||
111 | /// </summary> | ||
112 | /// <param name="stream">Possible duplicate stream.</param> | ||
113 | /// <returns>If the stream is a DuplicateStream, returns | ||
114 | /// the duplicate's source; otherwise returns the same stream.</returns> | ||
115 | public static Stream OriginalStream(Stream stream) | ||
116 | { | ||
117 | DuplicateStream dupStream = stream as DuplicateStream; | ||
118 | return dupStream != null ? dupStream.Source : stream; | ||
119 | } | ||
120 | |||
121 | /// <summary> | ||
122 | /// Flushes the source stream. | ||
123 | /// </summary> | ||
124 | public override void Flush() | ||
125 | { | ||
126 | this.source.Flush(); | ||
127 | } | ||
128 | |||
129 | /// <summary> | ||
130 | /// Sets the length of the source stream. | ||
131 | /// </summary> | ||
132 | /// <param name="value">The desired length of the stream in bytes.</param> | ||
133 | public override void SetLength(long value) | ||
134 | { | ||
135 | this.source.SetLength(value); | ||
136 | } | ||
137 | |||
138 | /// <summary> | ||
139 | /// Closes the underlying stream, effectively closing ALL duplicates. | ||
140 | /// </summary> | ||
141 | public override void Close() | ||
142 | { | ||
143 | this.source.Close(); | ||
144 | } | ||
145 | |||
146 | /// <summary> | ||
147 | /// Reads from the source stream while maintaining a separate position | ||
148 | /// and not impacting the source stream's position. | ||
149 | /// </summary> | ||
150 | /// <param name="buffer">An array of bytes. When this method returns, the buffer | ||
151 | /// contains the specified byte array with the values between offset and | ||
152 | /// (offset + count - 1) replaced by the bytes read from the current source.</param> | ||
153 | /// <param name="offset">The zero-based byte offset in buffer at which to begin | ||
154 | /// storing the data read from the current stream.</param> | ||
155 | /// <param name="count">The maximum number of bytes to be read from the current stream.</param> | ||
156 | /// <returns>The total number of bytes read into the buffer. This can be less | ||
157 | /// than the number of bytes requested if that many bytes are not currently available, | ||
158 | /// or zero (0) if the end of the stream has been reached.</returns> | ||
159 | public override int Read(byte[] buffer, int offset, int count) | ||
160 | { | ||
161 | long saveSourcePosition = this.source.Position; | ||
162 | this.source.Position = this.position; | ||
163 | int read = this.source.Read(buffer, offset, count); | ||
164 | this.position = this.source.Position; | ||
165 | this.source.Position = saveSourcePosition; | ||
166 | return read; | ||
167 | } | ||
168 | |||
169 | /// <summary> | ||
170 | /// Writes to the source stream while maintaining a separate position | ||
171 | /// and not impacting the source stream's position. | ||
172 | /// </summary> | ||
173 | /// <param name="buffer">An array of bytes. This method copies count | ||
174 | /// bytes from buffer to the current stream.</param> | ||
175 | /// <param name="offset">The zero-based byte offset in buffer at which | ||
176 | /// to begin copying bytes to the current stream.</param> | ||
177 | /// <param name="count">The number of bytes to be written to the | ||
178 | /// current stream.</param> | ||
179 | public override void Write(byte[] buffer, int offset, int count) | ||
180 | { | ||
181 | long saveSourcePosition = this.source.Position; | ||
182 | this.source.Position = this.position; | ||
183 | this.source.Write(buffer, offset, count); | ||
184 | this.position = this.source.Position; | ||
185 | this.source.Position = saveSourcePosition; | ||
186 | } | ||
187 | |||
188 | /// <summary> | ||
189 | /// Changes the position of this stream without impacting the | ||
190 | /// source stream's position. | ||
191 | /// </summary> | ||
192 | /// <param name="offset">A byte offset relative to the origin parameter.</param> | ||
193 | /// <param name="origin">A value of type SeekOrigin indicating the reference | ||
194 | /// point used to obtain the new position.</param> | ||
195 | /// <returns>The new position within the current stream.</returns> | ||
196 | public override long Seek(long offset, SeekOrigin origin) | ||
197 | { | ||
198 | long originPosition = 0; | ||
199 | if (origin == SeekOrigin.Current) | ||
200 | { | ||
201 | originPosition = this.position; | ||
202 | } | ||
203 | else if (origin == SeekOrigin.End) | ||
204 | { | ||
205 | originPosition = this.Length; | ||
206 | } | ||
207 | |||
208 | this.position = originPosition + offset; | ||
209 | return this.position; | ||
210 | } | ||
211 | } | ||
212 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.Compression/IPackStreamContext.cs b/src/dtf/WixToolset.Dtf.Compression/IPackStreamContext.cs new file mode 100644 index 00000000..19d77be5 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.Compression/IPackStreamContext.cs | |||
@@ -0,0 +1,117 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Dtf.Compression | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | using System.Diagnostics.CodeAnalysis; | ||
8 | |||
9 | /// <summary> | ||
10 | /// This interface provides the methods necessary for the | ||
11 | /// <see cref="CompressionEngine"/> to open and close streams for archives | ||
12 | /// and files. The implementor of this interface can use any kind of logic | ||
13 | /// to determine what kind of streams to open and where. | ||
14 | /// </summary> | ||
15 | public interface IPackStreamContext | ||
16 | { | ||
17 | /// <summary> | ||
18 | /// Gets the name of the archive with a specified number. | ||
19 | /// </summary> | ||
20 | /// <param name="archiveNumber">The 0-based index of the archive | ||
21 | /// within the chain.</param> | ||
22 | /// <returns>The name of the requested archive. May be an empty string | ||
23 | /// for non-chained archives, but may never be null.</returns> | ||
24 | /// <remarks>The archive name is the name stored within the archive, used for | ||
25 | /// identification of the archive especially among archive chains. That | ||
26 | /// name is often, but not necessarily the same as the filename of the | ||
27 | /// archive package.</remarks> | ||
28 | string GetArchiveName(int archiveNumber); | ||
29 | |||
30 | /// <summary> | ||
31 | /// Opens a stream for writing an archive package. | ||
32 | /// </summary> | ||
33 | /// <param name="archiveNumber">The 0-based index of the archive within | ||
34 | /// the chain.</param> | ||
35 | /// <param name="archiveName">The name of the archive that was returned | ||
36 | /// by <see cref="GetArchiveName"/>.</param> | ||
37 | /// <param name="truncate">True if the stream should be truncated when | ||
38 | /// opened (if it already exists); false if an existing stream is being | ||
39 | /// re-opened for writing additional data.</param> | ||
40 | /// <param name="compressionEngine">Instance of the compression engine | ||
41 | /// doing the operations.</param> | ||
42 | /// <returns>A writable Stream where the compressed archive bytes will be | ||
43 | /// written, or null to cancel the archive creation.</returns> | ||
44 | /// <remarks> | ||
45 | /// If this method returns null, the archive engine will throw a | ||
46 | /// FileNotFoundException. | ||
47 | /// </remarks> | ||
48 | Stream OpenArchiveWriteStream( | ||
49 | int archiveNumber, | ||
50 | string archiveName, | ||
51 | bool truncate, | ||
52 | CompressionEngine compressionEngine); | ||
53 | |||
54 | /// <summary> | ||
55 | /// Closes a stream where an archive package was written. | ||
56 | /// </summary> | ||
57 | /// <param name="archiveNumber">The 0-based index of the archive within | ||
58 | /// the chain.</param> | ||
59 | /// <param name="archiveName">The name of the archive that was previously | ||
60 | /// returned by | ||
61 | /// <see cref="GetArchiveName"/>.</param> | ||
62 | /// <param name="stream">A stream that was previously returned by | ||
63 | /// <see cref="OpenArchiveWriteStream"/> and is now ready to be closed.</param> | ||
64 | /// <remarks> | ||
65 | /// If there is another archive package in the chain, then after this stream | ||
66 | /// is closed a new stream will be opened. | ||
67 | /// </remarks> | ||
68 | void CloseArchiveWriteStream(int archiveNumber, string archiveName, Stream stream); | ||
69 | |||
70 | /// <summary> | ||
71 | /// Opens a stream to read a file that is to be included in an archive. | ||
72 | /// </summary> | ||
73 | /// <param name="path">The path of the file within the archive. This is often, | ||
74 | /// but not necessarily, the same as the relative path of the file outside | ||
75 | /// the archive.</param> | ||
76 | /// <param name="attributes">Returned attributes of the opened file, to be | ||
77 | /// stored in the archive.</param> | ||
78 | /// <param name="lastWriteTime">Returned last-modified time of the opened file, | ||
79 | /// to be stored in the archive.</param> | ||
80 | /// <returns>A readable Stream where the file bytes will be read from before | ||
81 | /// they are compressed, or null to skip inclusion of the file and continue to | ||
82 | /// the next file.</returns> | ||
83 | [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters")] | ||
84 | Stream OpenFileReadStream( | ||
85 | string path, | ||
86 | out FileAttributes attributes, | ||
87 | out DateTime lastWriteTime); | ||
88 | |||
89 | /// <summary> | ||
90 | /// Closes a stream that has been used to read a file. | ||
91 | /// </summary> | ||
92 | /// <param name="path">The path of the file within the archive; the same as | ||
93 | /// the path provided | ||
94 | /// when the stream was opened.</param> | ||
95 | /// <param name="stream">A stream that was previously returned by | ||
96 | /// <see cref="OpenFileReadStream"/> and is now ready to be closed.</param> | ||
97 | void CloseFileReadStream(string path, Stream stream); | ||
98 | |||
99 | /// <summary> | ||
100 | /// Gets extended parameter information specific to the compression | ||
101 | /// format being used. | ||
102 | /// </summary> | ||
103 | /// <param name="optionName">Name of the option being requested.</param> | ||
104 | /// <param name="parameters">Parameters for the option; for per-file options, | ||
105 | /// the first parameter is typically the internal file path.</param> | ||
106 | /// <returns>Option value, or null to use the default behavior.</returns> | ||
107 | /// <remarks> | ||
108 | /// This method provides a way to set uncommon options during packaging, or a | ||
109 | /// way to handle aspects of compression formats not supported by the base library. | ||
110 | /// <para>For example, this may be used by the zip compression library to | ||
111 | /// specify different compression methods/levels on a per-file basis.</para> | ||
112 | /// <para>The available option names, parameters, and expected return values | ||
113 | /// should be documented by each compression library.</para> | ||
114 | /// </remarks> | ||
115 | object GetOption(string optionName, object[] parameters); | ||
116 | } | ||
117 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.Compression/IUnpackStreamContext.cs b/src/dtf/WixToolset.Dtf.Compression/IUnpackStreamContext.cs new file mode 100644 index 00000000..f0bc6aad --- /dev/null +++ b/src/dtf/WixToolset.Dtf.Compression/IUnpackStreamContext.cs | |||
@@ -0,0 +1,71 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Dtf.Compression | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | |||
8 | /// <summary> | ||
9 | /// This interface provides the methods necessary for the <see cref="CompressionEngine"/> to open | ||
10 | /// and close streams for archives and files. The implementor of this interface can use any | ||
11 | /// kind of logic to determine what kind of streams to open and where | ||
12 | /// </summary> | ||
13 | public interface IUnpackStreamContext | ||
14 | { | ||
15 | /// <summary> | ||
16 | /// Opens the archive stream for reading. | ||
17 | /// </summary> | ||
18 | /// <param name="archiveNumber">The zero-based index of the archive to open.</param> | ||
19 | /// <param name="archiveName">The name of the archive being opened.</param> | ||
20 | /// <param name="compressionEngine">Instance of the compression engine doing the operations.</param> | ||
21 | /// <returns>A stream from which archive bytes are read, or null to cancel extraction | ||
22 | /// of the archive.</returns> | ||
23 | /// <remarks> | ||
24 | /// When the first archive in a chain is opened, the name is not yet known, so the | ||
25 | /// provided value will be an empty string. When opening further archives, the | ||
26 | /// provided value is the next-archive name stored in the previous archive. This | ||
27 | /// name is often, but not necessarily, the same as the filename of the archive | ||
28 | /// package to be opened. | ||
29 | /// <para>If this method returns null, the archive engine will throw a | ||
30 | /// FileNotFoundException.</para> | ||
31 | /// </remarks> | ||
32 | Stream OpenArchiveReadStream(int archiveNumber, string archiveName, CompressionEngine compressionEngine); | ||
33 | |||
34 | /// <summary> | ||
35 | /// Closes a stream where an archive package was read. | ||
36 | /// </summary> | ||
37 | /// <param name="archiveNumber">The archive number of the stream to close.</param> | ||
38 | /// <param name="archiveName">The name of the archive being closed.</param> | ||
39 | /// <param name="stream">The stream that was previously returned by | ||
40 | /// <see cref="OpenArchiveReadStream"/> and is now ready to be closed.</param> | ||
41 | void CloseArchiveReadStream(int archiveNumber, string archiveName, Stream stream); | ||
42 | |||
43 | /// <summary> | ||
44 | /// Opens a stream for writing extracted file bytes. | ||
45 | /// </summary> | ||
46 | /// <param name="path">The path of the file within the archive. This is often, but | ||
47 | /// not necessarily, the same as the relative path of the file outside the archive.</param> | ||
48 | /// <param name="fileSize">The uncompressed size of the file to be extracted.</param> | ||
49 | /// <param name="lastWriteTime">The last write time of the file to be extracted.</param> | ||
50 | /// <returns>A stream where extracted file bytes are to be written, or null to skip | ||
51 | /// extraction of the file and continue to the next file.</returns> | ||
52 | /// <remarks> | ||
53 | /// The implementor may use the path, size and date information to dynamically | ||
54 | /// decide whether or not the file should be extracted. | ||
55 | /// </remarks> | ||
56 | Stream OpenFileWriteStream(string path, long fileSize, DateTime lastWriteTime); | ||
57 | |||
58 | /// <summary> | ||
59 | /// Closes a stream where an extracted file was written. | ||
60 | /// </summary> | ||
61 | /// <param name="path">The path of the file within the archive.</param> | ||
62 | /// <param name="stream">The stream that was previously returned by <see cref="OpenFileWriteStream"/> | ||
63 | /// and is now ready to be closed.</param> | ||
64 | /// <param name="attributes">The attributes of the extracted file.</param> | ||
65 | /// <param name="lastWriteTime">The last write time of the file.</param> | ||
66 | /// <remarks> | ||
67 | /// The implementor may wish to apply the attributes and date to the newly-extracted file. | ||
68 | /// </remarks> | ||
69 | void CloseFileWriteStream(string path, Stream stream, FileAttributes attributes, DateTime lastWriteTime); | ||
70 | } | ||
71 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.Compression/OffsetStream.cs b/src/dtf/WixToolset.Dtf.Compression/OffsetStream.cs new file mode 100644 index 00000000..65562524 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.Compression/OffsetStream.cs | |||
@@ -0,0 +1,206 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Dtf.Compression | ||
4 | { | ||
5 | using System; | ||
6 | using System.IO; | ||
7 | |||
8 | /// <summary> | ||
9 | /// Wraps a source stream and offsets all read/write/seek calls by a given value. | ||
10 | /// </summary> | ||
11 | /// <remarks> | ||
12 | /// This class is used to trick archive an packing or unpacking process | ||
13 | /// into reading or writing at an offset into a file, primarily for | ||
14 | /// self-extracting packages. | ||
15 | /// </remarks> | ||
16 | public class OffsetStream : Stream | ||
17 | { | ||
18 | private Stream source; | ||
19 | private long sourceOffset; | ||
20 | |||
21 | /// <summary> | ||
22 | /// Creates a new OffsetStream instance from a source stream | ||
23 | /// and using a specified offset. | ||
24 | /// </summary> | ||
25 | /// <param name="source">Underlying stream for which all calls will be offset.</param> | ||
26 | /// <param name="offset">Positive or negative number of bytes to offset.</param> | ||
27 | public OffsetStream(Stream source, long offset) | ||
28 | { | ||
29 | if (source == null) | ||
30 | { | ||
31 | throw new ArgumentNullException("source"); | ||
32 | } | ||
33 | |||
34 | this.source = source; | ||
35 | this.sourceOffset = offset; | ||
36 | |||
37 | this.source.Seek(this.sourceOffset, SeekOrigin.Current); | ||
38 | } | ||
39 | |||
40 | /// <summary> | ||
41 | /// Gets the underlying stream that this OffsetStream calls into. | ||
42 | /// </summary> | ||
43 | public Stream Source | ||
44 | { | ||
45 | get { return this.source; } | ||
46 | } | ||
47 | |||
48 | /// <summary> | ||
49 | /// Gets the number of bytes to offset all calls before | ||
50 | /// redirecting to the underlying stream. | ||
51 | /// </summary> | ||
52 | public long Offset | ||
53 | { | ||
54 | get { return this.sourceOffset; } | ||
55 | } | ||
56 | |||
57 | /// <summary> | ||
58 | /// Gets a value indicating whether the source stream supports reading. | ||
59 | /// </summary> | ||
60 | /// <value>true if the stream supports reading; otherwise, false.</value> | ||
61 | public override bool CanRead | ||
62 | { | ||
63 | get | ||
64 | { | ||
65 | return this.source.CanRead; | ||
66 | } | ||
67 | } | ||
68 | |||
69 | /// <summary> | ||
70 | /// Gets a value indicating whether the source stream supports writing. | ||
71 | /// </summary> | ||
72 | /// <value>true if the stream supports writing; otherwise, false.</value> | ||
73 | public override bool CanWrite | ||
74 | { | ||
75 | get | ||
76 | { | ||
77 | return this.source.CanWrite; | ||
78 | } | ||
79 | } | ||
80 | |||
81 | /// <summary> | ||
82 | /// Gets a value indicating whether the source stream supports seeking. | ||
83 | /// </summary> | ||
84 | /// <value>true if the stream supports seeking; otherwise, false.</value> | ||
85 | public override bool CanSeek | ||
86 | { | ||
87 | get | ||
88 | { | ||
89 | return this.source.CanSeek; | ||
90 | } | ||
91 | } | ||
92 | |||
93 | /// <summary> | ||
94 | /// Gets the effective length of the stream, which is equal to | ||
95 | /// the length of the source stream minus the offset. | ||
96 | /// </summary> | ||
97 | public override long Length | ||
98 | { | ||
99 | get { return this.source.Length - this.sourceOffset; } | ||
100 | } | ||
101 | |||
102 | /// <summary> | ||
103 | /// Gets or sets the effective position of the stream, which | ||
104 | /// is equal to the position of the source stream minus the offset. | ||
105 | /// </summary> | ||
106 | public override long Position | ||
107 | { | ||
108 | get { return this.source.Position - this.sourceOffset; } | ||
109 | set { this.source.Position = value + this.sourceOffset; } | ||
110 | } | ||
111 | |||
112 | /// <summary> | ||
113 | /// Reads a sequence of bytes from the source stream and advances | ||
114 | /// the position within the stream by the number of bytes read. | ||
115 | /// </summary> | ||
116 | /// <param name="buffer">An array of bytes. When this method returns, the buffer | ||
117 | /// contains the specified byte array with the values between offset and | ||
118 | /// (offset + count - 1) replaced by the bytes read from the current source.</param> | ||
119 | /// <param name="offset">The zero-based byte offset in buffer at which to begin | ||
120 | /// storing the data read from the current stream.</param> | ||
121 | /// <param name="count">The maximum number of bytes to be read from the current stream.</param> | ||
122 | /// <returns>The total number of bytes read into the buffer. This can be less | ||
123 | /// than the number of bytes requested if that many bytes are not currently available, | ||
124 | /// or zero (0) if the end of the stream has been reached.</returns> | ||
125 | public override int Read(byte[] buffer, int offset, int count) | ||
126 | { | ||
127 | return this.source.Read(buffer, offset, count); | ||
128 | } | ||
129 | |||
130 | /// <summary> | ||
131 | /// Writes a sequence of bytes to the source stream and advances the | ||
132 | /// current position within this stream by the number of bytes written. | ||
133 | /// </summary> | ||
134 | /// <param name="buffer">An array of bytes. This method copies count | ||
135 | /// bytes from buffer to the current stream.</param> | ||
136 | /// <param name="offset">The zero-based byte offset in buffer at which | ||
137 | /// to begin copying bytes to the current stream.</param> | ||
138 | /// <param name="count">The number of bytes to be written to the | ||
139 | /// current stream.</param> | ||
140 | public override void Write(byte[] buffer, int offset, int count) | ||
141 | { | ||
142 | this.source.Write(buffer, offset, count); | ||
143 | } | ||
144 | |||
145 | /// <summary> | ||
146 | /// Reads a byte from the stream and advances the position within the | ||
147 | /// source stream by one byte, or returns -1 if at the end of the stream. | ||
148 | /// </summary> | ||
149 | /// <returns>The unsigned byte cast to an Int32, or -1 if at the | ||
150 | /// end of the stream.</returns> | ||
151 | public override int ReadByte() | ||
152 | { | ||
153 | return this.source.ReadByte(); | ||
154 | } | ||
155 | |||
156 | /// <summary> | ||
157 | /// Writes a byte to the current position in the source stream and | ||
158 | /// advances the position within the stream by one byte. | ||
159 | /// </summary> | ||
160 | /// <param name="value">The byte to write to the stream.</param> | ||
161 | public override void WriteByte(byte value) | ||
162 | { | ||
163 | this.source.WriteByte(value); | ||
164 | } | ||
165 | |||
166 | /// <summary> | ||
167 | /// Flushes the source stream. | ||
168 | /// </summary> | ||
169 | public override void Flush() | ||
170 | { | ||
171 | this.source.Flush(); | ||
172 | } | ||
173 | |||
174 | /// <summary> | ||
175 | /// Sets the position within the current stream, which is | ||
176 | /// equal to the position within the source stream minus the offset. | ||
177 | /// </summary> | ||
178 | /// <param name="offset">A byte offset relative to the origin parameter.</param> | ||
179 | /// <param name="origin">A value of type SeekOrigin indicating | ||
180 | /// the reference point used to obtain the new position.</param> | ||
181 | /// <returns>The new position within the current stream.</returns> | ||
182 | public override long Seek(long offset, SeekOrigin origin) | ||
183 | { | ||
184 | return this.source.Seek(offset + (origin == SeekOrigin.Begin ? this.sourceOffset : 0), origin) - this.sourceOffset; | ||
185 | } | ||
186 | |||
187 | /// <summary> | ||
188 | /// Sets the effective length of the stream, which is equal to | ||
189 | /// the length of the source stream minus the offset. | ||
190 | /// </summary> | ||
191 | /// <param name="value">The desired length of the | ||
192 | /// current stream in bytes.</param> | ||
193 | public override void SetLength(long value) | ||
194 | { | ||
195 | this.source.SetLength(value + this.sourceOffset); | ||
196 | } | ||
197 | |||
198 | /// <summary> | ||
199 | /// Closes the underlying stream. | ||
200 | /// </summary> | ||
201 | public override void Close() | ||
202 | { | ||
203 | this.source.Close(); | ||
204 | } | ||
205 | } | ||
206 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.Compression/SafeNativeMethods.cs b/src/dtf/WixToolset.Dtf.Compression/SafeNativeMethods.cs new file mode 100644 index 00000000..1829ba81 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.Compression/SafeNativeMethods.cs | |||
@@ -0,0 +1,22 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Dtf.Compression | ||
4 | { | ||
5 | using System; | ||
6 | using System.Security; | ||
7 | using System.Runtime.InteropServices; | ||
8 | |||
9 | [SuppressUnmanagedCodeSecurity] | ||
10 | internal static class SafeNativeMethods | ||
11 | { | ||
12 | [DllImport("kernel32.dll", SetLastError = true)] | ||
13 | [return: MarshalAs(UnmanagedType.Bool)] | ||
14 | internal static extern bool DosDateTimeToFileTime( | ||
15 | short wFatDate, short wFatTime, out long fileTime); | ||
16 | |||
17 | [DllImport("kernel32.dll", SetLastError = true)] | ||
18 | [return: MarshalAs(UnmanagedType.Bool)] | ||
19 | internal static extern bool FileTimeToDosDateTime( | ||
20 | ref long fileTime, out short wFatDate, out short wFatTime); | ||
21 | } | ||
22 | } | ||
diff --git a/src/dtf/WixToolset.Dtf.Compression/WixToolset.Dtf.Compression.csproj b/src/dtf/WixToolset.Dtf.Compression/WixToolset.Dtf.Compression.csproj new file mode 100644 index 00000000..e49a446b --- /dev/null +++ b/src/dtf/WixToolset.Dtf.Compression/WixToolset.Dtf.Compression.csproj | |||
@@ -0,0 +1,21 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
3 | |||
4 | <Project Sdk="Microsoft.NET.Sdk"> | ||
5 | <PropertyGroup> | ||
6 | <RootNamespace>WixToolset.Dtf.Compression</RootNamespace> | ||
7 | <AssemblyName>WixToolset.Dtf.Compression</AssemblyName> | ||
8 | <TargetFrameworks>netstandard2.0;net20</TargetFrameworks> | ||
9 | <Description>Abstract base libraries for archive packing and unpacking</Description> | ||
10 | <CreateDocumentationFile>true</CreateDocumentationFile> | ||
11 | </PropertyGroup> | ||
12 | |||
13 | <ItemGroup> | ||
14 | <None Include="Compression.cd" /> | ||
15 | </ItemGroup> | ||
16 | |||
17 | <ItemGroup> | ||
18 | <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" /> | ||
19 | <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" /> | ||
20 | </ItemGroup> | ||
21 | </Project> | ||