diff --git a/direct/src/p3d/PackageInfo.py b/direct/src/p3d/PackageInfo.py index c3aa09e26c..8eca0c58f9 100644 --- a/direct/src/p3d/PackageInfo.py +++ b/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() diff --git a/direct/src/p3d/PatchMaker.py b/direct/src/p3d/PatchMaker.py index b131df9d4b..acbb5f41f5 100644 --- a/direct/src/p3d/PatchMaker.py +++ b/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) diff --git a/direct/src/plugin/Sources.pp b/direct/src/plugin/Sources.pp index b71be6273d..bf96fdb097 100644 --- a/direct/src/plugin/Sources.pp +++ b/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 \ diff --git a/direct/src/plugin/fileSpec.I b/direct/src/plugin/fileSpec.I index 9096839d21..f511ff8ac7 100755 --- a/direct/src/plugin/fileSpec.I +++ b/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 diff --git a/direct/src/plugin/fileSpec.cxx b/direct/src/plugin/fileSpec.cxx index 7690428246..6683f0111c 100755 --- a/direct/src/plugin/fileSpec.cxx +++ b/direct/src/plugin/fileSpec.cxx @@ -17,9 +17,11 @@ #include #include +#include #include #include +#include #ifdef _WIN32 #include @@ -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 ©) : _got_hash(copy._got_hash) { memcpy(_hash, copy._hash, sizeof(_hash)); + _actual_file = NULL; } //////////////////////////////////////////////////////////////////// @@ -75,6 +79,18 @@ operator = (const FileSpec ©) { _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 diff --git a/direct/src/plugin/fileSpec.h b/direct/src/plugin/fileSpec.h index e16569d23b..45629e9a1b 100755 --- a/direct/src/plugin/fileSpec.h +++ b/direct/src/plugin/fileSpec.h @@ -31,6 +31,8 @@ public: FileSpec(); FileSpec(const FileSpec ©); void operator = (const FileSpec ©); + ~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" diff --git a/direct/src/plugin/p3dInstance.cxx b/direct/src/plugin/p3dInstance.cxx index d09f714dcc..27e1700676 100644 --- a/direct/src/plugin/p3dInstance.cxx +++ b/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) { diff --git a/direct/src/plugin/p3dPackage.cxx b/direct/src/plugin/p3dPackage.cxx index 944b6cc12b..fa7c02aaee 100755 --- a/direct/src/plugin/p3dPackage.cxx +++ b/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"; + } + } + } } //////////////////////////////////////////////////////////////////// diff --git a/direct/src/plugin/p3dPackage.h b/direct/src/plugin/p3dPackage.h index ab32fff642..94f3ce2d10 100755 --- a/direct/src/plugin/p3dPackage.h +++ b/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; diff --git a/direct/src/plugin/p3dPatchFinder.I b/direct/src/plugin/p3dPatchFinder.I new file mode 100644 index 0000000000..6e9264d58e --- /dev/null +++ b/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." +// +//////////////////////////////////////////////////////////////////// + diff --git a/direct/src/plugin/p3dPatchFinder.cxx b/direct/src/plugin/p3dPatchFinder.cxx new file mode 100644 index 0000000000..1dd030fdf2 --- /dev/null +++ b/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(); +} diff --git a/direct/src/plugin/p3dPatchFinder.h b/direct/src/plugin/p3dPatchFinder.h new file mode 100644 index 0000000000..b82da4de7b --- /dev/null +++ b/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 +#include + +//////////////////////////////////////////////////////////////////// +// 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 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 PackageVersions; + PackageVersions _package_versions; + + typedef vector Packages; + Packages _packages; +}; + +#include "p3dPatchFinder.I" + +inline ostream &operator << (ostream &out, const P3DPatchFinder::PackageVersionKey &key) { + key.output(out); + return out; +} + +#endif + diff --git a/direct/src/plugin/p3d_plugin_composite1.cxx b/direct/src/plugin/p3d_plugin_composite1.cxx index e03e738dbd..0aa11a4b21 100644 --- a/direct/src/plugin/p3d_plugin_composite1.cxx +++ b/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"