root / trunk / hammock / src / net35 / ICSharpCode.SharpZipLib.Silverlight / Tar / TarOutputStream.cs @ 0eea575a
History | View | Annotate | Download (17.3 kB)
1 |
// TarOutputStream.cs |
---|---|
2 |
// |
3 |
// Copyright (C) 2001 Mike Krueger |
4 |
// Copyright 2005 John Reilly |
5 |
// |
6 |
// This program is free software; you can redistribute it and/or |
7 |
// modify it under the terms of the GNU General Public License |
8 |
// as published by the Free Software Foundation; either version 2 |
9 |
// of the License, or (at your option) any later version. |
10 |
// |
11 |
// This program is distributed in the hope that it will be useful, |
12 |
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 |
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 |
// GNU General Public License for more details. |
15 |
// |
16 |
// You should have received a copy of the GNU General Public License |
17 |
// along with this program; if not, write to the Free Software |
18 |
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
19 |
// |
20 |
// Linking this library statically or dynamically with other modules is |
21 |
// making a combined work based on this library. Thus, the terms and |
22 |
// conditions of the GNU General Public License cover the whole |
23 |
// combination. |
24 |
// |
25 |
// As a special exception, the copyright holders of this library give you |
26 |
// permission to link this library with independent modules to produce an |
27 |
// executable, regardless of the license terms of these independent |
28 |
// modules, and to copy and distribute the resulting executable under |
29 |
// terms of your choice, provided that you also meet, for each linked |
30 |
// independent module, the terms and conditions of the license of that |
31 |
// module. An independent module is a module which is not derived from |
32 |
// or based on this library. If you modify this library, you may extend |
33 |
// this exception to your version of the library, but you are not |
34 |
// obligated to do so. If you do not wish to do so, delete this |
35 |
// exception statement from your version. |
36 |
|
37 |
using System; |
38 |
using System.IO; |
39 |
|
40 |
namespace ICSharpCode.SharpZipLib.Silverlight.Tar |
41 |
{ |
42 |
/// <summary> |
43 |
/// The TarOutputStream writes a UNIX tar archive as an OutputStream. |
44 |
/// Methods are provided to put entries, and then write their contents |
45 |
/// by writing to this stream using write(). |
46 |
/// </summary> |
47 |
/// public |
48 |
public class TarOutputStream : Stream |
49 |
{ |
50 |
#region Constructors |
51 |
|
52 |
/// <summary> |
53 |
/// Construct TarOutputStream using default block factor |
54 |
/// </summary> |
55 |
/// <param name="outputStream">stream to write to</param> |
56 |
public TarOutputStream(Stream outputStream) |
57 |
: this(outputStream, TarBuffer.DefaultBlockFactor) |
58 |
{ |
59 |
} |
60 |
|
61 |
/// <summary> |
62 |
/// Construct TarOutputStream with user specified block factor |
63 |
/// </summary> |
64 |
/// <param name="outputStream">stream to write to</param> |
65 |
/// <param name="blockFactor">blocking factor</param> |
66 |
public TarOutputStream(Stream outputStream, int blockFactor) |
67 |
{ |
68 |
if (outputStream == null) |
69 |
{ |
70 |
throw new ArgumentNullException("outputStream"); |
71 |
} |
72 |
|
73 |
this.outputStream = outputStream; |
74 |
_buffer = TarBuffer.CreateOutputTarBuffer(outputStream, blockFactor); |
75 |
|
76 |
assemblyBuffer = new byte[TarBuffer.BlockSize]; |
77 |
blockBuffer = new byte[TarBuffer.BlockSize]; |
78 |
} |
79 |
|
80 |
#endregion |
81 |
|
82 |
/// <summary> |
83 |
/// true if the stream supports reading; otherwise, false. |
84 |
/// </summary> |
85 |
public override bool CanRead |
86 |
{ |
87 |
get { return outputStream.CanRead; } |
88 |
} |
89 |
|
90 |
/// <summary> |
91 |
/// true if the stream supports seeking; otherwise, false. |
92 |
/// </summary> |
93 |
public override bool CanSeek |
94 |
{ |
95 |
get { return outputStream.CanSeek; } |
96 |
} |
97 |
|
98 |
/// <summary> |
99 |
/// true if stream supports writing; otherwise, false. |
100 |
/// </summary> |
101 |
public override bool CanWrite |
102 |
{ |
103 |
get { return outputStream.CanWrite; } |
104 |
} |
105 |
|
106 |
/// <summary> |
107 |
/// length of stream in bytes |
108 |
/// </summary> |
109 |
public override long Length |
110 |
{ |
111 |
get { return outputStream.Length; } |
112 |
} |
113 |
|
114 |
/// <summary> |
115 |
/// gets or sets the position within the current stream. |
116 |
/// </summary> |
117 |
public override long Position |
118 |
{ |
119 |
get { return outputStream.Position; } |
120 |
set { outputStream.Position = value; } |
121 |
} |
122 |
|
123 |
/// <summary> |
124 |
/// Get the record size being used by this stream's TarBuffer. |
125 |
/// </summary> |
126 |
public int RecordSize |
127 |
{ |
128 |
get { return _buffer.RecordSize; } |
129 |
} |
130 |
|
131 |
/// <summary> |
132 |
/// Get a value indicating wether an entry is open, requiring more data to be written. |
133 |
/// </summary> |
134 |
private bool IsEntryOpen |
135 |
{ |
136 |
get { return (currBytes < currSize); } |
137 |
} |
138 |
|
139 |
/// <summary> |
140 |
/// set the position within the current stream |
141 |
/// </summary> |
142 |
/// <param name="offset">The offset relative to the <paramref name="origin"/> to seek to</param> |
143 |
/// <param name="origin">The <see cref="SeekOrigin"/> to seek from.</param> |
144 |
/// <returns>The new position in the stream.</returns> |
145 |
public override long Seek(long offset, SeekOrigin origin) |
146 |
{ |
147 |
return outputStream.Seek(offset, origin); |
148 |
} |
149 |
|
150 |
/// <summary> |
151 |
/// Set the length of the current stream |
152 |
/// </summary> |
153 |
/// <param name="value">The new stream length.</param> |
154 |
public override void SetLength(long value) |
155 |
{ |
156 |
outputStream.SetLength(value); |
157 |
} |
158 |
|
159 |
/// <summary> |
160 |
/// Read a byte from the stream and advance the position within the stream |
161 |
/// by one byte or returns -1 if at the end of the stream. |
162 |
/// </summary> |
163 |
/// <returns>The byte value or -1 if at end of stream</returns> |
164 |
public override int ReadByte() |
165 |
{ |
166 |
return outputStream.ReadByte(); |
167 |
} |
168 |
|
169 |
/// <summary> |
170 |
/// read bytes from the current stream and advance the position within the |
171 |
/// stream by the number of bytes read. |
172 |
/// </summary> |
173 |
/// <param name="buffer">The buffer to store read bytes in.</param> |
174 |
/// <param name="offset">The index into the buffer to being storing bytes at.</param> |
175 |
/// <param name="count">The desired number of bytes to read.</param> |
176 |
/// <returns>The total number of bytes read, or zero if at the end of the stream. |
177 |
/// The number of bytes may be less than the <paramref name="count">count</paramref> |
178 |
/// requested if data is not avialable.</returns> |
179 |
public override int Read(byte[] buffer, int offset, int count) |
180 |
{ |
181 |
return outputStream.Read(buffer, offset, count); |
182 |
} |
183 |
|
184 |
/// <summary> |
185 |
/// All buffered data is written to destination |
186 |
/// </summary> |
187 |
public override void Flush() |
188 |
{ |
189 |
outputStream.Flush(); |
190 |
} |
191 |
|
192 |
/// <summary> |
193 |
/// Ends the TAR archive without closing the underlying OutputStream. |
194 |
/// The result is that the EOF block of nulls is written. |
195 |
/// </summary> |
196 |
public void Finish() |
197 |
{ |
198 |
if (IsEntryOpen) |
199 |
{ |
200 |
CloseEntry(); |
201 |
} |
202 |
WriteEofBlock(); |
203 |
} |
204 |
|
205 |
/// <summary> |
206 |
/// Ends the TAR archive and closes the underlying OutputStream. |
207 |
/// </summary> |
208 |
/// <remarks>This means that Finish() is called followed by calling the |
209 |
/// TarBuffer's Close().</remarks> |
210 |
public override void Close() |
211 |
{ |
212 |
if (!isClosed) |
213 |
{ |
214 |
isClosed = true; |
215 |
Finish(); |
216 |
_buffer.Close(); |
217 |
} |
218 |
} |
219 |
|
220 |
/// <summary> |
221 |
/// Get the record size being used by this stream's TarBuffer. |
222 |
/// </summary> |
223 |
/// <returns> |
224 |
/// The TarBuffer record size. |
225 |
/// </returns> |
226 |
[Obsolete("Use RecordSize property instead")] |
227 |
public int GetRecordSize() |
228 |
{ |
229 |
return _buffer.RecordSize; |
230 |
} |
231 |
|
232 |
/// <summary> |
233 |
/// Put an entry on the output stream. This writes the entry's |
234 |
/// header and positions the output stream for writing |
235 |
/// the contents of the entry. Once this method is called, the |
236 |
/// stream is ready for calls to write() to write the entry's |
237 |
/// contents. Once the contents are written, closeEntry() |
238 |
/// <B>MUST</B> be called to ensure that all buffered data |
239 |
/// is completely written to the output stream. |
240 |
/// </summary> |
241 |
/// <param name="entry"> |
242 |
/// The TarEntry to be written to the archive. |
243 |
/// </param> |
244 |
public void PutNextEntry(TarEntry entry) |
245 |
{ |
246 |
if (entry == null) |
247 |
{ |
248 |
throw new ArgumentNullException("entry"); |
249 |
} |
250 |
|
251 |
if (entry.TarHeader.Name.Length >= TarHeader.NAMELEN) |
252 |
{ |
253 |
var longHeader = new TarHeader{TypeFlag = TarHeader.LF_GNU_LONGNAME}; |
254 |
longHeader.Name = longHeader.Name + "././@LongLink"; |
255 |
longHeader.UserId = 0; |
256 |
longHeader.GroupId = 0; |
257 |
longHeader.GroupName = String.Empty; |
258 |
longHeader.UserName = String.Empty; |
259 |
longHeader.LinkName = String.Empty; |
260 |
|
261 |
longHeader.Size = entry.TarHeader.Name.Length; |
262 |
|
263 |
longHeader.WriteHeader(blockBuffer); |
264 |
_buffer.WriteBlock(blockBuffer); // Add special long filename header block |
265 |
|
266 |
var nameCharIndex = 0; |
267 |
|
268 |
while (nameCharIndex < entry.TarHeader.Name.Length) |
269 |
{ |
270 |
Array.Clear(blockBuffer, 0, blockBuffer.Length); |
271 |
TarHeader.GetAsciiBytes(entry.TarHeader.Name, nameCharIndex, blockBuffer, 0, TarBuffer.BlockSize); |
272 |
nameCharIndex += TarBuffer.BlockSize; |
273 |
_buffer.WriteBlock(blockBuffer); |
274 |
} |
275 |
} |
276 |
|
277 |
entry.WriteEntryHeader(blockBuffer); |
278 |
_buffer.WriteBlock(blockBuffer); |
279 |
|
280 |
currBytes = 0; |
281 |
|
282 |
currSize = entry.IsDirectory ? 0 : entry.Size; |
283 |
} |
284 |
|
285 |
/// <summary> |
286 |
/// Close an entry. This method MUST be called for all file |
287 |
/// entries that contain data. The reason is that we must |
288 |
/// buffer data written to the stream in order to satisfy |
289 |
/// the buffer's block based writes. Thus, there may be |
290 |
/// data fragments still being assembled that must be written |
291 |
/// to the output stream before this entry is closed and the |
292 |
/// next entry written. |
293 |
/// </summary> |
294 |
public void CloseEntry() |
295 |
{ |
296 |
if (assemblyBufferLength > 0) |
297 |
{ |
298 |
Array.Clear(assemblyBuffer, assemblyBufferLength, assemblyBuffer.Length - assemblyBufferLength); |
299 |
|
300 |
_buffer.WriteBlock(assemblyBuffer); |
301 |
|
302 |
currBytes += assemblyBufferLength; |
303 |
assemblyBufferLength = 0; |
304 |
} |
305 |
|
306 |
if (currBytes < currSize) |
307 |
{ |
308 |
var errorText = string.Format( |
309 |
"Entry closed at '{0}' before the '{1}' bytes specified in the header were written", |
310 |
currBytes, currSize); |
311 |
throw new TarException(errorText); |
312 |
} |
313 |
} |
314 |
|
315 |
/// <summary> |
316 |
/// Writes a byte to the current tar archive entry. |
317 |
/// This method simply calls Write(byte[], int, int). |
318 |
/// </summary> |
319 |
/// <param name="value"> |
320 |
/// The byte to be written. |
321 |
/// </param> |
322 |
public override void WriteByte(byte value) |
323 |
{ |
324 |
Write(new[]{value}, 0, 1); |
325 |
} |
326 |
|
327 |
/// <summary> |
328 |
/// Writes bytes to the current tar archive entry. This method |
329 |
/// is aware of the current entry and will throw an exception if |
330 |
/// you attempt to write bytes past the length specified for the |
331 |
/// current entry. The method is also (painfully) aware of the |
332 |
/// record buffering required by TarBuffer, and manages buffers |
333 |
/// that are not a multiple of recordsize in length, including |
334 |
/// assembling records from small buffers. |
335 |
/// </summary> |
336 |
/// <param name = "buffer"> |
337 |
/// The buffer to write to the archive. |
338 |
/// </param> |
339 |
/// <param name = "offset"> |
340 |
/// The offset in the buffer from which to get bytes. |
341 |
/// </param> |
342 |
/// <param name = "count"> |
343 |
/// The number of bytes to write. |
344 |
/// </param> |
345 |
public override void Write(byte[] buffer, int offset, int count) |
346 |
{ |
347 |
if (buffer == null) |
348 |
{ |
349 |
throw new ArgumentNullException("buffer"); |
350 |
} |
351 |
|
352 |
if (offset < 0) |
353 |
{ |
354 |
throw new ArgumentOutOfRangeException("offset", "Cannot be negative"); |
355 |
} |
356 |
|
357 |
if (buffer.Length - offset < count) |
358 |
{ |
359 |
throw new ArgumentException("offset and count combination is invalid"); |
360 |
} |
361 |
|
362 |
if (count < 0) |
363 |
{ |
364 |
throw new ArgumentOutOfRangeException("count", "Cannot be negative"); |
365 |
} |
366 |
|
367 |
if ((currBytes + count) > currSize) |
368 |
{ |
369 |
var errorText = string.Format("request to write '{0}' bytes exceeds size in header of '{1}' bytes", |
370 |
count, currSize); |
371 |
throw new ArgumentOutOfRangeException("count", errorText); |
372 |
} |
373 |
|
374 |
// |
375 |
// We have to deal with assembly!!! |
376 |
// The programmer can be writing little 32 byte chunks for all |
377 |
// we know, and we must assemble complete blocks for writing. |
378 |
// TODO REVIEW Maybe this should be in TarBuffer? Could that help to |
379 |
// eliminate some of the buffer copying. |
380 |
// |
381 |
if (assemblyBufferLength > 0) |
382 |
{ |
383 |
if ((assemblyBufferLength + count) >= blockBuffer.Length) |
384 |
{ |
385 |
var aLen = blockBuffer.Length - assemblyBufferLength; |
386 |
|
387 |
Array.Copy(assemblyBuffer, 0, blockBuffer, 0, assemblyBufferLength); |
388 |
Array.Copy(buffer, offset, blockBuffer, assemblyBufferLength, aLen); |
389 |
|
390 |
_buffer.WriteBlock(blockBuffer); |
391 |
|
392 |
currBytes += blockBuffer.Length; |
393 |
|
394 |
offset += aLen; |
395 |
count -= aLen; |
396 |
|
397 |
assemblyBufferLength = 0; |
398 |
} |
399 |
else |
400 |
{ |
401 |
Array.Copy(buffer, offset, assemblyBuffer, assemblyBufferLength, count); |
402 |
offset += count; |
403 |
assemblyBufferLength += count; |
404 |
count -= count; |
405 |
} |
406 |
} |
407 |
|
408 |
// |
409 |
// When we get here we have EITHER: |
410 |
// o An empty "assembly" buffer. |
411 |
// o No bytes to write (count == 0) |
412 |
// |
413 |
while (count > 0) |
414 |
{ |
415 |
if (count < blockBuffer.Length) |
416 |
{ |
417 |
Array.Copy(buffer, offset, assemblyBuffer, assemblyBufferLength, count); |
418 |
assemblyBufferLength += count; |
419 |
break; |
420 |
} |
421 |
|
422 |
_buffer.WriteBlock(buffer, offset); |
423 |
|
424 |
var bufferLength = blockBuffer.Length; |
425 |
currBytes += bufferLength; |
426 |
count -= bufferLength; |
427 |
offset += bufferLength; |
428 |
} |
429 |
} |
430 |
|
431 |
/// <summary> |
432 |
/// Write an EOF (end of archive) block to the tar archive. |
433 |
/// An EOF block consists of all zeros. |
434 |
/// </summary> |
435 |
private void WriteEofBlock() |
436 |
{ |
437 |
Array.Clear(blockBuffer, 0, blockBuffer.Length); |
438 |
_buffer.WriteBlock(blockBuffer); |
439 |
} |
440 |
|
441 |
#region Instance Fields |
442 |
|
443 |
/// <summary> |
444 |
/// 'Assembly' buffer used to assemble data before writing |
445 |
/// </summary> |
446 |
protected byte[] assemblyBuffer; |
447 |
|
448 |
/// <summary> |
449 |
/// current 'Assembly' buffer length |
450 |
/// </summary> |
451 |
private int assemblyBufferLength; |
452 |
|
453 |
/// <summary> |
454 |
/// single block working buffer |
455 |
/// </summary> |
456 |
protected byte[] blockBuffer; |
457 |
|
458 |
/// <summary> |
459 |
/// TarBuffer used to provide correct blocking factor |
460 |
/// </summary> |
461 |
protected TarBuffer _buffer; |
462 |
|
463 |
/// <summary> |
464 |
/// bytes written for this entry so far |
465 |
/// </summary> |
466 |
private long currBytes; |
467 |
|
468 |
/// <summary> |
469 |
/// Size for the current entry |
470 |
/// </summary> |
471 |
protected long currSize; |
472 |
|
473 |
/// <summary> |
474 |
/// Flag indicating wether this instance has been closed or not. |
475 |
/// </summary> |
476 |
private bool isClosed; |
477 |
|
478 |
/// <summary> |
479 |
/// the destination stream for the archive contents |
480 |
/// </summary> |
481 |
protected Stream outputStream; |
482 |
|
483 |
#endregion |
484 |
} |
485 |
} |
486 |
|
487 |
/* The original Java file had this header: |
488 |
** Authored by Timothy Gerard Endres |
489 |
** <mailto:time@gjt.org> <http://www.trustice.com> |
490 |
** |
491 |
** This work has been placed into the public domain. |
492 |
** You may use this work in any way and for any purpose you wish. |
493 |
** |
494 |
** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND, |
495 |
** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR |
496 |
** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY |
497 |
** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR |
498 |
** REDISTRIBUTION OF THIS SOFTWARE. |
499 |
** |
500 |
*/ |