Statistics
| Branch: | Revision:

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
	*/