steps toward C++ patching

This commit is contained in:
David Rose 2009-09-27 23:46:33 +00:00
parent 083e7cbe78
commit 81c2514bc4
13 changed files with 883 additions and 76 deletions

View File

@ -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()

View File

@ -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)

View File

@ -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 \

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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) {

View File

@ -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";
}
}
}
}
////////////////////////////////////////////////////////////////////

View File

@ -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;

View File

@ -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."
//
////////////////////////////////////////////////////////////////////

View File

@ -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();
}

View File

@ -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

View File

@ -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"