Main Page   Class Hierarchy   Alphabetical List   Compound List   File List   Compound Members   File Members  

panda/src/downloadertools/multify.cxx

Go to the documentation of this file.
00001 // Filename: multify.cxx
00002 // Created by:  
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
00008 //
00009 // All use of this software is subject to the terms of the Panda 3d
00010 // Software license.  You should have received a copy of this license
00011 // along with this source code; you will also find a current copy of
00012 // the license at http://www.panda3d.org/license.txt .
00013 //
00014 // To contact the maintainers of this program write to
00015 // panda3d@yahoogroups.com .
00016 //
00017 ////////////////////////////////////////////////////////////////////
00018 
00019 #include "pandabase.h"
00020 #ifndef HAVE_GETOPT
00021   #include "gnu_getopt.h"
00022 #else
00023   #include <getopt.h>
00024 #endif
00025 #include "multifile.h"
00026 #include "filename.h"
00027 #include "pset.h"
00028 #include <stdio.h>
00029 
00030 
00031 bool create = false;      // -c
00032 bool append = false;      // -r
00033 bool list = false;        // -t
00034 bool extract = false;     // -x
00035 bool verbose = false;     // -v
00036 bool compress = false;    // -z
00037 int default_compression_level = 6;
00038 Filename multifile_name;  // -f
00039 bool got_multifile_name = false;
00040 bool to_stdout = false;   // -O
00041 Filename chdir_to;        // -C
00042 bool got_chdir_to = false;
00043 size_t scale_factor = 0;  // -F
00044 pset<string> dont_compress; // -Z
00045 
00046 // Default extensions not to compress.  May be overridden with -Z.
00047 string dont_compress_str = "jpg,mp3";
00048 
00049 void 
00050 usage() {
00051   cerr <<
00052     "Usage: multify -[c|r|t|x] -f <multifile_name> [options] <subfile_name> ...\n";
00053 }
00054 
00055 void 
00056 help() {
00057   usage();
00058   cerr << "\n"
00059     "multify is used to store and extract files from a Panda Multifile.\n"
00060     "This is similar to a tar or zip file in that it is an archive file that\n"
00061     "contains a number of subfiles that may later be extracted.\n\n"
00062 
00063     "Panda's VirtualFileSystem is capable of mounting Multifiles for direct\n"
00064     "access to the subfiles contained within without having to extract them\n"
00065     "out to independent files first.\n\n"
00066 
00067     "The command-line options for multify are designed to be similar to those\n"
00068     "for tar, the traditional Unix archiver utility.\n\n"
00069 
00070     "Options:\n\n"
00071     
00072     "  You must specify exactly one of the following command switches:\n\n"
00073 
00074     "  -c\n"
00075     "      Create a new Multifile archive.  Subfiles named on the command line\n"
00076     "      will be added to the new Multifile.  If the Multifile already exists,\n"
00077     "      it is first removed.\n\n"
00078 
00079     "  -r\n"
00080     "      Rewrite an existing Multifile archive.  Subfiles named on the command\n"
00081     "      line will be added to the Multifile or will replace subfiles within\n"
00082     "      the Multifile with the same name.  The Multifile will be repacked\n"
00083     "      after completion, even if no Subfiles were added.\n\n"
00084 
00085     "  -t\n"
00086     "      List the contents of an existing Multifile.  With -v, this shows\n"
00087     "      the size of each Subfile and its compression ratio, if compressed.\n\n"
00088 
00089     "  -x\n"
00090     "      Extract the contents of an existing Multifile.  The Subfiles named on\n"
00091     "      the command line, or all Subfiles if nothing is named, are extracted\n"
00092     "      into the current directory or into whichever directory is specified\n"
00093     "      with -C.\n\n\n"
00094 
00095     
00096     "  You must always specify the following switch:\n\n"
00097 
00098     "  -f <multifile_name>\n"
00099     "      Names the Multifile that will be operated on.\n\n\n"
00100 
00101     "  The remaining switches are optional:\n\n"
00102 
00103     "  -v\n"
00104     "      Run verbosely.  In -c, -r, or -x mode, list each file as it is\n"
00105     "      written or extracted.  In -t mode, list more information about each\n"
00106     "      file.\n\n"
00107 
00108     "  -z\n"
00109     "      Compress subfiles as they are written to the Multifile.  Unlike tar\n"
00110     "      (but like zip), subfiles are compressed individually, instead of the\n"
00111     "      entire archive being compressed as one stream.  It is not necessary\n"
00112     "      to specify -z when extracting compressed subfiles; they will always be\n"
00113     "      decompressed automatically.  Also see -Z, which restricts which\n"
00114     "      subfiles will be compressed based on the filename extension.\n\n"
00115 
00116     "  -F <scale_factor>\n"
00117     "      Specify a Multifile scale factor.  This is only necessary to support\n"
00118     "      Multifiles that will exceed 4GB in size.  The default scale factor is\n"
00119     "      1, which should be sufficient for almost any application, but the total\n"
00120     "      size of the Multifile will be limited to 4GB * scale_factor.  The size\n"
00121     "      of individual subfiles may not exceed 4GB in any case.\n\n"
00122 
00123     "  -C <extract_dir>\n"
00124 
00125     "      With -x, change to the named directory before extracting files;\n"
00126     "      that is, extract subfiles into the named directory.\n\n"
00127 
00128     "  -O\n"
00129     "      With -x, extract subfiles to standard output instead of to disk.\n\n"
00130 
00131     "  -Z <extension_list>\n"
00132     "      Specify a comma-separated list of filename extensions that represent\n"
00133     "      files that are not to be compressed.  The default if this is omitted is\n"
00134     "      \"" << dont_compress_str << "\".  Specify -Z \"\" (be sure to include the space) to allow\n"
00135     "      all files to be compressed.\n\n"
00136 
00137     "  -1 .. -9\n"
00138     "      Specify the compression level when -z is in effect.  Larger numbers\n"
00139     "      generate slightly smaller files, but compression takes longer.  The\n"
00140     "      default is -" << default_compression_level << ".\n\n";
00141 }
00142 
00143 bool
00144 is_named(const string &subfile_name, int argc, char *argv[]) {
00145   // Returns true if the indicated subfile appears on the list of
00146   // files named on the command line.
00147   if (argc < 2) {
00148     // No named files; everything is listed.
00149     return true;
00150   }
00151 
00152   for (int i = 1; i < argc; i++) {
00153     if (subfile_name == argv[i]) {
00154       return true;
00155     }
00156   }
00157 
00158   return false;
00159 }
00160 
00161 int
00162 get_compression_level(const Filename &subfile_name) {
00163   // Returns the appropriate compression level for the named file.
00164   if (!compress) {
00165     // Don't compress anything.
00166     return 0;
00167   }
00168 
00169   string ext = subfile_name.get_extension();
00170   if (dont_compress.find(ext) != dont_compress.end()) {
00171     // This extension is listed on the -Z parameter list; don't
00172     // compress it.
00173     return 0;
00174   }
00175 
00176   // Go ahead and compress this file.
00177   return default_compression_level;
00178 }
00179 
00180 bool
00181 add_directory(Multifile &multifile, const Filename &directory_name) {
00182   vector_string files;
00183   if (!directory_name.scan_directory(files)) {
00184     cerr << "Unable to scan directory " << directory_name << "\n";
00185     return false;
00186   }
00187 
00188   bool okflag = true;
00189 
00190   vector_string::const_iterator fi;
00191   for (fi = files.begin(); fi != files.end(); ++fi) {
00192     Filename subfile_name(directory_name, (*fi));
00193     if (subfile_name.is_directory()) {
00194       okflag = add_directory(multifile, subfile_name);
00195 
00196     } else if (!subfile_name.exists()) {
00197       cerr << "Not found: " << subfile_name << "\n";
00198       okflag = false;
00199 
00200     } else {
00201       string new_subfile_name =
00202         multifile.add_subfile(subfile_name, subfile_name,
00203                               get_compression_level(subfile_name));
00204       if (new_subfile_name.empty()) {
00205         cerr << "Unable to add " << subfile_name << ".\n";
00206         okflag = false;
00207       } else {
00208         if (verbose) {
00209           cout << new_subfile_name << "\n";
00210         }
00211       }
00212     }
00213   }
00214 
00215   return okflag;
00216 }
00217 
00218 bool
00219 add_files(int argc, char *argv[]) {
00220   Multifile multifile;
00221   if (append) {
00222     if (!multifile.open_read_write(multifile_name)) {
00223       cerr << "Unable to open " << multifile_name << " for updating.\n";
00224       return false;
00225     }
00226   } else {
00227     if (!multifile.open_write(multifile_name)) {
00228       cerr << "Unable to open " << multifile_name << " for writing.\n";
00229       return false;
00230     }
00231   }
00232 
00233   if (scale_factor != 0 && scale_factor != multifile.get_scale_factor()) {
00234     cerr << "Setting scale factor to " << scale_factor << "\n";
00235     multifile.set_scale_factor(scale_factor);
00236   }
00237 
00238   bool okflag = true;
00239   for (int i = 1; i < argc; i++) {
00240     Filename subfile_name = Filename::from_os_specific(argv[i]);
00241     if (subfile_name.is_directory()) {
00242       if (!add_directory(multifile, subfile_name)) {
00243         okflag = false;
00244       }
00245 
00246     } else if (!subfile_name.exists()) {
00247       cerr << "Not found: " << subfile_name << "\n";
00248       okflag = false;
00249 
00250     } else {
00251       string new_subfile_name =
00252         multifile.add_subfile(subfile_name, subfile_name,
00253                               get_compression_level(subfile_name));
00254       if (new_subfile_name.empty()) {
00255         cerr << "Unable to add " << subfile_name << ".\n";
00256         okflag = false;
00257       } else {
00258         if (verbose) {
00259           cout << new_subfile_name << "\n";
00260         }
00261       }
00262     }
00263   }
00264 
00265   if (multifile.needs_repack()) {
00266     if (!multifile.repack()) {
00267       cerr << "Failed to write " << multifile_name << ".\n";
00268       okflag = false;
00269     }
00270   } else {
00271     if (!multifile.flush()) {
00272       cerr << "Failed to write " << multifile_name << ".\n";
00273       okflag = false;
00274     }
00275   }
00276 
00277   return okflag;
00278 }
00279 
00280 bool
00281 extract_files(int argc, char *argv[]) {
00282   if (!multifile_name.exists()) {
00283     cerr << multifile_name << " not found.\n";
00284     return false;
00285   }
00286   Multifile multifile;
00287   if (!multifile.open_read(multifile_name)) {
00288     cerr << "Unable to open " << multifile_name << " for reading.\n";
00289     return false;
00290   }
00291 
00292   int num_subfiles = multifile.get_num_subfiles();
00293 
00294   for (int i = 0; i < num_subfiles; i++) {
00295     string subfile_name = multifile.get_subfile_name(i);
00296     if (is_named(subfile_name, argc, argv)) {
00297       Filename filename = subfile_name;
00298       if (got_chdir_to) {
00299         filename = Filename(chdir_to, subfile_name);
00300       }
00301       if (to_stdout) {
00302         if (verbose) {
00303           cerr << filename << "\n";
00304         }
00305         multifile.extract_subfile_to(i, cout);
00306       } else {
00307         if (verbose) {
00308           cout << filename << "\n";
00309         }
00310         multifile.extract_subfile(i, filename);
00311       }
00312     }
00313   }
00314 
00315   return true;
00316 }
00317 
00318 bool
00319 list_files(int argc, char *argv[]) {
00320   if (!multifile_name.exists()) {
00321     cerr << multifile_name << " not found.\n";
00322     return false;
00323   }
00324   Multifile multifile;
00325   if (!multifile.open_read(multifile_name)) {
00326     cerr << "Unable to open " << multifile_name << " for reading.\n";
00327     return false;
00328   }
00329 
00330   int num_subfiles = multifile.get_num_subfiles();
00331   
00332   if (verbose) {
00333     cout << num_subfiles << " subfiles:\n" << flush;
00334     for (int i = 0; i < num_subfiles; i++) {
00335       string subfile_name = multifile.get_subfile_name(i);
00336       if (is_named(subfile_name, argc, argv)) {
00337         if (multifile.is_subfile_compressed(i)) {
00338           size_t orig_length = multifile.get_subfile_length(i);
00339           size_t compressed_length = multifile.get_subfile_compressed_length(i);
00340           double ratio = 1.0;
00341           if (orig_length != 0) {
00342             ratio = (double)compressed_length / (double)orig_length;
00343           }
00344           printf("%12d %3.0f%%  %s\n",
00345                  multifile.get_subfile_length(i),
00346                  100.0 - ratio * 100.0,
00347                  subfile_name.c_str());
00348         } else {
00349           printf("%12d       %s\n", 
00350                  multifile.get_subfile_length(i),
00351                  subfile_name.c_str());
00352         }
00353       }
00354     }
00355     fflush(stdout);
00356     if (multifile.get_scale_factor() != 1) {
00357       cout << "Scale factor is " << multifile.get_scale_factor() << "\n";
00358     }
00359     if (multifile.needs_repack()) {
00360       cout << "Multifile needs to be repacked.\n";
00361     }
00362   } else {
00363     for (int i = 0; i < num_subfiles; i++) {
00364       string subfile_name = multifile.get_subfile_name(i);
00365       if (is_named(subfile_name, argc, argv)) {
00366         cout << subfile_name << "\n";
00367       }
00368     }
00369   }
00370 
00371   return true;
00372 }
00373 
00374 void
00375 tokenize_extensions(const string &str, pset<string> &extensions) {
00376   size_t p = 0;
00377   while (p < str.length()) {
00378     size_t q = str.find_first_of(",", p);
00379     if (q == string::npos) {
00380       extensions.insert(str.substr(p));
00381       return;
00382     }
00383     extensions.insert(str.substr(p, q - p));
00384     p = q + 1;
00385   }
00386   extensions.insert(string());
00387 }
00388 
00389 int
00390 main(int argc, char *argv[]) {
00391   if (argc < 2) {
00392     usage();
00393     return 1;
00394   }
00395 
00396   // To emulate tar, we assume an implicit hyphen in front of the
00397   // first argument if there is not one already.
00398   if (argc >= 2) {
00399     if (*argv[1] != '-' && *argv[1] != '\0') {
00400       char *new_arg = new char[strlen(argv[1]) + 2];
00401       new_arg[0] = '-';
00402       strcpy(new_arg + 1, argv[1]);
00403       argv[1] = new_arg;
00404     }
00405   }
00406 
00407   extern char *optarg;
00408   extern int optind;
00409   static const char *optflags = "crtxvz123456789Z:f:OC:F:h";
00410   int flag = getopt(argc, argv, optflags);
00411   Filename rel_path;
00412   while (flag != EOF) {
00413     switch (flag) {
00414     case 'c':
00415       create = true;
00416       break;
00417     case 'r':
00418       append = true;
00419       break;
00420     case 't':
00421       list = true;
00422       break;
00423     case 'x':
00424       extract = true;
00425       break;
00426     case 'v':
00427       verbose = true;
00428       break;
00429     case 'z':
00430       compress = true;
00431       break;
00432     case '1':
00433       default_compression_level = 1;
00434       compress = true;
00435       break;
00436     case '2':
00437       default_compression_level = 2;
00438       compress = true;
00439       break;
00440     case '3':
00441       default_compression_level = 3;
00442       compress = true;
00443       break;
00444     case '4':
00445       default_compression_level = 4;
00446       compress = true;
00447       break;
00448     case '5':
00449       default_compression_level = 5;
00450       compress = true;
00451       break;
00452     case '6':
00453       default_compression_level = 6;
00454       compress = true;
00455       break;
00456     case '7':
00457       default_compression_level = 7;
00458       compress = true;
00459       break;
00460     case '8':
00461       default_compression_level = 8;
00462       compress = true;
00463       break;
00464     case '9':
00465       default_compression_level = 9;
00466       compress = true;
00467       break;
00468     case 'Z':
00469       dont_compress_str = optarg;
00470       break;
00471     case 'f':
00472       multifile_name = Filename::from_os_specific(optarg);
00473       got_multifile_name = true;
00474       break;
00475     case 'C':
00476       chdir_to = Filename::from_os_specific(optarg);
00477       got_chdir_to = true;
00478       break;
00479     case 'O':
00480       to_stdout = true;
00481       break;
00482     case 'F':
00483       {
00484         char *endptr;
00485         scale_factor = strtol(optarg, &endptr, 10);
00486         if (*endptr != '\0') {
00487           cerr << "Invalid integer: " << optarg << "\n";
00488           usage();
00489           return 1;
00490         }
00491         if (scale_factor == 0) {
00492           cerr << "Scale factor must be nonzero.\n";
00493           usage();
00494           return 1;
00495         }
00496       }
00497       break;
00498 
00499     case 'h':
00500       help();
00501       return 1;
00502     case '?':
00503       usage();
00504       return 1;
00505     default:
00506       cerr << "Unhandled switch: " << flag << endl;
00507       break;
00508     }
00509     flag = getopt(argc, argv, optflags);
00510   }
00511   argc -= (optind - 1);
00512   argv += (optind - 1);
00513 
00514   // We should have exactly one of these options.
00515   if ((create?1:0) + (append?1:0) + (list?1:0) + (extract?1:0) != 1) {
00516     cerr << "Exactly one of -c, -r, -t, -x must be specified.\n";
00517     usage();
00518     return 1;
00519   }
00520 
00521   if (!got_multifile_name) {
00522     cerr << "Multifile name not specified.\n";
00523     usage();
00524     return 1;
00525   }
00526 
00527   // Split out the extensions named by -Z into different words.
00528   tokenize_extensions(dont_compress_str, dont_compress);
00529 
00530   bool okflag = true;
00531   if (create || append) {
00532     okflag = add_files(argc, argv);
00533   } else if (extract) {
00534     okflag = extract_files(argc, argv);
00535   } else { // list
00536     okflag = list_files(argc, argv);
00537   }
00538 
00539   if (okflag) {
00540     return 0;
00541   } else {
00542     return 1;
00543   }
00544 }

Generated on Fri May 2 00:36:53 2003 for Panda by doxygen1.3