Browse Source

define update_subfile

David Rose 22 years ago
parent
commit
fbdc3e52c7
3 changed files with 181 additions and 45 deletions
  1. 35 14
      panda/src/downloadertools/multify.cxx
  2. 140 29
      panda/src/express/multifile.cxx
  3. 6 2
      panda/src/express/multifile.h

+ 35 - 14
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 <multifile_name> [options] <subfile_name> ...\n";
+    "Usage: multify -[c|r|u|t|x] -f <multifile_name> [options] <subfile_name> ...\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 <extension_list>\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);

+ 140 - 29
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 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 add_new_subfile(subfile_name, 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<Subfiles::iterator, bool> 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();
+  }
 }
 
 ////////////////////////////////////////////////////////////////////

+ 6 - 2
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();