mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 10:54:24 -04:00
464 lines
12 KiB
C++
464 lines
12 KiB
C++
// Filename: multify.cxx
|
|
// Created by:
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PANDA 3D SOFTWARE
|
|
// Copyright (c) 2001, Disney Enterprises, Inc. All rights reserved
|
|
//
|
|
// All use of this software is subject to the terms of the Panda 3d
|
|
// Software license. You should have received a copy of this license
|
|
// along with this source code; you will also find a current copy of
|
|
// the license at http://www.panda3d.org/license.txt .
|
|
//
|
|
// To contact the maintainers of this program write to
|
|
// panda3d@yahoogroups.com .
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
#include "pandabase.h"
|
|
#ifndef HAVE_GETOPT
|
|
#include "gnu_getopt.h"
|
|
#else
|
|
#include <getopt.h>
|
|
#endif
|
|
#include "multifile.h"
|
|
#include "filename.h"
|
|
#include "pset.h"
|
|
#include <stdio.h>
|
|
|
|
|
|
bool create = false; // -c
|
|
bool append = false; // -r
|
|
bool list = false; // -t
|
|
bool extract = false; // -x
|
|
bool verbose = false; // -v
|
|
bool compress = false; // -z
|
|
int default_compression_level = 6;
|
|
Filename multifile_name; // -f
|
|
bool got_multifile_name = false;
|
|
bool to_stdout = false; // -O
|
|
Filename chdir_to; // -C
|
|
bool got_chdir_to = false;
|
|
size_t scale_factor = 0; // -F
|
|
pset<string> dont_compress; // -Z
|
|
|
|
void
|
|
usage() {
|
|
cerr << "Usage: multify -[c|r|t|x] -f <multifile_name> [options] <subfile_name> ...\n";
|
|
}
|
|
|
|
void
|
|
help() {
|
|
cerr << "multify is used to store and extract files from a Panda Multifile.\n"
|
|
<< "This is similar to a tar or zip file in that it is an archive file that\n"
|
|
<< "contains a number of subfiles that may later be extracted.\n\n"
|
|
|
|
<< "The command-line options for multify are designed to be similar to those\n"
|
|
<< "for tar, the traditional Unix archiver utility.\n\n";
|
|
usage();
|
|
}
|
|
|
|
bool
|
|
is_named(const string &subfile_name, int argc, char *argv[]) {
|
|
// Returns true if the indicated subfile appears on the list of
|
|
// files named on the command line.
|
|
if (argc < 2) {
|
|
// No named files; everything is listed.
|
|
return true;
|
|
}
|
|
|
|
for (int i = 1; i < argc; i++) {
|
|
if (subfile_name == argv[i]) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int
|
|
get_compression_level(const Filename &subfile_name) {
|
|
// Returns the appropriate compression level for the named file.
|
|
if (!compress) {
|
|
// Don't compress anything.
|
|
return 0;
|
|
}
|
|
|
|
string ext = subfile_name.get_extension();
|
|
if (dont_compress.find(ext) != dont_compress.end()) {
|
|
// This extension is listed on the -Z parameter list; don't
|
|
// compress it.
|
|
return 0;
|
|
}
|
|
|
|
// Go ahead and compress this file.
|
|
return default_compression_level;
|
|
}
|
|
|
|
bool
|
|
add_directory(Multifile &multifile, const Filename &directory_name) {
|
|
vector_string files;
|
|
if (!directory_name.scan_directory(files)) {
|
|
cerr << "Unable to scan directory " << directory_name << "\n";
|
|
return false;
|
|
}
|
|
|
|
bool okflag = true;
|
|
|
|
vector_string::const_iterator fi;
|
|
for (fi = files.begin(); fi != files.end(); ++fi) {
|
|
Filename subfile_name(directory_name, (*fi));
|
|
if (subfile_name.is_directory()) {
|
|
okflag = add_directory(multifile, subfile_name);
|
|
|
|
} else if (!subfile_name.exists()) {
|
|
cerr << "Not found: " << subfile_name << "\n";
|
|
okflag = false;
|
|
|
|
} else {
|
|
string new_subfile_name =
|
|
multifile.add_subfile(subfile_name, subfile_name,
|
|
get_compression_level(subfile_name));
|
|
if (new_subfile_name.empty()) {
|
|
cerr << "Unable to add " << subfile_name << ".\n";
|
|
okflag = false;
|
|
} else {
|
|
if (verbose) {
|
|
cout << new_subfile_name << "\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return okflag;
|
|
}
|
|
|
|
bool
|
|
add_files(int argc, char *argv[]) {
|
|
Multifile multifile;
|
|
if (append) {
|
|
if (!multifile.open_read_write(multifile_name)) {
|
|
cerr << "Unable to open " << multifile_name << " for updating.\n";
|
|
return false;
|
|
}
|
|
} else {
|
|
if (!multifile.open_write(multifile_name)) {
|
|
cerr << "Unable to open " << multifile_name << " for writing.\n";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (scale_factor != 0 && scale_factor != multifile.get_scale_factor()) {
|
|
cerr << "Setting scale factor to " << scale_factor << "\n";
|
|
multifile.set_scale_factor(scale_factor);
|
|
}
|
|
|
|
bool okflag = true;
|
|
for (int i = 1; i < argc; i++) {
|
|
Filename subfile_name = Filename::from_os_specific(argv[i]);
|
|
if (subfile_name.is_directory()) {
|
|
if (!add_directory(multifile, subfile_name)) {
|
|
okflag = false;
|
|
}
|
|
|
|
} else if (!subfile_name.exists()) {
|
|
cerr << "Not found: " << subfile_name << "\n";
|
|
okflag = false;
|
|
|
|
} else {
|
|
string new_subfile_name =
|
|
multifile.add_subfile(subfile_name, subfile_name,
|
|
get_compression_level(subfile_name));
|
|
if (new_subfile_name.empty()) {
|
|
cerr << "Unable to add " << subfile_name << ".\n";
|
|
okflag = false;
|
|
} else {
|
|
if (verbose) {
|
|
cout << new_subfile_name << "\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (multifile.needs_repack()) {
|
|
if (!multifile.repack()) {
|
|
cerr << "Failed to write " << multifile_name << ".\n";
|
|
okflag = false;
|
|
}
|
|
} else {
|
|
if (!multifile.flush()) {
|
|
cerr << "Failed to write " << multifile_name << ".\n";
|
|
okflag = false;
|
|
}
|
|
}
|
|
|
|
return okflag;
|
|
}
|
|
|
|
bool
|
|
extract_files(int argc, char *argv[]) {
|
|
if (!multifile_name.exists()) {
|
|
cerr << multifile_name << " not found.\n";
|
|
return false;
|
|
}
|
|
Multifile multifile;
|
|
if (!multifile.open_read(multifile_name)) {
|
|
cerr << "Unable to open " << multifile_name << " for reading.\n";
|
|
return false;
|
|
}
|
|
|
|
int num_subfiles = multifile.get_num_subfiles();
|
|
|
|
for (int i = 0; i < num_subfiles; i++) {
|
|
string subfile_name = multifile.get_subfile_name(i);
|
|
if (is_named(subfile_name, argc, argv)) {
|
|
Filename filename = subfile_name;
|
|
if (got_chdir_to) {
|
|
filename = Filename(chdir_to, subfile_name);
|
|
}
|
|
if (to_stdout) {
|
|
if (verbose) {
|
|
cerr << filename << "\n";
|
|
}
|
|
multifile.extract_subfile_to(i, cout);
|
|
} else {
|
|
if (verbose) {
|
|
cout << filename << "\n";
|
|
}
|
|
multifile.extract_subfile(i, filename);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
list_files(int argc, char *argv[]) {
|
|
if (!multifile_name.exists()) {
|
|
cerr << multifile_name << " not found.\n";
|
|
return false;
|
|
}
|
|
Multifile multifile;
|
|
if (!multifile.open_read(multifile_name)) {
|
|
cerr << "Unable to open " << multifile_name << " for reading.\n";
|
|
return false;
|
|
}
|
|
|
|
int num_subfiles = multifile.get_num_subfiles();
|
|
|
|
if (verbose) {
|
|
cout << num_subfiles << " subfiles:\n" << flush;
|
|
for (int i = 0; i < num_subfiles; i++) {
|
|
string subfile_name = multifile.get_subfile_name(i);
|
|
if (is_named(subfile_name, argc, argv)) {
|
|
if (multifile.is_subfile_compressed(i)) {
|
|
double ratio =
|
|
(double)multifile.get_subfile_compressed_length(i) /
|
|
(double)multifile.get_subfile_length(i);
|
|
printf("%12d %3.0f%% %s\n",
|
|
multifile.get_subfile_length(i),
|
|
100.0 - ratio * 100.0,
|
|
subfile_name.c_str());
|
|
} else {
|
|
printf("%12d %s\n",
|
|
multifile.get_subfile_length(i),
|
|
subfile_name.c_str());
|
|
}
|
|
}
|
|
}
|
|
fflush(stdout);
|
|
if (multifile.get_scale_factor() != 1) {
|
|
cout << "Scale factor is " << multifile.get_scale_factor() << "\n";
|
|
}
|
|
if (multifile.needs_repack()) {
|
|
cout << "Multifile needs to be repacked.\n";
|
|
}
|
|
} else {
|
|
for (int i = 0; i < num_subfiles; i++) {
|
|
string subfile_name = multifile.get_subfile_name(i);
|
|
if (is_named(subfile_name, argc, argv)) {
|
|
cout << subfile_name << "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
tokenize_extensions(const string &str, pset<string> &extensions) {
|
|
size_t p = 0;
|
|
while (p < str.length()) {
|
|
size_t q = str.find_first_of(",", p);
|
|
if (q == string::npos) {
|
|
extensions.insert(str.substr(p));
|
|
return;
|
|
}
|
|
extensions.insert(str.substr(p, q - p));
|
|
p = q + 1;
|
|
}
|
|
extensions.insert(string());
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[]) {
|
|
if (argc < 2) {
|
|
usage();
|
|
return 1;
|
|
}
|
|
|
|
// To emulate tar, we assume an implicit hyphen in front of the
|
|
// first argument if there is not one already.
|
|
if (argc >= 2) {
|
|
if (*argv[1] != '-' && *argv[1] != '\0') {
|
|
char *new_arg = new char[strlen(argv[1]) + 2];
|
|
new_arg[0] = '-';
|
|
strcpy(new_arg + 1, argv[1]);
|
|
argv[1] = new_arg;
|
|
}
|
|
}
|
|
|
|
// Default extensions not to compress. May be overridden with -Z.
|
|
string dont_compress_str = "jpg,mp3";
|
|
|
|
extern char *optarg;
|
|
extern int optind;
|
|
static const char *optflags = "crtxvz123456789Z:f:OC:F:h";
|
|
int flag = getopt(argc, argv, optflags);
|
|
Filename rel_path;
|
|
while (flag != EOF) {
|
|
switch (flag) {
|
|
case 'c':
|
|
create = true;
|
|
break;
|
|
case 'r':
|
|
append = true;
|
|
break;
|
|
case 't':
|
|
list = true;
|
|
break;
|
|
case 'x':
|
|
extract = true;
|
|
break;
|
|
case 'v':
|
|
verbose = true;
|
|
break;
|
|
case 'z':
|
|
compress = true;
|
|
break;
|
|
case '1':
|
|
default_compression_level = 1;
|
|
compress = true;
|
|
break;
|
|
case '2':
|
|
default_compression_level = 2;
|
|
compress = true;
|
|
break;
|
|
case '3':
|
|
default_compression_level = 3;
|
|
compress = true;
|
|
break;
|
|
case '4':
|
|
default_compression_level = 4;
|
|
compress = true;
|
|
break;
|
|
case '5':
|
|
default_compression_level = 5;
|
|
compress = true;
|
|
break;
|
|
case '6':
|
|
default_compression_level = 6;
|
|
compress = true;
|
|
break;
|
|
case '7':
|
|
default_compression_level = 7;
|
|
compress = true;
|
|
break;
|
|
case '8':
|
|
default_compression_level = 8;
|
|
compress = true;
|
|
break;
|
|
case '9':
|
|
default_compression_level = 9;
|
|
compress = true;
|
|
break;
|
|
case 'Z':
|
|
dont_compress_str = optarg;
|
|
break;
|
|
case 'f':
|
|
multifile_name = Filename::from_os_specific(optarg);
|
|
got_multifile_name = true;
|
|
break;
|
|
case 'C':
|
|
chdir_to = Filename::from_os_specific(optarg);
|
|
got_chdir_to = true;
|
|
break;
|
|
case 'O':
|
|
to_stdout = true;
|
|
break;
|
|
case 'F':
|
|
{
|
|
char *endptr;
|
|
scale_factor = strtol(optarg, &endptr, 10);
|
|
if (*endptr != '\0') {
|
|
cerr << "Invalid integer: " << optarg << "\n";
|
|
usage();
|
|
return 1;
|
|
}
|
|
if (scale_factor == 0) {
|
|
cerr << "Scale factor must be nonzero.\n";
|
|
usage();
|
|
return 1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'h':
|
|
help();
|
|
return 1;
|
|
case '?':
|
|
usage();
|
|
return 1;
|
|
default:
|
|
cerr << "Unhandled switch: " << flag << endl;
|
|
break;
|
|
}
|
|
flag = getopt(argc, argv, optflags);
|
|
}
|
|
argc -= (optind - 1);
|
|
argv += (optind - 1);
|
|
|
|
// We should have exactly one of these options.
|
|
if ((create?1:0) + (append?1:0) + (list?1:0) + (extract?1:0) != 1) {
|
|
cerr << "Exactly one of -c, -r, -t, -x must be specified.\n";
|
|
usage();
|
|
return 1;
|
|
}
|
|
|
|
if (!got_multifile_name) {
|
|
cerr << "Multifile name not specified.\n";
|
|
usage();
|
|
return 1;
|
|
}
|
|
|
|
// Split out the extensions named by -Z into different words.
|
|
tokenize_extensions(dont_compress_str, dont_compress);
|
|
|
|
bool okflag = true;
|
|
if (create || append) {
|
|
okflag = add_files(argc, argv);
|
|
} else if (extract) {
|
|
okflag = extract_files(argc, argv);
|
|
} else { // list
|
|
okflag = list_files(argc, argv);
|
|
}
|
|
|
|
if (okflag) {
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|