Browse Source

steps toward C++ patching

David Rose 16 years ago
parent
commit
81c2514bc4

+ 1 - 9
direct/src/p3d/PackageInfo.py

@@ -514,15 +514,7 @@ class PackageInfo:
         from direct.p3d.PatchMaker import PatchMaker
 
         patchMaker = PatchMaker(self.packageDir)
-        package = patchMaker.readPackageDescFile(self.descFileBasename)
-        patchMaker.buildPatchChains()
-        fromPv = patchMaker.getPackageVersion(package.getGenericKey(fileSpec))
-        toPv = package.currentPv
-
-        patchChain = None
-        if toPv and fromPv:
-            patchChain = toPv.getPatchChain(fromPv)
-
+        patchChain = patchMaker.getPatchChainToCurrent(self.descFileBasename, fileSpec)
         if patchChain is None:
             # No path.
             patchMaker.cleanup()

+ 83 - 29
direct/src/p3d/PatchMaker.py

@@ -10,15 +10,15 @@ class PatchMaker:
 
     class PackageVersion:
         """ A specific patch version of a package.  This is not just
-        the package's "version" number; it also corresponds to the
+        the package's "version" string; it also corresponds to the
         particular patch version, which increments independently of
         the "version". """
         
-        def __init__(self, packageName, platform, version, host, file):
+        def __init__(self, packageName, platform, version, hostUrl, file):
             self.packageName = packageName
             self.platform = platform
             self.version = version
-            self.host = host
+            self.hostUrl = hostUrl
             self.file = file
             self.printName = None
 
@@ -43,8 +43,8 @@ class PatchMaker:
 
         def getPatchChain(self, startPv):
             """ Returns a list of patches that, when applied in
-            sequence to the indicated patchVersion object, will
-            produce this patchVersion object.  Returns None if no
+            sequence to the indicated PackageVersion object, will
+            produce this PackageVersion object.  Returns None if no
             chain can be found. """
 
             if self is startPv:
@@ -173,7 +173,7 @@ class PatchMaker:
                 if patch.packageName == package.packageName and \
                    patch.platform == package.platform and \
                    patch.version == package.version and \
-                   patch.host == package.host:
+                   patch.hostUrl == package.hostUrl:
                     return patch.toPv
 
             return None
@@ -186,25 +186,49 @@ class PatchMaker:
             self.packageName = package.packageName
             self.platform = package.platform
             self.version = package.version
-            self.host = None
+            self.hostUrl = None
+
+            # FileSpec for the patchfile itself
+            self.file = None
+
+            # FileSpec for the package file that the patch is applied to
+            self.sourceFile = None
+
+            # FileSpec for the package file that the patch generates
+            self.targetFile = None
+
+            # The PackageVersion corresponding to our sourceFile
+            self.fromPv = None
+
+            # The PackageVersion corresponding to our targetFile
+            self.toPv = None
 
         def getSourceKey(self):
-            return (self.packageName, self.platform, self.version, self.host, self.sourceFile)
+            """ Returns the key for locating the package that this
+            patchfile can be applied to. """
+            return (self.packageName, self.platform, self.version, self.hostUrl, self.sourceFile)
 
         def getTargetKey(self):
-            return (self.packageName, self.platform, self.version, self.host, self.targetFile)
+            """ Returns the key for locating the package that this
+            patchfile will generate. """
+            return (self.packageName, self.platform, self.version, self.hostUrl, self.targetFile)
 
         def fromFile(self, packageDir, patchFilename, sourceFile, targetFile):
+            """ Creates the data structures from an existing patchfile
+            on disk. """
+            
             self.file = FileSpec()
             self.file.fromFile(packageDir, patchFilename)
             self.sourceFile = sourceFile
             self.targetFile = targetFile
 
         def loadXml(self, xpatch):
+            """ Reads the data structures from an xml file. """
+            
             self.packageName = xpatch.Attribute('name') or self.packageName
             self.platform = xpatch.Attribute('platform') or self.platform
             self.version = xpatch.Attribute('version') or self.version
-            self.host = xpatch.Attribute('host') or self.host
+            self.hostUrl = xpatch.Attribute('host') or self.hostUrl
 
             self.file = FileSpec()
             self.file.loadXml(xpatch)
@@ -228,8 +252,8 @@ class PatchMaker:
                 xpatch.SetAttribute('platform', self.platform)
             if self.version != package.version:
                 xpatch.SetAttribute('version', self.version)
-            if self.host != package.host:
-                xpatch.SetAttribute('host', self.host)
+            if self.hostUrl != package.hostUrl:
+                xpatch.SetAttribute('host', self.hostUrl)
 
             self.file.storeXml(xpatch)
 
@@ -259,7 +283,7 @@ class PatchMaker:
             self.packageName = None
             self.platform = None
             self.version = None
-            self.host = None
+            self.hostUrl = None
             self.currentFile = None
             self.baseFile = None
 
@@ -268,18 +292,25 @@ class PatchMaker:
             self.patches = []
 
         def getCurrentKey(self):
-            return (self.packageName, self.platform, self.version, self.host, self.currentFile)
+            """ Returns the key to locate the current version of this
+            package. """
+            
+            return (self.packageName, self.platform, self.version, self.hostUrl, self.currentFile)
 
         def getBaseKey(self):
-            return (self.packageName, self.platform, self.version, self.host, self.baseFile)
+            """ Returns the key to locate the "base" or oldest version
+            of this package. """
+            
+            return (self.packageName, self.platform, self.version, self.hostUrl, self.baseFile)
 
         def getGenericKey(self, fileSpec):
-            """ Returns the key that has the indicated FileSpec. """
-            return (self.packageName, self.platform, self.version, self.host, fileSpec)
+            """ Returns the key that has the indicated hash. """
+            return (self.packageName, self.platform, self.version, self.hostUrl, fileSpec)
 
         def readDescFile(self):
-            """ Reads the existing package.xml file and stores
-            it in this class for later rewriting. """
+            """ Reads the existing package.xml file and stores it in
+            this class for later rewriting.  Returns true on success,
+            false on failure. """
 
             self.anyChanges = False
 
@@ -287,11 +318,11 @@ class PatchMaker:
             self.doc = TiXmlDocument(packageDescFullpath.toOsSpecific())
             if not self.doc.LoadFile():
                 print "Couldn't read %s" % (packageDescFullpath)
-                return
+                return False
             
             xpackage = self.doc.FirstChildElement('package')
             if not xpackage:
-                return
+                return False
             self.packageName = xpackage.Attribute('name')
             self.platform = xpackage.Attribute('platform')
             self.version = xpackage.Attribute('version')
@@ -300,7 +331,7 @@ class PatchMaker:
             # "none" host.  TODO: support patching from packages on
             # other hosts, which means we'll need to fill in a value
             # here for those hosts.
-            self.host = None
+            self.hostUrl = None
 
             # Get the current patch version.  If we have a
             # patch_version attribute, it refers to this particular
@@ -389,6 +420,8 @@ class PatchMaker:
                 self.patches.append(patchfile)
                 xpatch = xpatch.NextSiblingElement('patch')
 
+            return True
+
         def writeDescFile(self):
             """ Rewrites the desc file with the new patch
             information. """
@@ -436,7 +469,7 @@ class PatchMaker:
                 fileSpec.fromFile(self.patchMaker.installDir, self.packageDesc)
                 fileSpec.storeXml(self.contentsDocPackage)
             
-
+    # PatchMaker constructor.
     def __init__(self, installDir):
         self.installDir = installDir
         self.packageVersions = {}
@@ -468,15 +501,36 @@ class PatchMaker:
         for pv in self.packageVersions.values():
             pv.cleanup()
 
+    def getPatchChainToCurrent(self, descFilename, fileSpec):
+        """ Reads the package defined in the indicated desc file, and
+        constructs a patch chain from the version represented by
+        fileSpec to the current version of this package, if possible.
+        Returns the patch chain if successful, or None otherwise. """
+        
+        package = self.readPackageDescFile(descFilename)
+        if not package:
+            return None
+        
+        self.buildPatchChains()
+        fromPv = self.getPackageVersion(package.getGenericKey(fileSpec))
+        toPv = package.currentPv
+
+        patchChain = None
+        if toPv and fromPv:
+            patchChain = toPv.getPatchChain(fromPv)
+
+        return patchChain
+
     def readPackageDescFile(self, descFilename):
         """ Reads a desc file associated with a particular package,
-        and adds the package to self.packageVersions.  Returns the
-        Package object. """
+        and adds the package to self.packages.  Returns the Package
+        object, or None on failure. """
 
         package = self.Package(Filename(descFilename), self)
-        package.readDescFile()
+        if not package.readDescFile():
+            return None
+        
         self.packages.append(package)
-
         return package
 
     def readContentsFile(self):
@@ -522,10 +576,10 @@ class PatchMaker:
         """ Returns a shared PackageVersion object for the indicated
         key. """
 
-        packageName, platform, version, host, file = key
+        packageName, platform, version, hostUrl, file = key
 
         # We actually key on the hash, not the FileSpec itself.
-        k = (packageName, platform, version, host, file.hash)
+        k = (packageName, platform, version, hostUrl, file.hash)
         pv = self.packageVersions.get(k, None)
         if not pv:
             pv = self.PackageVersion(*key)

+ 2 - 0
direct/src/plugin/Sources.pp

@@ -48,6 +48,7 @@
     p3dObject.h p3dObject.I \
     p3dOsxSplashWindow.h p3dOsxSplashWindow.I \
     p3dPackage.h p3dPackage.I \
+    p3dPatchFinder.h p3dPatchFinder.I \
     p3dPythonObject.h \
     p3dReferenceCount.h p3dReferenceCount.I \
     p3dSession.h p3dSession.I \
@@ -81,6 +82,7 @@
     p3dObject.cxx \
     p3dOsxSplashWindow.cxx \
     p3dPackage.cxx \
+    p3dPatchFinder.cxx \
     p3dPythonObject.cxx \
     p3dReferenceCount.cxx \
     p3dSession.cxx \

+ 20 - 4
direct/src/plugin/fileSpec.I

@@ -15,7 +15,7 @@
 
 ////////////////////////////////////////////////////////////////////
 //     Function: FileSpec::get_filename
-//       Access: Private
+//       Access: Public
 //  Description: Returns the relative path to this file on disk,
 //               within the package root directory.
 ////////////////////////////////////////////////////////////////////
@@ -26,7 +26,7 @@ get_filename() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: FileSpec::set_filename
-//       Access: Private
+//       Access: Public
 //  Description: Changes the relative path to this file on disk,
 //               within the package root directory.
 ////////////////////////////////////////////////////////////////////
@@ -37,7 +37,7 @@ set_filename(const string &filename) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: FileSpec::get_pathname
-//       Access: Private
+//       Access: Public
 //  Description: Returns the full path to this file on disk.
 ////////////////////////////////////////////////////////////////////
 inline string FileSpec::
@@ -47,7 +47,7 @@ get_pathname(const string &package_dir) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: FileSpec::get_size
-//       Access: Private
+//       Access: Public
 //  Description: Returns the expected size of this file on disk, in
 //               bytes.
 ////////////////////////////////////////////////////////////////////
@@ -56,6 +56,22 @@ get_size() const {
   return _size;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: FileSpec::get_actual_file
+//       Access: Public
+//  Description: After a call to quick_verify() or full_verify(), this
+//               method *may* return a pointer to a FileSpec that
+//               represents the actual data read on disk, or it may
+//               return NULL.  If this returns a non-NULL value, you
+//               may use it to extract the md5 hash of the existing
+//               file, thus saving the effort of performing the hash
+//               twice.
+////////////////////////////////////////////////////////////////////
+inline const FileSpec *FileSpec::
+get_actual_file() const {
+  return _actual_file;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: FileSpec::decode_hexdigit
 //       Access: Private

+ 92 - 26
direct/src/plugin/fileSpec.cxx

@@ -17,9 +17,11 @@
 
 #include <fstream>
 #include <fcntl.h>
+#include <assert.h>
 
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <time.h>
 
 #ifdef _WIN32
 #include <sys/utime.h>
@@ -44,6 +46,7 @@ FileSpec() {
   _timestamp = 0;
   memset(_hash, 0, sizeof(_hash));
   _got_hash = false;
+  _actual_file = NULL;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -59,6 +62,7 @@ FileSpec(const FileSpec &copy) :
   _got_hash(copy._got_hash)
 {
   memcpy(_hash, copy._hash, sizeof(_hash));
+  _actual_file = NULL;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -75,6 +79,18 @@ operator = (const FileSpec &copy) {
   _got_hash = copy._got_hash;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: FileSpec::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+FileSpec::
+~FileSpec() {
+  if (_actual_file != NULL) {
+    delete _actual_file;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: FileSpec::load_xml
 //       Access: Public
@@ -119,7 +135,12 @@ load_xml(TiXmlElement *xelement) {
 //               redownloaded.
 ////////////////////////////////////////////////////////////////////
 bool FileSpec::
-quick_verify(const string &package_dir) const {
+quick_verify(const string &package_dir) {
+  if (_actual_file != NULL) {
+    delete _actual_file;
+    _actual_file = NULL;
+  }
+
   string pathname = get_pathname(package_dir);
   struct stat st;
   if (stat(pathname.c_str(), &st) != 0) {
@@ -143,7 +164,7 @@ quick_verify(const string &package_dir) const {
 
   // If the size is right but the timestamp is wrong, the file
   // soft-fails.  We follow this up with a hash check.
-  if (!check_hash(pathname)) {
+  if (!priv_check_hash(pathname, st)) {
     // Hard fail, the hash is wrong.
     //cerr << "hash check wrong: " << _filename << "\n";
     return false;
@@ -174,7 +195,12 @@ quick_verify(const string &package_dir) const {
 //               redownloaded.
 ////////////////////////////////////////////////////////////////////
 bool FileSpec::
-full_verify(const string &package_dir) const {
+full_verify(const string &package_dir) {
+  if (_actual_file != NULL) {
+    delete _actual_file;
+    _actual_file = NULL;
+  }
+
   string pathname = get_pathname(package_dir);
   struct stat st;
   if (stat(pathname.c_str(), &st) != 0) {
@@ -188,7 +214,7 @@ full_verify(const string &package_dir) const {
     return false;
   }
 
-  if (!check_hash(pathname)) {
+  if (!priv_check_hash(pathname, st)) {
     // Hard fail, the hash is wrong.
     //cerr << "hash check wrong: " << _filename << "\n";
     return false;
@@ -218,31 +244,12 @@ full_verify(const string &package_dir) const {
 ////////////////////////////////////////////////////////////////////
 bool FileSpec::
 check_hash(const string &pathname) const {
-  ifstream stream(pathname.c_str(), ios::in | ios::binary);
-  if (!stream) {
-    //cerr << "unable to read " << pathname << "\n";
+  FileSpec other;
+  if (!other.read_hash(pathname)) {
     return false;
   }
 
-  unsigned char md[hash_size];
-
-  MD5_CTX ctx;
-  MD5_Init(&ctx);
-
-  static const int buffer_size = 4096;
-  char buffer[buffer_size];
-
-  stream.read(buffer, buffer_size);
-  size_t count = stream.gcount();
-  while (count != 0) {
-    MD5_Update(&ctx, buffer, count);
-    stream.read(buffer, buffer_size);
-    count = stream.gcount();
-  }
-
-  MD5_Final(md, &ctx);
-
-  return (memcmp(md, _hash, hash_size) == 0);
+  return (memcmp(_hash, other._hash, hash_size) == 0);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -280,6 +287,65 @@ read_hash(const string &pathname) {
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: FileSpec::compare_hash
+//       Access: Public
+//  Description: Returns true if this hash sorts before the other
+//               hash, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool FileSpec::
+compare_hash(const FileSpec &other) const {
+  return memcmp(_hash, other._hash, hash_size) < 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FileSpec::write
+//       Access: Public
+//  Description: Describes the data in the FileSpec.
+////////////////////////////////////////////////////////////////////
+void FileSpec::
+write(ostream &out) const {
+  out << "filename: " << _filename << ", " << _size << " bytes, "
+      << asctime(localtime(&_timestamp));
+  // asctime includes a newline.
+  out << "hash: ";
+  stream_hex(out, _hash, hash_size);
+  out << "\n";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FileSpec::output_hash
+//       Access: Public
+//  Description: Writes just the hash code.
+////////////////////////////////////////////////////////////////////
+void FileSpec::
+output_hash(ostream &out) const {
+  stream_hex(out, _hash, hash_size);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FileSpec::priv_check_hash
+//       Access: Private
+//  Description: Returns true if the file has the expected md5 hash,
+//               false otherwise.  Updates _actual_file with the data
+//               read from disk, including the hash, for future
+//               reference.
+////////////////////////////////////////////////////////////////////
+bool FileSpec::
+priv_check_hash(const string &pathname, const struct stat &st) {
+  assert(_actual_file == NULL);
+  _actual_file = new FileSpec;
+  _actual_file->_filename = pathname;
+  _actual_file->_size = st.st_size;
+  _actual_file->_timestamp = st.st_mtime;
+
+  if (!_actual_file->read_hash(pathname)) {
+    return false;
+  }
+
+  return (memcmp(_hash, _actual_file->_hash, hash_size) == 0);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: FileSpec::decode_hex
 //       Access: Private, Static

+ 12 - 2
direct/src/plugin/fileSpec.h

@@ -31,6 +31,8 @@ public:
   FileSpec();
   FileSpec(const FileSpec &copy);
   void operator = (const FileSpec &copy);
+  ~FileSpec();
+
   void load_xml(TiXmlElement *xelement);
 
   inline const string &get_filename() const;
@@ -38,13 +40,19 @@ public:
   inline string get_pathname(const string &package_dir) const;
   inline size_t get_size() const;
   
-  bool quick_verify(const string &package_dir) const;
-  bool full_verify(const string &package_dir) const;
+  bool quick_verify(const string &package_dir);
+  bool full_verify(const string &package_dir);
+  inline const FileSpec *get_actual_file() const;
   
   bool check_hash(const string &pathname) const;
   bool read_hash(const string &pathname);
+  bool compare_hash(const FileSpec &other) const;
+
+  void write(ostream &out) const;
+  void output_hash(ostream &out) const;
 
 private:
+  bool priv_check_hash(const string &pathname, const struct stat &st);
   static inline int decode_hexdigit(char c);
   static inline char encode_hexdigit(int c);
 
@@ -59,6 +67,8 @@ private:
   time_t _timestamp;
   unsigned char _hash[hash_size];
   bool _got_hash;
+
+  FileSpec *_actual_file;
 };
 
 #include "fileSpec.I"

+ 3 - 3
direct/src/plugin/p3dInstance.cxx

@@ -1795,7 +1795,7 @@ report_package_info_ready(P3DPackage *package) {
       _download_package_index = 0;
       _total_downloaded = 0;
       
-      nout << "Beginning download of " << _downloading_packages.size()
+      nout << "Beginning install of " << _downloading_packages.size()
            << " packages, total " << _total_download_size
            << " bytes required.\n";
       
@@ -1844,7 +1844,7 @@ start_next_download() {
       _panda_script_object->set_int_property("downloadPackageSize", package->get_download_size());
       set_install_label("Installing " + name);
 
-      nout << "Downloading " << package->get_package_name()
+      nout << "Installing " << package->get_package_name()
            << ", package " << _download_package_index + 1
            << " of " << _downloading_packages.size()
            << ", " << package->get_download_size()
@@ -1999,7 +1999,7 @@ report_package_progress(P3DPackage *package, double progress) {
 ////////////////////////////////////////////////////////////////////
 void P3DInstance::
 report_package_done(P3DPackage *package, bool success) {
-  nout << "Done downloading " << package->get_package_name()
+  nout << "Done installing " << package->get_package_name()
        << ": success = " << success << "\n";
 
   if (package == _image_package) {

+ 35 - 2
direct/src/plugin/p3dPackage.cxx

@@ -17,6 +17,7 @@
 #include "p3dInstance.h"
 #include "p3dMultifileReader.h"
 #include "p3dTemporaryFile.h"
+#include "p3dPatchFinder.h"
 #include "mkdir_complete.h"
 
 #include "zlib.h"
@@ -672,7 +673,7 @@ got_desc_file(TiXmlDocument *doc, bool freshly_downloaded) {
   } else {
     // We need to get the file data still, but at least we know all
     // about it by this point.
-    build_install_plans();
+    build_install_plans(doc);
 
     if (!_allow_data_download) {
       // Not authorized to start downloading yet; just report that
@@ -712,7 +713,7 @@ clear_install_plans() {
 //               to download and install the package.
 ////////////////////////////////////////////////////////////////////
 void P3DPackage::
-build_install_plans() {
+build_install_plans(TiXmlDocument *doc) {
   clear_install_plans();
 
   if (_instances.empty()) {
@@ -728,6 +729,8 @@ build_install_plans() {
   _install_plans.push_back(InstallPlan());
   InstallPlan &plan = _install_plans.back();
 
+  bool needs_redownload = false;
+  
   InstallStep *step;
   if (!_uncompressed_archive.quick_verify(_package_dir)) {
     // The uncompressed archive is no good.
@@ -735,6 +738,7 @@ build_install_plans() {
     if (!_compressed_archive.quick_verify(_package_dir)) {
       // The compressed archive is no good either.  Download a new
       // compressed archive.
+      needs_redownload = true;
       step = new InstallStepDownloadFile(this, _compressed_archive);
       plan.push_back(step);
     }
@@ -748,6 +752,35 @@ build_install_plans() {
   // Unpack the uncompressed archive.
   step = new InstallStepUnpackArchive(this, _unpack_size);
   plan.push_back(step);
+
+  if (needs_redownload) {
+    // Since we need to do some downloading, try to build a plan that
+    // involves downloading patches instead of downloading the whole
+    // file.  This will be our first choice, plan A, if we can do it.
+
+    // We'll need the md5 hash of the uncompressed archive currently
+    // on disk.
+
+    // Maybe we've already read the md5 hash and we have it stored here.
+    const FileSpec *on_disk_ptr = _uncompressed_archive.get_actual_file();
+    FileSpec on_disk;
+    if (on_disk_ptr == NULL) {
+      // If not, we have to go read it now.
+      if (on_disk.read_hash(_uncompressed_archive.get_pathname(_package_dir))) {
+        on_disk_ptr = &on_disk;
+      }
+    }
+
+    if (on_disk_ptr != NULL) {
+      P3DPatchFinder patch_finder;
+      P3DPatchFinder::Patchfiles chain;
+      if (patch_finder.get_patch_chain_to_current(chain, doc, *on_disk_ptr)) {
+        cerr << "got patch chain of length " << chain.size() << "\n";
+      } else {
+        cerr << "No patch chain possible.\n";
+      }
+    }
+  }
 }
 
 ////////////////////////////////////////////////////////////////////

+ 1 - 1
direct/src/plugin/p3dPackage.h

@@ -180,7 +180,7 @@ private:
   void got_desc_file(TiXmlDocument *doc, bool freshly_downloaded);
 
   void clear_install_plans();
-  void build_install_plans();
+  void build_install_plans(TiXmlDocument *doc);
   void follow_install_plans(bool download_finished);
 
   class InstallStep;

+ 14 - 0
direct/src/plugin/p3dPatchFinder.I

@@ -0,0 +1,14 @@
+// Filename: p3dPatchFinder.I
+// Created by:  drose (27Sep09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+

+ 439 - 0
direct/src/plugin/p3dPatchFinder.cxx

@@ -0,0 +1,439 @@
+// Filename: p3dPatchFinder.cxx
+// Created by:  drose (27Sep09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "p3dPatchFinder.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchFinder::PackageVersion::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+P3DPatchFinder::PackageVersion::
+PackageVersion(const PackageVersionKey &key) :
+  _package_name(key._package_name),
+  _platform(key._platform),
+  _version(key._version),
+  _host_url(key._host_url),
+  _file(key._file)
+{
+  _package_current = NULL;
+  _package_base = NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchFinder::PackageVersion::get_patch_chain
+//       Access: Public
+//  Description: Fills chain with the list of patches that, when
+//               applied in sequence to the indicated PackageVersion
+//               object, produces this PackageVersion object.  Returns
+//               false if no chain can be found.
+////////////////////////////////////////////////////////////////////
+bool P3DPatchFinder::PackageVersion::
+get_patch_chain(Patchfiles &chain, PackageVersion *start_pv) {
+  chain.clear();
+  if (this == start_pv) {
+    // We're already here.  A zero-length patch chain is therefore the
+    // answer.
+    return true;
+  }
+
+  bool found_any = false;
+  Patchfiles::iterator pi;
+  for (pi = _from_patches.begin(); pi != _from_patches.end(); ++pi) {
+    Patchfile *patchfile = (*pi);
+    PackageVersion *from_pv = patchfile->_from_pv;
+    assert(from_pv != NULL);
+    Patchfiles this_chain;
+    if (from_pv->get_patch_chain(this_chain, start_pv)) {
+      // There's a path through this patchfile.
+      this_chain.push_back(patchfile);
+      if (!found_any || this_chain.size() < chain.size()) {
+        found_any = true;
+        chain.swap(this_chain);
+      }
+    }
+  }
+
+  // If found_any is true, we've already filled chain with the
+  // shortest path found.
+  return found_any;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchFinder::PackageVersionKey::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+P3DPatchFinder::PackageVersionKey::
+PackageVersionKey(const string &package_name,
+                  const string &platform,
+                  const string &version,
+                  const string &host_url,
+                  const FileSpec &file) :
+  _package_name(package_name),
+  _platform(platform),
+  _version(version),
+  _host_url(host_url),
+  _file(file)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchFinder::PackageVersionKey::operator <
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+bool P3DPatchFinder::PackageVersionKey::
+operator < (const PackageVersionKey &other) const {
+  if (_package_name != other._package_name) {
+    return _package_name < other._package_name;
+  }
+  if (_platform != other._platform) {
+    return _platform < other._platform;
+  }
+  if (_version != other._version) {
+    return _version < other._version;
+  }
+  if (_host_url != other._host_url) {
+    return _host_url < other._host_url;
+  }
+  return _file.compare_hash(other._file);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchFinder::PackageVersionKey::operator <
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void P3DPatchFinder::PackageVersionKey::
+output(ostream &out) const {
+  out << "(" << _package_name << ", " << _platform << ", " << _version
+      << ", " << _host_url << ", ";
+  _file.output_hash(out);
+  out << ")";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchFinder::Patchfile::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+P3DPatchFinder::Patchfile::
+Patchfile(Package *package) :
+  _package(package),
+  _from_pv(NULL),
+  _to_pv(NULL)
+{
+  _package_name = package->_package_name;
+  _platform = package->_platform;
+  _version = package->_version;
+  _host_url = package->_host_url;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchFinder::Patchfile::get_source_key
+//       Access: Public
+//  Description: Returns the key for locating the package that this
+//               patchfile can be applied to.
+////////////////////////////////////////////////////////////////////
+P3DPatchFinder::PackageVersionKey P3DPatchFinder::Patchfile::
+get_source_key() const {
+  return PackageVersionKey(_package_name, _platform, _version, _host_url, _source_file);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchFinder::Patchfile::get_target_key
+//       Access: Public
+//  Description: Returns the key for locating the package that this
+//               patchfile will generate.
+////////////////////////////////////////////////////////////////////
+P3DPatchFinder::PackageVersionKey P3DPatchFinder::Patchfile::
+get_target_key() const {
+  return PackageVersionKey(_package_name, _platform, _version, _host_url, _target_file);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchFinder::Patchfile::load_xml
+//       Access: Public
+//  Description: Reads the data structures from an xml file.
+////////////////////////////////////////////////////////////////////
+void P3DPatchFinder::Patchfile::
+load_xml(TiXmlElement *xpatch) {
+  const char *package_name_cstr = xpatch->Attribute("name");
+  if (package_name_cstr != NULL && *package_name_cstr) {
+    _package_name = package_name_cstr;
+  }
+  const char *platform_cstr = xpatch->Attribute("platform");
+  if (platform_cstr != NULL && *platform_cstr) {
+    _platform = platform_cstr;
+  }
+  const char *version_cstr = xpatch->Attribute("version");
+  if (version_cstr != NULL && *version_cstr) {
+    _version = version_cstr;
+  }
+  const char *host_url_cstr = xpatch->Attribute("host");
+  if (host_url_cstr != NULL && *host_url_cstr) {
+    _host_url = host_url_cstr;
+  }
+
+  _file.load_xml(xpatch);
+
+  TiXmlElement *xsource = xpatch->FirstChildElement("source");
+  if (xsource != NULL) {
+    _source_file.load_xml(xsource);
+  }
+  TiXmlElement *xtarget = xpatch->FirstChildElement("target");
+  if (xtarget != NULL) {
+    _target_file.load_xml(xtarget);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchFinder::Package::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+P3DPatchFinder::Package::
+Package() {
+  _current_pv = NULL;
+  _base_pv = NULL;
+  _got_base_file = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchFinder::Package::get_current_key
+//       Access: Public
+//  Description: Returns the key to locate the current version of this
+//               package.
+////////////////////////////////////////////////////////////////////
+P3DPatchFinder::PackageVersionKey P3DPatchFinder::Package::
+get_current_key() const {
+  return PackageVersionKey(_package_name, _platform, _version, _host_url, _current_file);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchFinder::Package::get_base_key
+//       Access: Public
+//  Description: Returns the key to locate the "base" or oldest
+//               version of this package.
+////////////////////////////////////////////////////////////////////
+P3DPatchFinder::PackageVersionKey P3DPatchFinder::Package::
+get_base_key() const {
+  return PackageVersionKey(_package_name, _platform, _version, _host_url, _base_file);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchFinder::Package::get_generic_key
+//       Access: Public
+//  Description: Returns the key that has the indicated hash.
+////////////////////////////////////////////////////////////////////
+P3DPatchFinder::PackageVersionKey P3DPatchFinder::Package::
+get_generic_key(const FileSpec &file) const {
+  return PackageVersionKey(_package_name, _platform, _version, _host_url, file);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchFinder::Package::read_desc_file
+//       Access: Public
+//  Description: Reads the package's desc file for the package
+//               information.  Returns true on success, false on
+//               failure.
+////////////////////////////////////////////////////////////////////
+bool P3DPatchFinder::Package::
+read_desc_file(TiXmlDocument *doc) {
+  TiXmlElement *xpackage = doc->FirstChildElement("package");
+  if (xpackage == NULL) {
+    return false;
+  }
+
+  const char *package_name_cstr = xpackage->Attribute("name");
+  if (package_name_cstr != NULL && *package_name_cstr) {
+    _package_name = package_name_cstr;
+  }
+  const char *platform_cstr = xpackage->Attribute("platform");
+  if (platform_cstr != NULL && *platform_cstr) {
+    _platform = platform_cstr;
+  }
+  const char *version_cstr = xpackage->Attribute("version");
+  if (version_cstr != NULL && *version_cstr) {
+    _version = version_cstr;
+  }
+  const char *host_url_cstr = xpackage->Attribute("host");
+  if (host_url_cstr != NULL && *host_url_cstr) {
+    _host_url = host_url_cstr;
+  }
+
+  // Get the current version.
+  TiXmlElement *xarchive = xpackage->FirstChildElement("uncompressed_archive");
+  if (xarchive != NULL) {
+    _current_file.load_xml(xarchive);
+  }
+
+  // Get the base_version--the bottom (oldest) of the patch chain.
+  xarchive = xpackage->FirstChildElement("base_version");
+  if (xarchive != NULL) {
+    _base_file.load_xml(xarchive);
+    _got_base_file = true;
+  }
+
+  _patches.clear();
+  TiXmlElement *xpatch = xpackage->FirstChildElement("patch");
+  while (xpatch != NULL) {
+    Patchfile *patchfile = new Patchfile(this);
+    patchfile->load_xml(xpatch);
+    _patches.push_back(patchfile);
+    xpatch = xpatch->NextSiblingElement("patch");
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchFinder::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+P3DPatchFinder::
+P3DPatchFinder() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchFinder::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+P3DPatchFinder::
+~P3DPatchFinder() {
+  // TODO.  Cleanup nicely.
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchFinder::get_patch_chain_to_current
+//       Access: Public
+//  Description: Loads the package defined in the indicated desc file,
+//               and constructs a patch chain from the version
+//               represented by file to the current version of this
+//               package, if possible. Returns true if successful,
+//               false otherwise.
+////////////////////////////////////////////////////////////////////
+bool P3DPatchFinder::
+get_patch_chain_to_current(Patchfiles &chain, TiXmlDocument *doc,
+                           const FileSpec &file) {
+  chain.clear();
+  Package *package = read_package_desc_file(doc);
+  if (package == NULL) {
+    return false;
+  }
+
+  build_patch_chains();
+  PackageVersion *from_pv = get_package_version(package->get_generic_key(file));
+  PackageVersion *to_pv = package->_current_pv;
+
+  if (to_pv != NULL && from_pv != NULL) {
+    return to_pv->get_patch_chain(chain, from_pv);
+  }
+
+  return false;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchFinder::read_package_desc_file
+//       Access: Public
+//  Description: Reads a desc file associated with a particular
+//               package, and adds the package to
+//               _packages.  Returns the Package object, or
+//               NULL on failure.
+////////////////////////////////////////////////////////////////////
+P3DPatchFinder::Package *P3DPatchFinder::
+read_package_desc_file(TiXmlDocument *doc) {
+  Package *package = new Package;
+  if (!package->read_desc_file(doc)) {
+    delete package;
+    return NULL;
+  }
+
+  _packages.push_back(package);
+  return package;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchFinder::build_patch_chains
+//       Access: Public
+//  Description: Builds up the chains of PackageVersions and the
+//               patchfiles that connect them.
+////////////////////////////////////////////////////////////////////
+void P3DPatchFinder::
+build_patch_chains() {
+  Packages::iterator pi;
+  for (pi = _packages.begin(); pi != _packages.end(); ++pi) {
+    Package *package = (*pi);
+    if (!package->_got_base_file) {
+      // This package doesn't have any versions yet.
+      continue;
+    }
+
+    PackageVersion *current_pv = get_package_version(package->get_current_key());
+    package->_current_pv = current_pv;
+    current_pv->_package_current = package;
+    current_pv->_print_name = package->_current_file.get_filename();
+
+    PackageVersion *base_pv = get_package_version(package->get_base_key());
+    package->_base_pv = base_pv;
+    base_pv->_package_base = package;
+    base_pv->_print_name = package->_base_file.get_filename();
+
+    Patchfiles::iterator fi;
+    for (fi = package->_patches.begin(); fi != package->_patches.end(); ++fi) {
+      record_patchfile(*fi);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchFinder::get_package_version
+//       Access: Public
+//  Description: Returns a shared PackageVersion object for the
+//               indicated key.
+////////////////////////////////////////////////////////////////////
+P3DPatchFinder::PackageVersion *P3DPatchFinder::
+get_package_version(const PackageVersionKey &key) {
+  assert(!key._package_name.empty());
+  PackageVersions::const_iterator vi = _package_versions.find(key);
+  if (vi != _package_versions.end()) {
+    return (*vi).second;
+  }
+
+  PackageVersion *pv = new PackageVersion(key);
+  bool inserted = _package_versions.insert(PackageVersions::value_type(key, pv)).second;
+  assert(inserted);
+  return pv;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPatchFinder::record_patchfile
+//       Access: Public
+//  Description: Adds the indicated patchfile to the patch chains.
+////////////////////////////////////////////////////////////////////
+void P3DPatchFinder::
+record_patchfile(Patchfile *patchfile) {
+  PackageVersion *from_pv = get_package_version(patchfile->get_source_key());
+  patchfile->_from_pv = from_pv;
+  from_pv->_to_patches.push_back(patchfile);
+
+  PackageVersion *to_pv = get_package_version(patchfile->get_target_key());
+  patchfile->_to_pv = to_pv;
+  to_pv->_from_patches.push_back(patchfile);
+  to_pv->_print_name = patchfile->_file.get_filename();
+}

+ 180 - 0
direct/src/plugin/p3dPatchFinder.h

@@ -0,0 +1,180 @@
+// Filename: p3dPatchFinder.h
+// Created by:  drose (27Sep09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef P3DPATCHFINDER_H
+#define P3DPATCHFINDER_H
+
+#include "p3d_plugin_common.h"
+#include "fileSpec.h"
+#include "get_tinyxml.h"
+#include <vector>
+#include <map>
+
+////////////////////////////////////////////////////////////////////
+//       Class : P3DPatchFinder
+// Description : This class is used to reconstruct the patch
+//               chain--the chain of patch files needed to generate a
+//               file--for downloading a package via patches, rather
+//               than downloading the entire file.
+//
+//               It is similar to PatchMaker.py, except it only reads
+//               patches, it does not generate them.
+////////////////////////////////////////////////////////////////////
+class P3DPatchFinder {
+public:
+  class Package;
+  class Patchfile;
+
+  typedef vector<Patchfile *> Patchfiles;
+
+  // This class is used to index into a map to locate PackageVersion
+  // objects, below.
+  class PackageVersionKey {
+  public:
+    PackageVersionKey(const string &package_name,
+                      const string &platform,
+                      const string &version,
+                      const string &host_url,
+                      const FileSpec &file);
+    bool operator < (const PackageVersionKey &other) const;
+    void output(ostream &out) const;
+
+  public:
+    string _package_name;
+    string _platform;
+    string _version;
+    string _host_url;
+    FileSpec _file;
+  };
+
+  // A specific version of a package.  This is not just a package's
+  // "version" string; it also corresponds to the particular patch
+  // version, which increments independently of the "version".
+  class PackageVersion {
+  public:
+    PackageVersion(const PackageVersionKey &key);
+
+    bool get_patch_chain(Patchfiles &chain, PackageVersion *start_pv);
+
+  public:
+    string _package_name;
+    string _platform;
+    string _version;
+    string _host_url;
+    FileSpec _file;
+    string _print_name;
+
+    // The Package object that produces this version if this is the
+    // current form or the base form, respectively.
+    Package *_package_current;
+    Package *_package_base;
+
+    // A list of patchfiles that can produce this version.
+    Patchfiles _from_patches;
+
+    // A list of patchfiles that can start from this version.
+    Patchfiles _to_patches;
+  };
+
+  // A single patchfile for a package.
+  class Patchfile {
+  public:
+    Patchfile(Package *package);
+
+    PackageVersionKey get_source_key() const;
+    PackageVersionKey get_target_key() const;
+    void load_xml(TiXmlElement *xpatch);
+
+  public:
+    Package *_package;
+    string _package_name;
+    string _platform;
+    string _version;
+    string _host_url;
+
+    // The patchfile itself
+    FileSpec _file;
+
+    // The package file that the patch is applied to
+    FileSpec _source_file;
+
+    // The package file that the patch generates
+    FileSpec _target_file;
+
+    // The PackageVersion corresponding to our source_file
+    PackageVersion *_from_pv;
+
+    // The PackageVersion corresponding to our target_file
+    PackageVersion *_to_pv;
+  };
+
+  // This is a particular package.  This contains all of the
+  // information extracted from the package's desc file.
+  class Package {
+  public:
+    Package();
+
+    PackageVersionKey get_current_key() const;
+    PackageVersionKey get_base_key() const;
+    PackageVersionKey get_generic_key(const FileSpec &file) const;
+
+    bool read_desc_file(TiXmlDocument *doc);
+
+  public:
+    string _package_name;
+    string _platform;
+    string _version;
+    string _host_url;
+
+    PackageVersion *_current_pv;
+    PackageVersion *_base_pv;
+
+    FileSpec _current_file;
+    FileSpec _base_file;
+    bool _got_base_file;
+
+    Patchfiles _patches;
+  };
+
+public:
+  P3DPatchFinder();
+  ~P3DPatchFinder();
+
+  bool get_patch_chain_to_current(Patchfiles &chain, TiXmlDocument *doc,
+                                  const FileSpec &file);
+
+  Package *read_package_desc_file(TiXmlDocument *doc);
+  void build_patch_chains();
+  PackageVersion *get_package_version(const PackageVersionKey &key);
+
+private:
+  void record_patchfile(Patchfile *patchfile);
+
+private:
+  typedef map<PackageVersionKey, PackageVersion *> PackageVersions;
+  PackageVersions _package_versions;
+
+  typedef vector<Package *> Packages;
+  Packages _packages;
+};
+
+#include "p3dPatchFinder.I"
+
+inline ostream &operator << (ostream &out, const P3DPatchFinder::PackageVersionKey &key) {
+  key.output(out);
+  return out;
+}
+
+#endif
+

+ 1 - 0
direct/src/plugin/p3d_plugin_composite1.cxx

@@ -17,6 +17,7 @@
 #include "p3dObject.cxx"
 #include "p3dOsxSplashWindow.cxx"
 #include "p3dPackage.cxx"
+#include "p3dPatchFinder.cxx"
 #include "p3dPythonObject.cxx"
 #include "p3dReferenceCount.cxx"
 #include "p3dSession.cxx"