Added hammock project to debug streaming issues
[pithos-ms-client] / trunk / hammock / src / net35 / ICSharpCode.SharpZipLib.Silverlight / Tar / TarArchive.cs
1 // TarArchive.cs
2 //
3 // Copyright (C) 2001 Mike Krueger
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 //
19 // Linking this library statically or dynamically with other modules is
20 // making a combined work based on this library.  Thus, the terms and
21 // conditions of the GNU General Public License cover the whole
22 // combination.
23 //
24 // As a special exception, the copyright holders of this library give you
25 // permission to link this library with independent modules to produce an
26 // executable, regardless of the license terms of these independent
27 // modules, and to copy and distribute the resulting executable under
28 // terms of your choice, provided that you also meet, for each linked
29 // independent module, the terms and conditions of the license of that
30 // module.  An independent module is a module which is not derived from
31 // or based on this library.  If you modify this library, you may extend
32 // this exception to your version of the library, but you are not
33 // obligated to do so.  If you do not wish to do so, delete this
34 // exception statement from your version.
35
36 using System;
37 using System.IO;
38 using System.Text;
39
40 namespace ICSharpCode.SharpZipLib.Silverlight.Tar
41 {
42     /// <summary>
43     /// Used to advise clients of 'events' while processing archives
44     /// </summary>
45     public delegate void ProgressMessageHandler(TarArchive archive, TarEntry entry, string message);
46
47     /// <summary>
48     /// The TarArchive class implements the concept of a
49     /// 'Tape Archive'. A tar archive is a series of entries, each of
50     /// which represents a file system object. Each entry in
51     /// the archive consists of a header block followed by 0 or more data blocks.
52     /// Directory entries consist only of the header block, and are followed by entries
53     /// for the directory's contents. File entries consist of a
54     /// header followed by the number of blocks needed to
55     /// contain the file's contents. All entries are written on
56     /// block boundaries. Blocks are 512 bytes long.
57     /// 
58     /// TarArchives are instantiated in either read or write mode,
59     /// based upon whether they are instantiated with an InputStream
60     /// or an OutputStream. Once instantiated TarArchives read/write
61     /// mode can not be changed.
62     /// 
63     /// There is currently no support for random access to tar archives.
64     /// However, it seems that subclassing TarArchive, and using the
65     /// TarBuffer.CurrentRecord and TarBuffer.CurrentBlock
66     /// properties, this would be rather trivial.
67     /// </summary>
68     public class TarArchive : IDisposable
69     {
70         /// <summary>
71         /// Get/set the ascii file translation flag. If ascii file translation
72         /// is true, then the file is checked to see if it a binary file or not. 
73         /// If the flag is true and the test indicates it is ascii text 
74         /// file, it will be translated. The translation converts the local
75         /// operating system's concept of line ends into the UNIX line end,
76         /// '\n', which is the defacto standard for a TAR archive. This makes
77         /// text files compatible with UNIX.
78         /// </summary>
79         public bool AsciiTranslate
80         {
81             get
82             {
83                 if (isDisposed)
84                 {
85                     throw new ObjectDisposedException("TarArchive");
86                 }
87
88                 return _asciiTranslate;
89             }
90
91             set
92             {
93                 if (isDisposed)
94                 {
95                     throw new ObjectDisposedException("TarArchive");
96                 }
97
98                 _asciiTranslate = value;
99             }
100         }
101
102         /// <summary>
103         /// PathPrefix is added to entry names as they are written if the value is not null.
104         /// A slash character is appended after PathPrefix 
105         /// </summary>
106         public string PathPrefix
107         {
108             get
109             {
110                 if (isDisposed)
111                 {
112                     throw new ObjectDisposedException("TarArchive");
113                 }
114
115                 return pathPrefix;
116             }
117
118             set
119             {
120                 if (isDisposed)
121                 {
122                     throw new ObjectDisposedException("TarArchive");
123                 }
124
125                 pathPrefix = value;
126             }
127         }
128
129         /// <summary>
130         /// RootPath is removed from entry names if it is found at the
131         /// beginning of the name.
132         /// </summary>
133         public string RootPath
134         {
135             get
136             {
137                 if (isDisposed)
138                 {
139                     throw new ObjectDisposedException("TarArchive");
140                 }
141
142                 return rootPath;
143             }
144
145             set
146             {
147                 if (isDisposed)
148                 {
149                     throw new ObjectDisposedException("TarArchive");
150                 }
151
152                 rootPath = value;
153             }
154         }
155
156         /// <summary>
157         /// Get or set a value indicating if overrides defined by <see cref="SetUserInfo">SetUserInfo</see> should be applied.
158         /// </summary>
159         /// <remarks>If overrides are not applied then the values as set in each header will be used.</remarks>
160         public bool ApplyUserInfoOverrides
161         {
162             get
163             {
164                 if (isDisposed)
165                 {
166                     throw new ObjectDisposedException("TarArchive");
167                 }
168
169                 return _applyUserInfoOverrides;
170             }
171
172             set
173             {
174                 if (isDisposed)
175                 {
176                     throw new ObjectDisposedException("TarArchive");
177                 }
178
179                 _applyUserInfoOverrides = value;
180             }
181         }
182
183         /// <summary>
184         /// Get the archive user id.
185         /// See <see cref="ApplyUserInfoOverrides">ApplyUserInfoOverrides</see> for detail
186         /// on how to allow setting values on a per entry basis.
187         /// </summary>
188         /// <returns>
189         /// The current user id.
190         /// </returns>
191         public int UserId
192         {
193             get
194             {
195                 if (isDisposed)
196                 {
197                     throw new ObjectDisposedException("TarArchive");
198                 }
199
200                 return _userId;
201             }
202         }
203
204         /// <summary>
205         /// Get the archive user name.
206         /// See <see cref="ApplyUserInfoOverrides">ApplyUserInfoOverrides</see> for detail
207         /// on how to allow setting values on a per entry basis.
208         /// </summary>
209         /// <returns>
210         /// The current user name.
211         /// </returns>
212         public string UserName
213         {
214             get
215             {
216                 if (isDisposed)
217                 {
218                     throw new ObjectDisposedException("TarArchive");
219                 }
220
221                 return _userName;
222             }
223         }
224
225         /// <summary>
226         /// Get the archive group id.
227         /// See <see cref="ApplyUserInfoOverrides">ApplyUserInfoOverrides</see> for detail
228         /// on how to allow setting values on a per entry basis.
229         /// </summary>
230         /// <returns>
231         /// The current group id.
232         /// </returns>
233         public int GroupId
234         {
235             get
236             {
237                 if (isDisposed)
238                 {
239                     throw new ObjectDisposedException("TarArchive");
240                 }
241
242                 return _groupId;
243             }
244         }
245
246         /// <summary>
247         /// Get the archive group name.
248         /// See <see cref="ApplyUserInfoOverrides">ApplyUserInfoOverrides</see> for detail
249         /// on how to allow setting values on a per entry basis.
250         /// </summary>
251         /// <returns>
252         /// The current group name.
253         /// </returns>
254         public string GroupName
255         {
256             get
257             {
258                 if (isDisposed)
259                 {
260                     throw new ObjectDisposedException("TarArchive");
261                 }
262
263                 return _groupName;
264             }
265         }
266
267         /// <summary>
268         /// Get the archive's record size. Tar archives are composed of
269         /// a series of RECORDS each containing a number of BLOCKS.
270         /// This allowed tar archives to match the IO characteristics of
271         /// the physical device being used. Archives are expected
272         /// to be properly "blocked".
273         /// </summary>
274         /// <returns>
275         /// The record size this archive is using.
276         /// </returns>
277         public int RecordSize
278         {
279             get
280             {
281                 if (isDisposed)
282                 {
283                     throw new ObjectDisposedException("TarArchive");
284                 }
285
286                 if (tarIn != null)
287                 {
288                     return tarIn.RecordSize;
289                 }
290                 if (tarOut != null)
291                 {
292                     return tarOut.RecordSize;
293                 }
294                 return TarBuffer.DefaultRecordSize;
295             }
296         }
297
298         #region IDisposable Members
299
300         void IDisposable.Dispose()
301         {
302             Close();
303         }
304
305         #endregion
306
307         /// <summary>
308         /// Client hook allowing detailed information to be reported during processing
309         /// </summary>
310         public event ProgressMessageHandler ProgressMessageEvent;
311
312         /// <summary>
313         /// Raises the ProgressMessage event
314         /// </summary>
315         /// <param name="entry">The <see cref="TarEntry">TarEntry</see> for this event</param>
316         /// <param name="message">message for this event.  Null is no message</param>
317         protected virtual void OnProgressMessageEvent(TarEntry entry, string message)
318         {
319             if (ProgressMessageEvent != null)
320             {
321                 ProgressMessageEvent(this, entry, message);
322             }
323         }
324
325         /// <summary>
326         /// Set the flag that determines whether existing files are
327         /// kept, or overwritten during extraction.
328         /// </summary>
329         /// <param name="keepOldFiles">
330         /// If true, do not overwrite existing files.
331         /// </param>
332         public void SetKeepOldFiles(bool keepOldFiles)
333         {
334             if (isDisposed)
335             {
336                 throw new ObjectDisposedException("TarArchive");
337             }
338             _keepOldFiles = keepOldFiles;
339         }
340
341         /// <summary>
342         /// Set the ascii file translation flag.
343         /// </summary>
344         /// <param name= "asciiTranslate">
345         /// If true, translate ascii text files.
346         /// </param>
347         [Obsolete("Use the AsciiTranslate property")]
348         public void SetAsciiTranslation(bool asciiTranslate)
349         {
350             if (isDisposed)
351             {
352                 throw new ObjectDisposedException("TarArchive");
353             }
354
355             _asciiTranslate = asciiTranslate;
356         }
357
358         /// <summary>
359         /// Set user and group information that will be used to fill in the
360         /// tar archive's entry headers. This information based on that available 
361         /// for the linux operating system, which is not always available on other
362         /// operating systems.  TarArchive allows the programmer to specify values
363         /// to be used in their place.
364         /// <see cref="ApplyUserInfoOverrides"/> is set to true by this call.
365         /// </summary>
366         /// <param name="userId">
367         /// The user id to use in the headers.
368         /// </param>
369         /// <param name="userName">
370         /// The user name to use in the headers.
371         /// </param>
372         /// <param name="groupId">
373         /// The group id to use in the headers.
374         /// </param>
375         /// <param name="groupName">
376         /// The group name to use in the headers.
377         /// </param>
378         public void SetUserInfo(int userId, string userName, int groupId, string groupName)
379         {
380             if (isDisposed)
381             {
382                 throw new ObjectDisposedException("TarArchive");
383             }
384
385             _userId = userId;
386             _userName = userName;
387             _groupId = groupId;
388             _groupName = groupName;
389             _applyUserInfoOverrides = true;
390         }
391
392         /// <summary>
393         /// Close the archive.
394         /// </summary>
395         [Obsolete("Use Close instead")]
396         public void CloseArchive()
397         {
398             Close();
399         }
400
401         /// <summary>
402         /// Perform the "list" command for the archive contents.
403         /// 
404         /// NOTE That this method uses the <see cref="ProgressMessageEvent"> progress event</see> to actually list
405         /// the contents. If the progress display event is not set, nothing will be listed!
406         /// </summary>
407         public void ListContents()
408         {
409             if (isDisposed)
410             {
411                 throw new ObjectDisposedException("TarArchive");
412             }
413
414             while (true)
415             {
416                 var entry = tarIn.GetNextEntry();
417
418                 if (entry == null)
419                 {
420                     break;
421                 }
422                 OnProgressMessageEvent(entry, null);
423             }
424         }
425
426         /// <summary>
427         /// Perform the "extract" command and extract the contents of the archive.
428         /// </summary>
429         /// <param name="destinationDirectory">
430         /// The destination directory into which to extract.
431         /// </param>
432         public void ExtractContents(string destinationDirectory)
433         {
434             if (isDisposed)
435             {
436                 throw new ObjectDisposedException("TarArchive");
437             }
438
439             while (true)
440             {
441                 var entry = tarIn.GetNextEntry();
442
443                 if (entry == null)
444                 {
445                     break;
446                 }
447
448                 ExtractEntry(destinationDirectory, entry);
449             }
450         }
451
452         /// <summary>
453         /// Extract an entry from the archive. This method assumes that the
454         /// tarIn stream has been properly set with a call to GetNextEntry().
455         /// </summary>
456         /// <param name="destDir">
457         /// The destination directory into which to extract.
458         /// </param>
459         /// <param name="entry">
460         /// The TarEntry returned by tarIn.GetNextEntry().
461         /// </param>
462         private void ExtractEntry(string destDir, TarEntry entry)
463         {
464             OnProgressMessageEvent(entry, null);
465
466             var name = entry.Name;
467
468             if (Path.IsPathRooted(name))
469             {
470                 // NOTE:
471                 // for UNC names...  \\machine\share\zoom\beet.txt gives \zoom\beet.txt
472                 name = name.Substring(Path.GetPathRoot(name).Length);
473             }
474
475             name = name.Replace('/', Path.DirectorySeparatorChar);
476
477             var destFile = Path.Combine(destDir, name);
478
479             if (entry.IsDirectory)
480             {
481                 EnsureDirectoryExists(destFile);
482             }
483             else
484             {
485                 var parentDirectory = Path.GetDirectoryName(destFile);
486                 EnsureDirectoryExists(parentDirectory);
487
488                 var process = true;
489                 var fileInfo = new FileInfo(destFile);
490                 if (fileInfo.Exists)
491                 {
492                     if (_keepOldFiles)
493                     {
494                         OnProgressMessageEvent(entry, "Destination file already exists");
495                         process = false;
496                     }
497                     else if ((fileInfo.Attributes & FileAttributes.ReadOnly) != 0)
498                     {
499                         OnProgressMessageEvent(entry, "Destination file already exists, and is read-only");
500                         process = false;
501                     }
502                 }
503
504                 if (process)
505                 {
506                     var asciiTrans = false;
507
508                     Stream outputStream = File.Create(destFile);
509                     if (_asciiTranslate)
510                     {
511                         asciiTrans = !IsBinary(destFile);
512                     }
513
514                     StreamWriter outw = null;
515                     if (asciiTrans)
516                     {
517                         outw = new StreamWriter(outputStream);
518                     }
519
520                     var rdbuf = new byte[32*1024];
521
522                     while (true)
523                     {
524                         var numRead = tarIn.Read(rdbuf, 0, rdbuf.Length);
525
526                         if (numRead <= 0)
527                         {
528                             break;
529                         }
530
531                         if (asciiTrans)
532                         {
533                             for (int off = 0, b = 0; b < numRead; ++b)
534                             {
535                                 if (rdbuf[b] == 10)
536                                 {
537                                     var s = Encoding.UTF8.GetString(rdbuf, off, (b - off));
538                                     outw.WriteLine(s);
539                                     off = b + 1;
540                                 }
541                             }
542                         }
543                         else
544                         {
545                             outputStream.Write(rdbuf, 0, numRead);
546                         }
547                     }
548
549                     if (asciiTrans)
550                     {
551                         outw.Close();
552                     }
553                     else
554                     {
555                         outputStream.Close();
556                     }
557                 }
558             }
559         }
560
561         /// <summary>
562         /// Write an entry to the archive. This method will call the putNextEntry
563         /// and then write the contents of the entry, and finally call closeEntry()
564         /// for entries that are files. For directories, it will call putNextEntry(),
565         /// and then, if the recurse flag is true, process each entry that is a
566         /// child of the directory.
567         /// </summary>
568         /// <param name="sourceEntry">
569         /// The TarEntry representing the entry to write to the archive.
570         /// </param>
571         /// <param name="recurse">
572         /// If true, process the children of directory entries.
573         /// </param>
574         public void WriteEntry(TarEntry sourceEntry, bool recurse)
575         {
576             if (sourceEntry == null)
577             {
578                 throw new ArgumentNullException("sourceEntry");
579             }
580
581             if (isDisposed)
582             {
583                 throw new ObjectDisposedException("TarArchive");
584             }
585
586             try
587             {
588                 if (recurse)
589                 {
590                     TarHeader.SetValueDefaults(sourceEntry.UserId, sourceEntry.UserName,
591                                                sourceEntry.GroupId, sourceEntry.GroupName);
592                 }
593                 InternalWriteEntry(sourceEntry, recurse);
594             }
595             finally
596             {
597                 if (recurse)
598                 {
599                     TarHeader.RestoreSetValues();
600                 }
601             }
602         }
603
604         /// <summary>
605         /// Write an entry to the archive. This method will call the putNextEntry
606         /// and then write the contents of the entry, and finally call closeEntry()
607         /// for entries that are files. For directories, it will call putNextEntry(),
608         /// and then, if the recurse flag is true, process each entry that is a
609         /// child of the directory.
610         /// </summary>
611         /// <param name="sourceEntry">
612         /// The TarEntry representing the entry to write to the archive.
613         /// </param>
614         /// <param name="recurse">
615         /// If true, process the children of directory entries.
616         /// </param>
617         private void InternalWriteEntry(TarEntry sourceEntry, bool recurse)
618         {
619             string tempFileName = null;
620             var entryFilename = sourceEntry.File;
621
622             var entry = (TarEntry) sourceEntry.Clone();
623
624             if (_applyUserInfoOverrides)
625             {
626                 entry.GroupId = _groupId;
627                 entry.GroupName = _groupName;
628                 entry.UserId = _userId;
629                 entry.UserName = _userName;
630             }
631
632             OnProgressMessageEvent(entry, null);
633
634             if (_asciiTranslate && !entry.IsDirectory)
635             {
636                 var asciiTrans = !IsBinary(entryFilename);
637
638                 if (asciiTrans)
639                 {
640                     tempFileName = Path.GetTempFileName();
641
642                     using (var inStream = File.OpenText(entryFilename))
643                     {
644                         using (Stream outStream = File.Create(tempFileName))
645                         {
646                             while (true)
647                             {
648                                 var line = inStream.ReadLine();
649                                 if (line == null)
650                                 {
651                                     break;
652                                 }
653                                 var data = Encoding.UTF8.GetBytes(line);
654                                 outStream.Write(data, 0, data.Length);
655                                 outStream.WriteByte((byte) '\n');
656                             }
657
658                             outStream.Flush();
659                         }
660                     }
661
662                     entry.Size = new FileInfo(tempFileName).Length;
663                     entryFilename = tempFileName;
664                 }
665             }
666
667             string newName = null;
668
669             if (rootPath != null)
670             {
671                 if (entry.Name.StartsWith(rootPath))
672                 {
673                     newName = entry.Name.Substring(rootPath.Length + 1);
674                 }
675             }
676
677             if (pathPrefix != null)
678             {
679                 newName = (newName == null) ? string.Format("{0}/{1}", pathPrefix, entry.Name) : pathPrefix + "/" + newName;
680             }
681
682             if (newName != null)
683             {
684                 entry.Name = newName;
685             }
686
687             tarOut.PutNextEntry(entry);
688
689             if (entry.IsDirectory)
690             {
691                 if (recurse)
692                 {
693                     var list = entry.GetDirectoryEntries();
694                     for (var i = 0; i < list.Length; ++i)
695                     {
696                         InternalWriteEntry(list[i], recurse);
697                     }
698                 }
699             }
700             else
701             {
702                 using (Stream inputStream = File.OpenRead(entryFilename))
703                 {
704                     var localBuffer = new byte[32*1024];
705                     while (true)
706                     {
707                         var numRead = inputStream.Read(localBuffer, 0, localBuffer.Length);
708
709                         if (numRead <= 0)
710                         {
711                             break;
712                         }
713
714                         tarOut.Write(localBuffer, 0, numRead);
715                     }
716                 }
717
718                 if (!string.IsNullOrEmpty(tempFileName))
719                 {
720                     File.Delete(tempFileName);
721                 }
722
723                 tarOut.CloseEntry();
724             }
725         }
726
727         /// <summary>
728         /// Releases the unmanaged resources used by the FileStream and optionally releases the managed resources.
729         /// </summary>
730         /// <param name="disposing">true to release both managed and unmanaged resources;
731         /// false to release only unmanaged resources.</param>
732         protected virtual void Dispose(bool disposing)
733         {
734             if (!isDisposed)
735             {
736                 isDisposed = true;
737                 if (disposing)
738                 {
739                     if (tarOut != null)
740                     {
741                         tarOut.Flush();
742                         tarOut.Close();
743                     }
744
745                     if (tarIn != null)
746                     {
747                         tarIn.Close();
748                     }
749                 }
750             }
751         }
752
753         /// <summary>
754         /// Closes the archive and releases any associated resources.
755         /// </summary>
756         public virtual void Close()
757         {
758             Dispose(true);
759             GC.SuppressFinalize(this);
760         }
761
762         /// <summary>
763         /// Ensures that resources are freed and other cleanup operations are performed
764         /// when the garbage collector reclaims the <see cref="TarArchive"/>.
765         /// </summary>
766         ~TarArchive()
767         {
768             Dispose(false);
769         }
770
771         private static void EnsureDirectoryExists(string directoryName)
772         {
773             if (!Directory.Exists(directoryName))
774             {
775                 try
776                 {
777                     Directory.CreateDirectory(directoryName);
778                 }
779                 catch (Exception e)
780                 {
781                     throw new TarException("Exception creating directory '" + directoryName + "', " + e.Message, e);
782                 }
783             }
784         }
785
786         // TODO: TarArchive - Is there a better way to test for a text file?
787         // It no longer reads entire files into memory but is still a weak test!
788         // This assumes that byte values 0-7, 14-31 or 255 are binary
789         // and that all non text files contain one of these values
790         private static bool IsBinary(string filename)
791         {
792             using (var fs = File.OpenRead(filename))
793             {
794                 var sampleSize = Math.Min(4096, (int) fs.Length);
795                 var content = new byte[sampleSize];
796
797                 var bytesRead = fs.Read(content, 0, sampleSize);
798
799                 for (var i = 0; i < bytesRead; ++i)
800                 {
801                     var b = content[i];
802                     if ((b < 8) || ((b > 13) && (b < 32)) || (b == 255))
803                     {
804                         return true;
805                     }
806                 }
807             }
808             return false;
809         }
810
811         #region Instance Fields
812
813         private readonly TarInputStream tarIn;
814         private readonly TarOutputStream tarOut;
815         private bool _applyUserInfoOverrides;
816         private bool _asciiTranslate;
817
818         private int _groupId;
819         private string _groupName = string.Empty;
820
821         private bool isDisposed;
822         private bool _keepOldFiles;
823         private string pathPrefix;
824         private string rootPath;
825         private int _userId;
826         private string _userName = string.Empty;
827
828         #endregion
829
830         #region Constructors
831
832         /// <summary>
833         /// Constructor for a default <see cref="TarArchive"/>.
834         /// </summary>
835         protected TarArchive()
836         {
837         }
838
839         /// <summary>
840         /// Initalise a TarArchive for input.
841         /// </summary>
842         /// <param name="stream">The <see cref="TarInputStream"/> to use for input.</param>
843         protected TarArchive(TarInputStream stream)
844         {
845             if (stream == null)
846             {
847                 throw new ArgumentNullException("stream");
848             }
849
850             tarIn = stream;
851         }
852
853         /// <summary>
854         /// Initialise a TarArchive for output.
855         /// </summary>
856         /// <param name="stream">The <see cref="TarOutputStream"/> to use for output.</param> 
857         protected TarArchive(TarOutputStream stream)
858         {
859             if (stream == null)
860             {
861                 throw new ArgumentNullException("stream");
862             }
863
864             tarOut = stream;
865         }
866
867         #endregion
868
869         #region Static factory methods
870
871         /// <summary>
872         /// The InputStream based constructors create a TarArchive for the
873         /// purposes of extracting or listing a tar archive. Thus, use
874         /// these constructors when you wish to extract files from or list
875         /// the contents of an existing tar archive.
876         /// </summary>
877         /// <param name="inputStream">The stream to retrieve archive data from.</param>
878         /// <returns>Returns a new <see cref="TarArchive"/> suitable for reading from.</returns>
879         public static TarArchive CreateInputTarArchive(Stream inputStream)
880         {
881             if (inputStream == null)
882             {
883                 throw new ArgumentNullException("inputStream");
884             }
885
886             return CreateInputTarArchive(inputStream, TarBuffer.DefaultBlockFactor);
887         }
888
889         /// <summary>
890         /// Create TarArchive for reading setting block factor
891         /// </summary>
892         /// <param name="inputStream">Stream for tar archive contents</param>
893         /// <param name="blockFactor">The blocking factor to apply</param>
894         /// <returns>Returns a <see cref="TarArchive"/> suitable for reading.</returns>
895         public static TarArchive CreateInputTarArchive(Stream inputStream, int blockFactor)
896         {
897             if (inputStream == null)
898             {
899                 throw new ArgumentNullException("inputStream");
900             }
901
902             return new TarArchive(new TarInputStream(inputStream, blockFactor));
903         }
904
905         /// <summary>
906         /// Create a TarArchive for writing to, using the default blocking factor
907         /// </summary>
908         /// <param name="outputStream">The <see cref="Stream"/> to write to</param>
909         /// <returns>Returns a <see cref="TarArchive"/> suitable for writing.</returns>
910         public static TarArchive CreateOutputTarArchive(Stream outputStream)
911         {
912             if (outputStream == null)
913             {
914                 throw new ArgumentNullException("outputStream");
915             }
916
917             return CreateOutputTarArchive(outputStream, TarBuffer.DefaultBlockFactor);
918         }
919
920         /// <summary>
921         /// Create a TarArchive for writing to
922         /// </summary>
923         /// <param name="outputStream">The stream to write to</param>
924         /// <param name="blockFactor">The blocking factor to use for buffering.</param>
925         /// <returns>Returns a <see cref="TarArchive"/> suitable for writing.</returns>
926         public static TarArchive CreateOutputTarArchive(Stream outputStream, int blockFactor)
927         {
928             if (outputStream == null)
929             {
930                 throw new ArgumentNullException("outputStream");
931             }
932
933             return new TarArchive(new TarOutputStream(outputStream, blockFactor));
934         }
935
936         #endregion
937     }
938 }
939
940 /* The original Java file had this header:
941         ** Authored by Timothy Gerard Endres
942         ** <mailto:time@gjt.org>  <http://www.trustice.com>
943         **
944         ** This work has been placed into the public domain.
945         ** You may use this work in any way and for any purpose you wish.
946         **
947         ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
948         ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
949         ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
950         ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
951         ** REDISTRIBUTION OF THIS SOFTWARE.
952         **
953         */