diff --git a/panda/src/downloadertools/multify.cxx b/panda/src/downloadertools/multify.cxx index 01cb82e3d6..a61cc3b5db 100644 --- a/panda/src/downloadertools/multify.cxx +++ b/panda/src/downloadertools/multify.cxx @@ -30,6 +30,7 @@ bool create = false; // -c bool append = false; // -r +bool update = false; // -u bool list = false; // -t bool extract = false; // -x bool verbose = false; // -v @@ -49,7 +50,7 @@ string dont_compress_str = "jpg,mp3"; void usage() { cerr << - "Usage: multify -[c|r|t|x] -f [options] ...\n"; + "Usage: multify -[c|r|u|t|x] -f [options] ...\n"; } void @@ -82,6 +83,12 @@ help() { " the Multifile with the same name. The Multifile will be repacked\n" " after completion, even if no Subfiles were added.\n\n" + " -u\n" + " Update an existing Multifile archive. This is similar to -r, except\n" + " that files are compared byte-for-byte with their corresponding files\n" + " in the archive first. If they have not changed, the multifile is not\n" + " modified (other than to repack it if necessary).\n\n" + " -t\n" " List the contents of an existing Multifile. With -v, this shows\n" " the size of each Subfile and its compression ratio, if compressed.\n\n" @@ -127,7 +134,6 @@ help() { " -O\n" " With -x, extract subfiles to standard output instead of to disk.\n\n" - " -Z \n" " Specify a comma-separated list of filename extensions that represent\n" " files that are not to be compressed. The default if this is omitted is\n" @@ -191,16 +197,23 @@ add_directory(Multifile &multifile, const Filename &directory_name) { 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); + 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)); + string new_subfile_name; + if (update) { + new_subfile_name = multifile.update_subfile + (subfile_name, subfile_name, get_compression_level(subfile_name)); + } else { + 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; @@ -218,7 +231,7 @@ add_directory(Multifile &multifile, const Filename &directory_name) { bool add_files(int argc, char *argv[]) { Multifile multifile; - if (append) { + if (append || update) { if (!multifile.open_read_write(multifile_name)) { cerr << "Unable to open " << multifile_name << " for updating.\n"; return false; @@ -248,9 +261,14 @@ add_files(int argc, char *argv[]) { okflag = false; } else { - string new_subfile_name = - multifile.add_subfile(subfile_name, subfile_name, - get_compression_level(subfile_name)); + string new_subfile_name; + if (update) { + new_subfile_name = multifile.update_subfile + (subfile_name, subfile_name, get_compression_level(subfile_name)); + } else { + 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; @@ -406,7 +424,7 @@ main(int argc, char *argv[]) { extern char *optarg; extern int optind; - static const char *optflags = "crtxvz123456789Z:f:OC:F:h"; + static const char *optflags = "crutxvz123456789Z:f:OC:F:h"; int flag = getopt(argc, argv, optflags); Filename rel_path; while (flag != EOF) { @@ -417,6 +435,9 @@ main(int argc, char *argv[]) { case 'r': append = true; break; + case 'u': + update = true; + break; case 't': list = true; break; @@ -512,8 +533,8 @@ main(int argc, char *argv[]) { 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"; + if ((create?1:0) + (append?1:0) + (update?1:0) + (list?1:0) + (extract?1:0) != 1) { + cerr << "Exactly one of -c, -r, -u, -t, -x must be specified.\n"; usage(); return 1; } @@ -528,7 +549,7 @@ main(int argc, char *argv[]) { tokenize_extensions(dont_compress_str, dont_compress); bool okflag = true; - if (create || append) { + if (create || append || update) { okflag = add_files(argc, argv); } else if (extract) { okflag = extract_files(argc, argv); diff --git a/panda/src/express/multifile.cxx b/panda/src/express/multifile.cxx index 406c854346..34088f3ed9 100644 --- a/panda/src/express/multifile.cxx +++ b/panda/src/express/multifile.cxx @@ -294,7 +294,10 @@ set_scale_factor(size_t scale_factor) { // Access: Published // Description: Adds a file on disk as a subfile to the Multifile. // The file named by filename will be read and added to -// the Multifile at the next call to flush(). +// the Multifile at the next call to flush(). If there +// already exists a subfile with the indicated name, it +// is replaced without examining its contents (but see +// also update_subfile). // // Returns the subfile name on success (it might have // been modified slightly), or empty string on failure. @@ -307,11 +310,58 @@ add_subfile(const string &subfile_name, const Filename &filename, if (!filename.exists()) { return string(); } - Subfile *subfile = new Subfile; - subfile->_source_filename = filename; - subfile->_source_filename.set_binary(); + string name = standardize_subfile_name(subfile_name); + if (!name.empty()) { + Subfile *subfile = new Subfile; + subfile->_name = name; + subfile->_source_filename = filename; + subfile->_source_filename.set_binary(); + + add_new_subfile(subfile, compression_level); + } - return add_new_subfile(subfile_name, subfile, compression_level); + return name; +} + +//////////////////////////////////////////////////////////////////// +// Function: Multifile::update_subfile +// Access: Published +// Description: Adds a file on disk to the subfile. If a subfile +// already exists with the same name, its contents are +// compared to the disk file, and it is replaced only if +// it is different; otherwise, the multifile is left +// unchanged. +//////////////////////////////////////////////////////////////////// +string Multifile:: +update_subfile(const string &subfile_name, const Filename &filename, + int compression_level) { + nassertr(is_write_valid(), string()); + + if (!filename.exists()) { + return string(); + } + string name = standardize_subfile_name(subfile_name); + if (!name.empty()) { + int index = find_subfile(name); + if (index >= 0) { + // The subfile already exists; compare it to the source file. + if (compare_subfile(index, filename)) { + // The files are identical; do nothing. + return name; + } + } + + // The subfile does not already exist or it is different from the + // source file. Add the new source file. + Subfile *subfile = new Subfile; + subfile->_name = name; + subfile->_source_filename = filename; + subfile->_source_filename.set_binary(); + + add_new_subfile(subfile, compression_level); + } + + return name; } //////////////////////////////////////////////////////////////////// @@ -545,7 +595,7 @@ get_num_subfiles() const { int Multifile:: find_subfile(const string &subfile_name) const { Subfile find_subfile; - find_subfile._name = subfile_name; + find_subfile._name = standardize_subfile_name(subfile_name); Subfiles::const_iterator fi; fi = _subfiles.find(&find_subfile); if (fi == _subfiles.end()) { @@ -590,7 +640,7 @@ has_directory(const string &subfile_name) const { // Access: Published // Description: Considers subfile_name to be the name of a // subdirectory within the Multifile, but not a file -// itself; ills the given vector up with the sorted list +// itself; fills the given vector up with the sorted list // of subdirectories or files within the named // directory. // @@ -808,6 +858,57 @@ extract_subfile(int index, const Filename &filename) { return extract_subfile_to(index, out); } +//////////////////////////////////////////////////////////////////// +// Function: Multifile::compare_subfile +// Access: Published +// Description: Performs a byte-for-byte comparison of the indicated +// file on disk with the nth subfile. Returns true if +// the files are equivalent, or false if they are +// different (or the file is missing). +//////////////////////////////////////////////////////////////////// +bool Multifile:: +compare_subfile(int index, const Filename &filename) { + nassertr(is_read_valid(), false); + nassertr(index >= 0 && index < (int)_subfiles.size(), false); + + if (!filename.exists()) { + express_cat.info() + << "File is missing: " << filename << "\n"; + return false; + } + + istream *in1 = open_read_subfile(index); + if (in1 == (istream *)NULL) { + return false; + } + + ifstream in2; + Filename bin_filename = Filename::binary_filename(filename); + if (!bin_filename.open_read(in2)) { + express_cat.info() + << "Cannot read " << filename << "\n"; + return false; + } + + int byte1 = in1->get(); + int byte2 = in2.get(); + while (!in1->fail() && !in1->eof() && + !in2.fail() && !in2.eof()) { + if (byte1 != byte2) { + delete in1; + return false; + } + byte1 = in1->get(); + byte2 = in2.get(); + } + + bool failed = (in1->fail() && !in1->eof()) || (in2.fail() && !in2.eof()); + delete in1; + + nassertr(!failed, false); + return true; +} + //////////////////////////////////////////////////////////////////// // Function: Multifile::output // Access: Published @@ -928,7 +1029,7 @@ open_read_write(iostream *multifile_stream) { //////////////////////////////////////////////////////////////////// // Function: Multifile::add_subfile // Access: Public -// Description: Adds a file on disk as a subfile to the Multifile. +// Description: Adds a file from a stream as a subfile to the Multifile. // The indicated istream will be read and its contents // added to the Multifile at the next call to flush(). // @@ -940,10 +1041,15 @@ add_subfile(const string &subfile_name, istream *subfile_data, int compression_level) { nassertr(is_write_valid(), string()); - Subfile *subfile = new Subfile; - subfile->_source = subfile_data; + string name = standardize_subfile_name(subfile_name); + if (!name.empty()) { + Subfile *subfile = new Subfile; + subfile->_name = name; + subfile->_source = subfile_data; + add_new_subfile(subfile, compression_level); + } - return add_new_subfile(subfile_name, subfile, compression_level); + return name; } //////////////////////////////////////////////////////////////////// @@ -1001,9 +1107,8 @@ pad_to_streampos(streampos fpos) { // Description: Adds a newly-allocated Subfile pointer to the // Multifile. //////////////////////////////////////////////////////////////////// -string Multifile:: -add_new_subfile(const string &subfile_name, Subfile *subfile, - int compression_level) { +void Multifile:: +add_new_subfile(Subfile *subfile, int compression_level) { if (compression_level != 0) { #ifndef HAVE_ZLIB express_cat.warning() @@ -1021,20 +1126,6 @@ add_new_subfile(const string &subfile_name, Subfile *subfile, _needs_repack = true; } - // Normalize the Subfile name: eliminate ./, leading slash, etc. - Filename name = subfile_name; - name.standardize(); - if (name.empty() || name == "/") { - // Invalid empty name. - return string(); - } - - if (name[0] == '/') { - subfile->_name = name.get_fullpath().substr(1); - } else { - subfile->_name = name; - } - pair insert_result = _subfiles.insert(subfile); if (!insert_result.second) { // Hmm, unable to insert. There must already be a subfile by that @@ -1046,7 +1137,27 @@ add_new_subfile(const string &subfile_name, Subfile *subfile, } _new_subfiles.push_back(subfile); - return subfile->_name; +} + +//////////////////////////////////////////////////////////////////// +// Function: Multifile::standardize_subfile_name +// Access: Private +// Description: Returns the standard form of the subfile name. +//////////////////////////////////////////////////////////////////// +string Multifile:: +standardize_subfile_name(const string &subfile_name) const { + Filename name = subfile_name; + name.standardize(); + if (name.empty() || name == "/") { + // Invalid empty name. + return string(); + } + + if (name[0] == '/') { + return name.get_fullpath().substr(1); + } else { + return name.get_fullpath(); + } } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/express/multifile.h b/panda/src/express/multifile.h index 95f16e1eef..1071f5e3fd 100644 --- a/panda/src/express/multifile.h +++ b/panda/src/express/multifile.h @@ -57,6 +57,8 @@ PUBLISHED: string add_subfile(const string &subfile_name, const Filename &filename, int compression_level); + string update_subfile(const string &subfile_name, const Filename &filename, + int compression_level); bool flush(); bool repack(); @@ -74,6 +76,7 @@ PUBLISHED: INLINE string read_subfile(int index); istream *open_read_subfile(int index); bool extract_subfile(int index, const Filename &filename); + bool compare_subfile(int index, const Filename &filename); void output(ostream &out) const; void ls(ostream &out = cout) const; @@ -129,8 +132,9 @@ private: INLINE streampos normalize_streampos(streampos fpos) const; streampos pad_to_streampos(streampos fpos); - string add_new_subfile(const string &subfile_name, Subfile *subfile, - int compression_level); + void add_new_subfile(Subfile *subfile, int compression_level); + string standardize_subfile_name(const string &subfile_name) const; + void clear_subfiles(); bool read_index(); bool write_header();