mirror support (python level)

This commit is contained in:
David Rose 2009-09-22 20:29:45 +00:00
parent 1ac436a01f
commit be34e2e587
8 changed files with 174 additions and 77 deletions

View File

@ -52,7 +52,7 @@ class DWBPackageInstaller(DirectWaitBar, PackageInstaller):
downloadStarted() and downloadFinished() to indicate the start
of a new package. """
if self['updateText']:
self['text'] = 'Installing %s' % (package.displayName)
self['text'] = 'Installing %s' % (package.getFormattedName())
self.show()
def packageProgress(self, package, progress):

View File

@ -25,7 +25,8 @@ class HostInfo:
# contents file is read.
self.descriptiveName = None
# A list of known mirrors for this host.
# A list of known mirrors for this host, all URL's guaranteed
# to end with a slash.
self.mirrors = []
# A map of keyword -> altHost URL's. An altHost is different
@ -159,8 +160,11 @@ class HostInfo:
xmirror = xhost.FirstChildElement('mirror')
while xmirror:
url = xmirror.Attribute('url')
if url and url not in self.mirrors:
self.mirrors.append(url)
if url:
if url[-1] != '/':
url += '/'
if url not in self.mirrors:
self.mirrors.append(url)
xmirror = xmirror.NextSiblingElement('mirror')
xalthost = xhost.FirstChildElement('alt_host')

View File

@ -3,6 +3,7 @@ from direct.p3d.FileSpec import FileSpec
from direct.showbase import VFSImporter
import os
import sys
import random
class PackageInfo:
@ -52,12 +53,12 @@ class PackageInfo:
# These will be filled in by HostInfo when the package is read
# from contents.xml.
self.descFileUrl = None
self.descFile = None
self.importDescFile = None
# These are filled in when the desc file is successfully read.
self.hasDescFile = False
self.patchVersion = None
self.displayName = None
self.uncompressedArchive = None
self.compressedArchive = None
@ -90,14 +91,32 @@ class PackageInfo:
return size
def getFormattedName(self):
""" Returns the name of this package, for output to the user.
This will be the "public" name of the package, as formatted
for user consumption; it will include capital letters and
spaces where appropriate. """
if self.displayName:
name = self.displayName
else:
name = self.packageName
if self.packageVersion:
name += ' %s' % (self.packageVersion)
if self.patchVersion:
name += ' rev %s' % (self.patchVersion)
return name
def setupFilenames(self):
""" This is called by the HostInfo when the package is read
from contents.xml, to set up the internal filenames and such
that rely on some of the information from contents.xml. """
self.descFileUrl = self.host.hostUrlPrefix + self.descFile.filename
basename = self.descFile.filename.rsplit('/', 1)[-1]
dirname, basename = self.descFile.filename.rsplit('/', 1)
self.descFileDirname = dirname
self.descFileBasename = basename
def checkStatus(self):
@ -136,28 +155,21 @@ class PackageInfo:
# We've already got one.
return True
url = URLSpec(self.descFileUrl)
print "Downloading desc file %s" % (url)
rf = Ramfile()
channel = http.getDocument(url)
if not channel.downloadToRam(rf):
print "Unable to download %s" % (url)
self.http = http
if not self.__downloadFile(
None, self.descFile,
urlbase = self.descFile.filename,
filename = self.descFileBasename):
# Couldn't download the desc file.
return False
filename = Filename(self.packageDir, self.descFileBasename)
filename.makeDir()
filename.unlink()
f = open(filename.toOsSpecific(), 'wb')
f.write(rf.getData())
f.close()
# Now that we've written the desc file, make it read-only.
os.chmod(filename.toOsSpecific(), 0444)
try:
self.readDescFile()
except ValueError:
if not self.readDescFile():
# Weird, it passed the hash check, but we still can't read
# it.
print "Failure reading %s" % (filename)
return False
@ -165,24 +177,28 @@ class PackageInfo:
def readDescFile(self):
""" Reads the desc xml file for this particular package.
Presumably this has already been downloaded and installed. """
Returns true on success, false on failure. """
if self.hasDescFile:
# No need to read it again.
return
return True
filename = Filename(self.packageDir, self.descFileBasename)
doc = TiXmlDocument(filename.toOsSpecific())
if not doc.LoadFile():
raise ValueError
return False
xpackage = doc.FirstChildElement('package')
if not xpackage:
raise ValueError
return False
try:
self.patchVersion = int(xpackage.Attribute('patch_version') or '')
except ValueError:
self.patchVersion = None
self.displayName = None
xconfig = xpackage.FirstChildElement('config')
if xconfig:
# The name for display to an English-speaking user.
@ -216,9 +232,9 @@ class PackageInfo:
if self.__checkArchiveStatus():
# It's all good.
self.hasPackage = True
return
return True
# We need to download an update.
# Now set up to download the update.
self.hasPackage = False
# Now determine what we will need to download, and build a
@ -238,7 +254,7 @@ class PackageInfo:
self.uncompressedArchive.actualFile = None
if self.uncompressedArchive.quickVerify(self.packageDir):
self.installPlans = [planA]
return
return True
# Maybe the compressed archive file is good.
if self.compressedArchive.quickVerify(self.packageDir):
@ -246,11 +262,11 @@ class PackageInfo:
step = self.InstallStep(self.__uncompressArchive, uncompressSize, self.uncompressFactor)
planA = [step] + planA
self.installPlans = [planA]
return
return True
# Maybe we can download one or more patches. We'll come back
# to that in a minute as plan A. For now, construct on plan
# B, which will be to download the whole archive.
# to that in a minute as plan A. For now, construct plan B,
# which will be to download the whole archive.
planB = planA[:]
uncompressSize = self.uncompressedArchive.size
@ -285,6 +301,8 @@ class PackageInfo:
# plan B as the only plan.
self.installPlans = [planB]
return True
def __scanDirectoryRecursively(self, dirname):
""" Generates a list of Filename objects: all of the files
(not directories) within and below the indicated dirname. """
@ -431,39 +449,66 @@ class PackageInfo:
patchMaker.cleanup()
return plan
def __downloadFile(self, step, fileSpec):
def __downloadFile(self, step, fileSpec, urlbase = None, filename = None):
""" Downloads the indicated file from the host into
packageDir. Returns true on success, false on failure. """
url = self.descFileUrl.rsplit('/', 1)[0]
url += '/' + fileSpec.filename
url = DocumentSpec(url)
print "Downloading package file %s" % (url)
targetPathname = Filename(self.packageDir, fileSpec.filename)
targetPathname.setBinary()
channel = self.http.makeChannel(False)
# TODO: check for a previous partial download, and resume it.
targetPathname.unlink()
channel.beginGetDocument(url)
channel.downloadToFile(targetPathname)
while channel.run():
step.bytesDone = channel.getBytesDownloaded()
self.__updateStepProgress(step)
Thread.considerYield()
step.bytesDone = channel.getBytesDownloaded()
self.__updateStepProgress(step)
if not channel.isValid():
print "Failed to download %s" % (url)
return False
if not urlbase:
urlbase = self.descFileDirname + '/' + fileSpec.filename
if not fileSpec.fullVerify(self.packageDir):
print "after downloading, %s incorrect" % (
fileSpec.filename)
return False
return True
# Build up a list of URL's to try downloading from.
tryUrls = []
if self.host.mirrors:
# Choose a mirror at random first, then a different
# mirror.
mirrors = self.host.mirrors[:]
for i in range(2):
mirror = random.choice(mirrors)
mirrors.remove(mirror)
tryUrls.append(mirror + urlbase)
if not mirrors:
break
# After trying two mirrors and failing (or if there are no
# mirrors), go get it from the original host.
url = self.host.hostUrlPrefix + urlbase
tryUrls.append(url)
for url in tryUrls:
url = DocumentSpec(url)
print "Downloading package file %s" % (url)
if not filename:
filename = fileSpec.filename
targetPathname = Filename(self.packageDir, filename)
targetPathname.setBinary()
channel = self.http.makeChannel(False)
# TODO: check for a previous partial download, and resume it.
targetPathname.makeDir()
targetPathname.unlink()
channel.beginGetDocument(url)
channel.downloadToFile(targetPathname)
while channel.run():
if step:
step.bytesDone = channel.getBytesDownloaded()
self.__updateStepProgress(step)
Thread.considerYield()
if step:
step.bytesDone = channel.getBytesDownloaded()
self.__updateStepProgress(step)
if not channel.isValid():
print "Failed to download %s" % (url)
continue
if not fileSpec.fullVerify(self.packageDir, pathname = targetPathname):
print "After downloading, %s incorrect" % (url)
continue
return True
# All mirrors failed.
return False
def __applyPatch(self, step, patchfile):
""" Applies the indicated patching in-place to the current

View File

@ -247,10 +247,11 @@ class PatchMaker:
""" This is a particular package. This contains all of the
information needed to reconstruct the package's desc file. """
def __init__(self, packageDesc, patchMaker):
def __init__(self, packageDesc, patchMaker, xpackage):
self.packageDir = Filename(patchMaker.installDir, packageDesc.getDirname())
self.packageDesc = packageDesc
self.patchMaker = patchMaker
self.contentsDocPackage = xpackage
self.patchVersion = 1
self.currentPv = None
self.basePv = None
@ -384,9 +385,9 @@ class PatchMaker:
""" Rewrites the desc file with the new patch
information. """
if not self.anyChanges:
# No need to rewrite.
return
## if not self.anyChanges:
## # No need to rewrite.
## return
xpackage = self.doc.FirstChildElement('package')
if not xpackage:
@ -418,6 +419,13 @@ class PatchMaker:
xpackage.InsertEndChild(xpatch)
self.doc.SaveFile()
# Now that we've rewritten the xml file, we have to change
# the contents.xml file that references it to indicate the
# new file hash.
fileSpec = FileSpec()
fileSpec.fromFile(self.patchMaker.installDir, self.packageDesc)
fileSpec.storeXml(self.contentsDocPackage)
def __init__(self, installDir):
@ -440,6 +448,7 @@ class PatchMaker:
else:
self.processSomePackages(packageNames)
self.writeContentsFile()
self.cleanup()
return True
@ -481,14 +490,25 @@ class PatchMaker:
filename = xpackage.Attribute('filename')
if filename and not solo:
filename = Filename(filename)
package = self.Package(filename, self)
package = self.Package(filename, self, xpackage)
package.readDescFile()
self.packages.append(package)
xpackage = xpackage.NextSiblingElement('package')
self.contentsDoc = doc
return True
def writeContentsFile(self):
""" Writes the contents.xml file at the end of processing. """
# We trust each of the packages to have already updated their
# element within the contents.xml document, so all we have to
# do is write out the document.
self.contentsDoc.SaveFile()
def getPackageVersion(self, key):
""" Returns a shared PackageVersion object for the indicated
key. """

View File

@ -1825,10 +1825,7 @@ start_next_download() {
if (!package->get_ready()) {
// This package is ready to download. Begin.
string name = package->get_package_display_name();
if (name.empty()) {
name = package->get_package_name();
}
string name = package->get_formatted_name();
_panda_script_object->set_string_property("downloadPackageName", package->get_package_name());
_panda_script_object->set_string_property("downloadPackageDisplayName", name);
_panda_script_object->set_int_property("downloadPackageNumber", _download_package_index + 1);

View File

@ -113,10 +113,8 @@ get_package_version() const {
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::get_package_display_name
// Access: Public
// Description: Returns the name of this package, for output to the
// user. This will be the "public" name of the package,
// as formatted for user consumption; it will include
// capital letters and spaces where appropriate.
// Description: Returns the display_name name of this package, as set
// in the desc file.
////////////////////////////////////////////////////////////////////
inline const string &P3DPackage::
get_package_display_name() const {

View File

@ -51,6 +51,7 @@ P3DPackage(P3DHost *host, const string &package_name,
if (!_package_version.empty()) {
_package_fullname += string(".") + _package_version;
}
_patch_version = 0;
// This is set true if the package is a "solo", i.e. a single
// file, instead of an xml file and a multifile to unpack.
@ -130,6 +131,34 @@ activate_download() {
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::get_formatted_name
// Access: Public
// Description: Returns the name of this package, for output to the
// user. This will be the "public" name of the package,
// as formatted for user consumption; it will include
// capital letters and spaces where appropriate.
////////////////////////////////////////////////////////////////////
string P3DPackage::
get_formatted_name() const {
ostringstream strm;
if (!_package_display_name.empty()) {
strm << _package_display_name;
} else {
strm << _package_name;
if (!_package_version.empty()) {
strm << " " << _package_version;
}
}
if (_patch_version != 0) {
strm << " rev " << _patch_version;
}
return strm.str();
}
////////////////////////////////////////////////////////////////////
// Function: P3DPackage::add_instance
// Access: Public
@ -442,6 +471,8 @@ got_desc_file(TiXmlDocument *doc, bool freshly_downloaded) {
TiXmlElement *xcompressed_archive = NULL;
if (xpackage != NULL) {
xpackage->Attribute("patch_version", &_patch_version);
xuncompressed_archive = xpackage->FirstChildElement("uncompressed_archive");
xcompressed_archive = xpackage->FirstChildElement("compressed_archive");

View File

@ -57,6 +57,7 @@ public:
inline const string &get_package_name() const;
inline const string &get_package_version() const;
inline const string &get_package_display_name() const;
string get_formatted_name() const;
inline const TiXmlElement *get_xconfig() const;
inline const string &get_desc_file_pathname() const;
@ -118,6 +119,7 @@ private:
string _package_name;
string _package_version;
string _package_platform;
int _patch_version;
string _alt_host;
bool _package_solo;
string _package_display_name;