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