00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
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;
00032 bool append = false;
00033 bool list = false;
00034 bool extract = false;
00035 bool verbose = false;
00036 bool compress = false;
00037 int default_compression_level = 6;
00038 Filename multifile_name;
00039 bool got_multifile_name = false;
00040 bool to_stdout = false;
00041 Filename chdir_to;
00042 bool got_chdir_to = false;
00043 size_t scale_factor = 0;
00044 pset<string> dont_compress;
00045
00046
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
00146
00147 if (argc < 2) {
00148
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
00164 if (!compress) {
00165
00166 return 0;
00167 }
00168
00169 string ext = subfile_name.get_extension();
00170 if (dont_compress.find(ext) != dont_compress.end()) {
00171
00172
00173 return 0;
00174 }
00175
00176
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
00397
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
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
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 {
00536 okflag = list_files(argc, argv);
00537 }
00538
00539 if (okflag) {
00540 return 0;
00541 } else {
00542 return 1;
00543 }
00544 }