2005-01-31 Zoltan Varga <vargaz@freemail.hu>
[mono.git] / mcs / class / ICSharpCode.SharpZipLib / ICSharpCode.SharpZipLib / Tar / TarArchive.cs
1 // TarInputStream.cs\r
2 // Copyright (C) 2001 Mike Krueger\r
3 //\r
4 // This program is free software; you can redistribute it and/or\r
5 // modify it under the terms of the GNU General Public License\r
6 // as published by the Free Software Foundation; either version 2\r
7 // of the License, or (at your option) any later version.\r
8 //\r
9 // This program is distributed in the hope that it will be useful,\r
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of\r
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
12 // GNU General Public License for more details.\r
13 //\r
14 // You should have received a copy of the GNU General Public License\r
15 // along with this program; if not, write to the Free Software\r
16 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\r
17 //\r
18 // Linking this library statically or dynamically with other modules is\r
19 // making a combined work based on this library.  Thus, the terms and\r
20 // conditions of the GNU General Public License cover the whole\r
21 // combination.\r
22 //\r
23 // As a special exception, the copyright holders of this library give you\r
24 // permission to link this library with independent modules to produce an\r
25 // executable, regardless of the license terms of these independent\r
26 // modules, and to copy and distribute the resulting executable under\r
27 // terms of your choice, provided that you also meet, for each linked\r
28 // independent module, the terms and conditions of the license of that\r
29 // module.  An independent module is a module which is not derived from\r
30 // or based on this library.  If you modify this library, you may extend\r
31 // this exception to your version of the library, but you are not\r
32 // obligated to do so.  If you do not wish to do so, delete this\r
33 // exception statement from your version.\r
34 \r
35 using System;\r
36 using System.IO;\r
37 using System.Text;\r
38 \r
39 namespace ICSharpCode.SharpZipLib.Tar {\r
40         \r
41         public delegate void ProgressMessageHandler(TarArchive archive, TarEntry entry, string message);\r
42         \r
43         /// <summary>\r
44         /// The TarArchive class implements the concept of a\r
45         /// tar archive. A tar archive is a series of entries, each of\r
46         /// which represents a file system object. Each entry in\r
47         /// the archive consists of a header block. Directory entries\r
48         /// consist only of the header block, and are followed by entries\r
49         /// for the directory's contents. File entries consist of a\r
50         /// header followed by the number of blocks needed to\r
51         /// contain the file's contents. All entries are written on\r
52         /// block boundaries. Blocks are 512 bytes long.\r
53         /// \r
54         /// TarArchives are instantiated in either read or write mode,\r
55         /// based upon whether they are instantiated with an InputStream\r
56         /// or an OutputStream. Once instantiated TarArchives read/write\r
57         /// mode can not be changed.\r
58         /// \r
59         /// There is currently no support for random access to tar archives.\r
60         /// However, it seems that subclassing TarArchive, and using the\r
61         /// TarBuffer.getCurrentRecordNum() and TarBuffer.getCurrentBlockNum()\r
62         /// methods, this would be rather trvial.\r
63         /// </summary>\r
64         public class TarArchive\r
65         {\r
66                 bool verbose;\r
67                 bool debug;\r
68                 bool keepOldFiles;\r
69                 bool asciiTranslate;\r
70                 \r
71                 int    userId;\r
72                 string userName;\r
73                 int    groupId;\r
74                 string groupName;\r
75                 \r
76                 string rootPath;\r
77                 string pathPrefix;\r
78                 \r
79                 int    recordSize;\r
80                 byte[] recordBuf;\r
81                 \r
82                 TarInputStream  tarIn;\r
83                 TarOutputStream tarOut;\r
84                 \r
85                 public event ProgressMessageHandler ProgressMessageEvent;\r
86                 \r
87                 protected virtual void OnProgressMessageEvent(TarEntry entry, string message)\r
88                 {\r
89                         if (ProgressMessageEvent != null) {\r
90                                 ProgressMessageEvent(this, entry, message);\r
91                         }\r
92                 }\r
93                 \r
94                 protected TarArchive()\r
95                 {\r
96                 }\r
97                 \r
98                 /// <summary>\r
99                 /// The InputStream based constructors create a TarArchive for the\r
100                 /// purposes of e'x'tracting or lis't'ing a tar archive. Thus, use\r
101                 /// these constructors when you wish to extract files from or list\r
102                 /// the contents of an existing tar archive.\r
103                 /// </summary>\r
104                 public static TarArchive CreateInputTarArchive(Stream inputStream)\r
105                 {\r
106                         return CreateInputTarArchive(inputStream, TarBuffer.DefaultBlockFactor);\r
107                 }\r
108                 \r
109                 public static TarArchive CreateInputTarArchive(Stream inputStream, int blockFactor)\r
110                 {\r
111                         TarArchive archive = new TarArchive();\r
112                         archive.tarIn = new TarInputStream(inputStream, blockFactor);\r
113                         archive.Initialize(blockFactor * TarBuffer.BlockSize);\r
114                         return archive;\r
115                 }\r
116                 \r
117                 /// <summary>\r
118                 /// The OutputStream based constructors create a TarArchive for the\r
119                 /// purposes of 'c'reating a tar archive. Thus, use these constructors\r
120                 /// when you wish to create a new tar archive and write files into it.\r
121                 /// </summary>\r
122                 public static TarArchive CreateOutputTarArchive(Stream outputStream)\r
123                 {\r
124                         return CreateOutputTarArchive(outputStream, TarBuffer.DefaultBlockFactor);\r
125                 }\r
126                 \r
127                 public static TarArchive CreateOutputTarArchive(Stream outputStream, int blockFactor)\r
128                 {\r
129                         TarArchive archive = new TarArchive();\r
130                         archive.tarOut = new TarOutputStream(outputStream, blockFactor);\r
131                         archive.Initialize(blockFactor * TarBuffer.BlockSize);\r
132                         return archive;\r
133                 }\r
134                 \r
135                 /// <summary>\r
136                 /// Common constructor initialization code.\r
137                 /// </summary>\r
138                 void Initialize(int recordSize)\r
139                 {\r
140          this.recordSize = recordSize;\r
141                         this.rootPath   = null;\r
142                         this.pathPrefix = null;\r
143                         \r
144 //                      this.tempPath   = System.getProperty( "user.dir" );\r
145                         \r
146                         this.userId    = 0;\r
147                         this.userName  = String.Empty;\r
148                         this.groupId   = 0;\r
149                         this.groupName = String.Empty;\r
150                         \r
151                         this.debug           = false;\r
152                         this.verbose         = false;\r
153                         this.keepOldFiles    = false;\r
154                         \r
155                         this.recordBuf = new byte[RecordSize];\r
156                 }\r
157                 \r
158                 ///\r
159            /// <summary> Set the debugging flag. </summary>\r
160                 ///\r
161                 /// <param name=debugF> The new debug setting. </param>\r
162                 \r
163                 public void SetDebug(bool debugF)\r
164                 {\r
165                         this.debug = debugF;\r
166                         if (this.tarIn != null) {\r
167                                 this.tarIn.SetDebug(debugF);\r
168                         } \r
169                         if (this.tarOut != null) {\r
170                                 this.tarOut.SetDebug(debugF);\r
171                         }\r
172                 }\r
173                 \r
174                 /// <summary>\r
175                 /// Get/Set the verbosity setting.\r
176                 /// </summary>\r
177                 public bool IsVerbose {\r
178                         get {\r
179                                 return verbose;\r
180                         }\r
181                         set {\r
182                                 verbose = value;\r
183                         }\r
184                 }\r
185                 \r
186                 /// <summary>\r
187                 /// Set the flag that determines whether existing files are\r
188                 /// kept, or overwritten during extraction.\r
189                 /// </summary>\r
190                 /// <param name="keepOldFiles">\r
191                 /// If true, do not overwrite existing files.\r
192                 /// </param>\r
193                 public void SetKeepOldFiles(bool keepOldFiles)\r
194                 {\r
195                         this.keepOldFiles = keepOldFiles;\r
196                 }\r
197                 \r
198                 /// <summary>\r
199                 /// Set the ascii file translation flag. If ascii file translation\r
200                 /// is true, then the MIME file type will be consulted to determine\r
201                 /// if the file is of type 'text/*'. If the MIME type is not found,\r
202                 /// then the TransFileTyper is consulted if it is not null. If\r
203                 /// either of these two checks indicates the file is an ascii text\r
204                 /// file, it will be translated. The translation converts the local\r
205                 /// operating system's concept of line ends into the UNIX line end,\r
206                 /// '\n', which is the defacto standard for a TAR archive. This makes\r
207                 /// text files compatible with UNIX.\r
208                 /// </summary>\r
209                 /// <param name= "asciiTranslate">\r
210                 /// If true, translate ascii text files.\r
211                 /// </param>\r
212                 public void SetAsciiTranslation(bool asciiTranslate)\r
213                 {\r
214                         this.asciiTranslate = asciiTranslate;\r
215                 }\r
216                 \r
217 /*\r
218                 /// <summary>\r
219                 /// Set the object that will determine if a file is of type\r
220                 /// ascii text for translation purposes.\r
221                 /// </summary>\r
222                 /// <param name="transTyper">\r
223                 /// The new TransFileTyper object.\r
224                 /// </param>\r
225                 public void SetTransFileTyper(TarTransFileTyper transTyper)\r
226                 {\r
227                         this.transTyper = transTyper;\r
228                 }\r
229 */\r
230                 \r
231                 /// <summary>\r
232                 /// Set user and group information that will be used to fill in the\r
233                 /// tar archive's entry headers. Since Java currently provides no means\r
234                 /// of determining a user name, user id, group name, or group id for\r
235                 /// a given File, TarArchive allows the programmer to specify values\r
236                 /// to be used in their place.\r
237                 /// </summary>\r
238                 /// <param name="userId">\r
239                 /// The user Id to use in the headers.\r
240                 /// </param>\r
241                 /// <param name="userName">\r
242                 /// The user name to use in the headers.\r
243                 /// </param>\r
244                 /// <param name="groupId">\r
245                 /// The group id to use in the headers.\r
246                 /// </param>\r
247                 /// <param name="groupName">\r
248                 /// The group name to use in the headers.\r
249                 /// </param>\r
250                 public void SetUserInfo(int userId, string userName, int groupId, string groupName)\r
251                 {\r
252                         this.userId    = userId;\r
253                         this.userName  = userName;\r
254                         this.groupId   = groupId;\r
255                         this.groupName = groupName;\r
256                 }\r
257                 \r
258                 /// <summary>\r
259                 /// Get the user id being used for archive entry headers.\r
260                 /// </summary>\r
261                 /// <returns>\r
262                 /// The current user id.\r
263                 /// </returns>\r
264                 public int UserId {\r
265                         get {\r
266                                 return this.userId;\r
267                         }\r
268                 }\r
269                 \r
270                 /// <summary>\r
271                 /// Get the user name being used for archive entry headers.\r
272                 /// </summary>\r
273                 /// <returns>\r
274                 /// The current user name.\r
275                 /// </returns>\r
276                 public string UserName {\r
277                         get {\r
278                                 return this.userName;\r
279                         }\r
280                 }\r
281                 \r
282                 /// <summary>\r
283                 /// Get the group id being used for archive entry headers.\r
284                 /// </summary>\r
285                 /// <returns>\r
286                 /// The current group id.\r
287                 /// </returns>\r
288                 public int GroupId {\r
289                         get {\r
290                                 return this.groupId;\r
291                         }\r
292                 }\r
293                 \r
294                 /// <summary>\r
295                 /// Get the group name being used for archive entry headers.\r
296                 /// </summary>\r
297                 /// <returns>\r
298                 /// The current group name.\r
299                 /// </returns>\r
300                 public string GroupName {\r
301                         get {\r
302                                 return this.groupName;\r
303                         }\r
304                 }\r
305                 \r
306                 /// <summary>\r
307                 /// Get the archive's record size. Because of its history, tar\r
308                 /// supports the concept of buffered IO consisting of RECORDS of\r
309                 /// BLOCKS. This allowed tar to match the IO characteristics of\r
310                 /// the physical device being used. Of course, in the Java world,\r
311                 /// this makes no sense, WITH ONE EXCEPTION - archives are expected\r
312                 /// to be properly "blocked". Thus, all of the horrible TarBuffer\r
313                 /// support boils down to simply getting the "boundaries" correct.\r
314                 /// </summary>\r
315                 /// <returns>\r
316                 /// The record size this archive is using.\r
317                 /// </returns>\r
318                 public int RecordSize {\r
319                         get {\r
320                                 if (this.tarIn != null) {\r
321                                         return this.tarIn.GetRecordSize();\r
322                                 } \r
323             else if (this.tarOut != null) {\r
324                                         return this.tarOut.GetRecordSize();\r
325                                 }\r
326                                 return TarBuffer.DefaultRecordSize;\r
327                         }\r
328                 }\r
329                 \r
330                 /// <summary>\r
331                 /// Close the archive. This simply calls the underlying\r
332                 /// tar stream's close() method.\r
333                 /// </summary>\r
334                 public void CloseArchive()\r
335                 {\r
336                         if (this.tarIn != null) {\r
337                                 this.tarIn.Close();\r
338                         } \r
339          else if (this.tarOut != null) {\r
340                                 this.tarOut.Flush();\r
341                                 this.tarOut.Close();\r
342                         }\r
343                 }\r
344                 \r
345                 /// <summary>\r
346                 /// Perform the "list" command and list the contents of the archive.\r
347                 /// \r
348                 /// NOTE That this method uses the progress display to actually list\r
349                 /// the conents. If the progress display is not set, nothing will be\r
350                 /// listed!\r
351                 /// </summary>\r
352                 public void ListContents()\r
353                 {\r
354                         while (true) {\r
355                                 TarEntry entry = this.tarIn.GetNextEntry();\r
356                                 \r
357                                 if (entry == null) {\r
358                                         if (this.debug) {\r
359                                                 Console.Error.WriteLine("READ EOF BLOCK");\r
360                                         }\r
361                                         break;\r
362                                 }\r
363                                 OnProgressMessageEvent(entry, null);\r
364                         }\r
365                 }\r
366                 \r
367                 /// <summary>\r
368                 /// Perform the "extract" command and extract the contents of the archive.\r
369                 /// </summary>\r
370                 /// <param name="destDir">\r
371                 /// The destination directory into which to extract.\r
372                 /// </param>\r
373                 public void ExtractContents(string destDir)\r
374                 {\r
375                         while (true) {\r
376                                 TarEntry entry = this.tarIn.GetNextEntry();\r
377                                 \r
378                                 if (entry == null) {\r
379                                         if (this.debug) {\r
380                                                 Console.Error.WriteLine("READ EOF BLOCK");\r
381                                         }\r
382                                         break;\r
383                                 }\r
384                                 \r
385                                 this.ExtractEntry(destDir, entry);\r
386                         }\r
387                 }\r
388                 \r
389                 void EnsureDirectoryExists(string directoryName)\r
390                 {\r
391                         if (!Directory.Exists(directoryName)) {\r
392                                 try {\r
393                                         Directory.CreateDirectory(directoryName);\r
394                                 }\r
395             catch (Exception e) {\r
396                                         throw new IOException("error making directory path '" + directoryName + "', " + e.Message);\r
397                                 }\r
398                         }\r
399                 }\r
400                 \r
401                 // TODO -jr- No longer reads entire file into memory but is still a weak test!\r
402                 bool IsBinary(string filename)\r
403                 {\r
404                         FileStream fs = File.OpenRead(filename);\r
405                         \r
406          int sampleSize = System.Math.Min(4096, (int)fs.Length);\r
407                         byte[] content = new byte[sampleSize];\r
408                         \r
409                         fs.Read(content, 0, sampleSize);\r
410                         fs.Close();\r
411                         \r
412                         // assume that ascii 0 or \r
413                         // ascii 255 are only found in non text files.\r
414                         // and that all non text files contain 0 and 255\r
415                         foreach (byte b in content) {\r
416                                 if (b == 0 || b == 255) {\r
417                                         return true;\r
418                                 }\r
419                         }\r
420                         \r
421                         return false;\r
422                 }               \r
423                 \r
424                 /// <summary>\r
425                 /// Extract an entry from the archive. This method assumes that the\r
426                 /// tarIn stream has been properly set with a call to getNextEntry().\r
427                 /// </summary>\r
428                 /// <param name="destDir">\r
429                 /// The destination directory into which to extract.\r
430                 /// </param>\r
431                 /// <param name="entry">\r
432                 /// The TarEntry returned by tarIn.getNextEntry().\r
433                 /// </param>\r
434                 void ExtractEntry(string destDir, TarEntry entry)\r
435                 {\r
436                         if (this.verbose) {\r
437                                 OnProgressMessageEvent(entry, null);\r
438                         }\r
439                         \r
440                         string name = entry.Name;\r
441                         name = name.Replace('/', Path.DirectorySeparatorChar);\r
442                         \r
443                         if (!destDir.EndsWith(Path.DirectorySeparatorChar.ToString())) {\r
444                                 destDir += Path.DirectorySeparatorChar;\r
445                         }\r
446                         \r
447                         string destFile = destDir + name;\r
448                         \r
449                         if (entry.IsDirectory) {\r
450                                 EnsureDirectoryExists(destFile);\r
451                         } \r
452          else {\r
453                                 string parentDirectory = Path.GetDirectoryName(destFile);\r
454                                 EnsureDirectoryExists(parentDirectory);\r
455                                 \r
456                                 if (this.keepOldFiles && File.Exists(destFile)) {\r
457                                         if (this.verbose) {\r
458                                                 OnProgressMessageEvent(entry, "Destination file already exists");\r
459                                         }\r
460                                 } \r
461             else {\r
462                                         bool asciiTrans = false;\r
463                                         Stream outputStream = File.Create(destFile);\r
464                                         if (this.asciiTranslate) {\r
465                                                 asciiTrans = !IsBinary(destFile);\r
466 // original java sourcecode : \r
467 //                                              MimeType mime      = null;\r
468 //                                              string contentType = null;\r
469 //                                              try {\r
470 //                                                      contentType = FileTypeMap.getDefaultFileTypeMap().getContentType( destFile );\r
471 //                                                      \r
472 //                                                      mime = new MimeType(contentType);\r
473 //                                                      \r
474 //                                                      if (mime.getPrimaryType().equalsIgnoreCase( "text" )) {\r
475 //                                                              asciiTrans = true;\r
476 //                                                      } else if ( this.transTyper != null ) {\r
477 //                                                          if ( this.transTyper.isAsciiFile( entry.getName() ) ) {\r
478 //                                                              asciiTrans = true;\r
479 //                                                          }\r
480 //                                                      }\r
481 //                                              } catch (MimeTypeParseException ex) {\r
482 //                                              }\r
483 //                                              \r
484 //                                              if (this.debug) {\r
485 //                                                      Console.Error.WriteLine(("EXTRACT TRANS? '" + asciiTrans + "'  ContentType='" + contentType + "'  PrimaryType='" + mime.getPrimaryType() + "'" );\r
486 //                                              }\r
487                                         }\r
488                                         \r
489                                         StreamWriter outw = null;\r
490                                         if (asciiTrans) {\r
491                                                 outw = new StreamWriter(outputStream);\r
492                                         }\r
493                                         \r
494                                         byte[] rdbuf = new byte[32 * 1024];\r
495                                         \r
496                                         while (true) {\r
497                                                 int numRead = this.tarIn.Read(rdbuf, 0, rdbuf.Length);\r
498                                                 \r
499                                                 if (numRead <= 0) {\r
500                                                         break;\r
501                                                 }\r
502                                                 \r
503                                                 if (asciiTrans) {\r
504                                                         for (int off = 0, b = 0; b < numRead; ++b) {\r
505                                                                 if (rdbuf[b] == 10) {\r
506                                                                         string s = Encoding.ASCII.GetString(rdbuf, off, (b - off));\r
507                                                                         outw.WriteLine(s);\r
508                                                                         off = b + 1;\r
509                                                                 }\r
510                                                         }\r
511                                                 } \r
512                   else {\r
513                                                         outputStream.Write(rdbuf, 0, numRead);\r
514                                                 }\r
515                                         }\r
516                                         \r
517                                         if (asciiTrans) {\r
518                                                 outw.Close();\r
519                                         } \r
520                else {\r
521                                                 outputStream.Close();\r
522                                         }\r
523                                 }\r
524                         }\r
525                 }\r
526                 \r
527                 /// <summary>\r
528                 /// Write an entry to the archive. This method will call the putNextEntry\r
529                 /// and then write the contents of the entry, and finally call closeEntry()()\r
530                 /// for entries that are files. For directories, it will call putNextEntry(),\r
531                 /// and then, if the recurse flag is true, process each entry that is a\r
532                 /// child of the directory.\r
533                 /// </summary>\r
534                 /// <param name="entry">\r
535                 /// The TarEntry representing the entry to write to the archive.\r
536                 /// </param>\r
537                 /// <param name="recurse">\r
538                 /// If true, process the children of directory entries.\r
539                 /// </param>\r
540                 public void WriteEntry(TarEntry entry, bool recurse)\r
541                 {\r
542                         bool asciiTrans = false;\r
543                         \r
544                         string tempFileName = null;\r
545                         string eFile        = entry.File;\r
546                         \r
547                         // Work on a copy of the entry so we can manipulate it.\r
548                         // Note that we must distinguish how the entry was constructed.\r
549                         //\r
550                         if (eFile == null || eFile.Length == 0) {\r
551                                 entry = TarEntry.CreateTarEntry(entry.Name);\r
552                         } \r
553          else {\r
554                                 //\r
555                                 // The user may have explicitly set the entry's name to\r
556                                 // something other than the file's path, so we must save\r
557                                 // and restore it. This should work even when the name\r
558                                 // was set from the File's name.\r
559                                 //\r
560                                 string saveName = entry.Name;\r
561                                 entry = TarEntry.CreateEntryFromFile(eFile);\r
562                                 entry.Name = saveName;\r
563                         }\r
564                         \r
565                         if (this.verbose) {\r
566                                 OnProgressMessageEvent(entry, null);\r
567                         }\r
568                         \r
569                         if (this.asciiTranslate && !entry.IsDirectory) {\r
570                                 asciiTrans = !IsBinary(eFile);\r
571 \r
572 // original java source :\r
573 //                              MimeType mime = null;\r
574 //                              string contentType = null;\r
575 //      \r
576 //                              try {\r
577 //                                      contentType = FileTypeMap.getDefaultFileTypeMap(). getContentType( eFile );\r
578 //                                      \r
579 //                                      mime = new MimeType( contentType );\r
580 //                                      \r
581 //                                      if ( mime.getPrimaryType().\r
582 //                                          equalsIgnoreCase( "text" ) )\r
583 //                                          {\r
584 //                                              asciiTrans = true;\r
585 //                                          }\r
586 //                                          else if ( this.transTyper != null )\r
587 //                                          {\r
588 //                                              if ( this.transTyper.isAsciiFile( eFile ) )\r
589 //                                              {\r
590 //                                                      asciiTrans = true;\r
591 //                                              }\r
592 //                                          }\r
593 //                              } catch ( MimeTypeParseException ex )\r
594 //                              {\r
595 //      //                               IGNORE THIS ERROR...\r
596 //                              }\r
597 //                      \r
598 //                      if (this.debug) {\r
599 //                              Console.Error.WriteLine("CREATE TRANS? '" + asciiTrans + "'  ContentType='" + contentType + "'  PrimaryType='" + mime.getPrimaryType()+ "'" );\r
600 //                      }\r
601                         \r
602                         if (asciiTrans) {\r
603                                 tempFileName = Path.GetTempFileName();\r
604                                 \r
605                                 StreamReader inStream  = File.OpenText(eFile);\r
606                                 Stream       outStream = new BufferedStream(File.Create(tempFileName));\r
607                                 \r
608                                 while (true) {\r
609                                         string line = inStream.ReadLine();\r
610                                         if (line == null) {\r
611                                                 break;\r
612                                         }\r
613                                         byte[] data = Encoding.ASCII.GetBytes(line);\r
614                                         outStream.Write(data, 0, data.Length);\r
615                                         outStream.WriteByte((byte)'\n');\r
616                                 }\r
617                                 \r
618                                 inStream.Close();\r
619 \r
620                                 outStream.Flush();\r
621                                 outStream.Close();\r
622                                 \r
623                                 entry.Size = new FileInfo(tempFileName).Length;\r
624                                 \r
625                                 eFile = tempFileName;\r
626                         }\r
627                         }\r
628                     \r
629                     string newName = null;\r
630                 \r
631                         if (this.rootPath != null) {\r
632                                 if (entry.Name.StartsWith(this.rootPath)) {\r
633                                         newName = entry.Name.Substring(this.rootPath.Length + 1 );\r
634                                 }\r
635                         }\r
636                         \r
637                         if (this.pathPrefix != null) {\r
638                                 newName = (newName == null) ? this.pathPrefix + "/" + entry.Name : this.pathPrefix + "/" + newName;\r
639                         }\r
640                         \r
641                         if (newName != null) {\r
642                                 entry.Name = newName;\r
643                         }\r
644                         \r
645                         this.tarOut.PutNextEntry(entry);\r
646                         \r
647                         if (entry.IsDirectory) {\r
648                                 if (recurse) {\r
649                                         TarEntry[] list = entry.GetDirectoryEntries();\r
650                                         for (int i = 0; i < list.Length; ++i) {\r
651                                                 this.WriteEntry(list[i], recurse);\r
652                                         }\r
653                                 }\r
654                         }\r
655          else {\r
656                                 Stream inputStream = File.OpenRead(eFile);\r
657                                 int numWritten = 0;\r
658                                 byte[] eBuf = new byte[32 * 1024];\r
659                                 while (true) {\r
660                                         int numRead = inputStream.Read(eBuf, 0, eBuf.Length);\r
661                                         \r
662                                         if (numRead <=0) {\r
663                                                 break;\r
664                                         }\r
665                                         \r
666                                         this.tarOut.Write(eBuf, 0, numRead);\r
667                                         numWritten +=  numRead;\r
668                                 }\r
669 \r
670 //                              Console.WriteLine("written " + numWritten + " bytes");\r
671                                 \r
672                                 inputStream.Close();\r
673                                 \r
674                                 if (tempFileName != null && tempFileName.Length > 0) {\r
675                                         File.Delete(tempFileName);\r
676                                 }\r
677                                 \r
678                                 this.tarOut.CloseEntry();\r
679                         }\r
680                 }\r
681         }\r
682 }\r
683 /* The original Java file had this header:\r
684         ** Authored by Timothy Gerard Endres\r
685         ** <mailto:time@gjt.org>  <http://www.trustice.com>\r
686         **\r
687         ** This work has been placed into the public domain.\r
688         ** You may use this work in any way and for any purpose you wish.\r
689         **\r
690         ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,\r
691         ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR\r
692         ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY\r
693         ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR\r
694         ** REDISTRIBUTION OF THIS SOFTWARE.\r
695         **\r
696         */\r