David Rose 16 years ago
parent
commit
e434ca0d57

+ 76 - 4
direct/src/p3d/AppRunner.py

@@ -113,6 +113,12 @@ class AppRunner(DirectObject):
         # hosts we have imported packages from.
         # hosts we have imported packages from.
         self.hosts = {}
         self.hosts = {}
 
 
+        # The altHost string that is in effect from the HTML tokens,
+        # if any, and the dictionary of URL remapping: orig host url
+        # -> alt host url.
+        self.altHost = None
+        self.altHostMap = {}
+
         # Application code can assign a callable object here; if so,
         # Application code can assign a callable object here; if so,
         # it will be invoked when an uncaught exception propagates to
         # it will be invoked when an uncaught exception propagates to
         # the top of the TaskMgr.run() loop.
         # the top of the TaskMgr.run() loop.
@@ -213,7 +219,7 @@ class AppRunner(DirectObject):
         finished; see the PackageInstaller class if you want this to
         finished; see the PackageInstaller class if you want this to
         happen asynchronously instead. """
         happen asynchronously instead. """
 
 
-        host = self.getHost(hostUrl)
+        host = self.getHostWithAlt(hostUrl)
         if not host.downloadContentsFile(self.http):
         if not host.downloadContentsFile(self.http):
             return False
             return False
 
 
@@ -235,10 +241,47 @@ class AppRunner(DirectObject):
 
 
         print "Package %s %s installed." % (packageName, version)
         print "Package %s %s installed." % (packageName, version)
 
 
+    def getHostWithAlt(self, hostUrl):
+        """ Returns a suitable HostInfo object for downloading
+        contents from the indicated URL.  This is almost always the
+        same thing as getHost(), except in the rare case when we have
+        an alt_host specified in the HTML tokens; in this case, we may
+        actually want to download the contents from a different URL
+        than the one given, for instance to download a version in
+        testing. """
+
+        altUrl = self.altHostMap.get(hostUrl, None)
+        if altUrl:
+            # We got an alternate host.  Use it.
+            return self.getHost(altUrl)
+
+        # We didn't get an aternate host, use the original.
+        host = self.getHost(hostUrl)
+
+        # But we might need to consult the host itself to see if *it*
+        # recommends an altHost.
+        if self.altHost:
+            # This means forcing the host to download its contents
+            # file on the spot, a blocking operation.  This is a
+            # little unfortunate, but since alt_host is so rarely
+            # used, probably not really a problem.
+            host.downloadContentsFile(self.http)
+            altUrl = host.altHosts.get(self.altHost, None)
+            if altUrl:
+                return self.getHost(altUrl)
+
+        # No shenanigans, just return the requested host.
+        return host
+        
     def getHost(self, hostUrl):
     def getHost(self, hostUrl):
         """ Returns a new HostInfo object corresponding to the
         """ Returns a new HostInfo object corresponding to the
         indicated host URL.  If we have already seen this URL
         indicated host URL.  If we have already seen this URL
-        previously, returns the same object. """
+        previously, returns the same object.
+
+        This returns the literal referenced host.  To return the
+        mapped host, which is the one we should actually download
+        from, see getHostWithAlt().  """
+
 
 
         if hostUrl is None:
         if hostUrl is None:
             hostUrl = PandaSystem.getPackageHostUrl()
             hostUrl = PandaSystem.getPackageHostUrl()
@@ -261,7 +304,7 @@ class AppRunner(DirectObject):
 
 
         # It's stale, get a new one.
         # It's stale, get a new one.
         url = URLSpec(host.hostUrlPrefix + fileSpec.filename)
         url = URLSpec(host.hostUrlPrefix + fileSpec.filename)
-        print "Downloading %s" % (url)
+        print "Freshening %s" % (url)
         doc = self.http.getDocument(url)
         doc = self.http.getDocument(url)
         if not doc.isValid():
         if not doc.isValid():
             return False
             return False
@@ -343,7 +386,6 @@ class AppRunner(DirectObject):
         # Now set up Python to import this stuff.
         # Now set up Python to import this stuff.
         VFSImporter.register()
         VFSImporter.register()
         sys.path.append(self.multifileRoot)
         sys.path.append(self.multifileRoot)
-        print "sys.path is: %s" % (sys.path)
 
 
         # Put our root directory on the model-path, too.
         # Put our root directory on the model-path, too.
         getModelPath().appendDirectory(self.multifileRoot)
         getModelPath().appendDirectory(self.multifileRoot)
@@ -490,6 +532,9 @@ class AppRunner(DirectObject):
         # aren't instance-ready.
         # aren't instance-ready.
         sys.argv = argv
         sys.argv = argv
 
 
+        # That means we now know the altHost in effect.
+        self.altHost = self.tokenDict.get('alt_host', None)
+
         # Tell the browser that Python is up and running, and ready to
         # Tell the browser that Python is up and running, and ready to
         # respond to queries.
         # respond to queries.
         self.notifyRequest('onpythonload')
         self.notifyRequest('onpythonload')
@@ -527,6 +572,11 @@ class AppRunner(DirectObject):
             if allowPythonDev:
             if allowPythonDev:
                 self.allowPythonDev = int(allowPythonDev)
                 self.allowPythonDev = int(allowPythonDev)
 
 
+            xhost = self.p3dConfig.FirstChildElement('host')
+            while xhost:
+                self.__readHostXml(xhost)
+                xhost = xhost.NextSiblingElement('host')
+
         # The interactiveConsole flag can only be set true if the
         # The interactiveConsole flag can only be set true if the
         # application has allow_python_dev set.
         # application has allow_python_dev set.
         if not self.allowPythonDev and interactiveConsole:
         if not self.allowPythonDev and interactiveConsole:
@@ -550,6 +600,27 @@ class AppRunner(DirectObject):
         # Send this call to the main thread; don't call it directly.
         # Send this call to the main thread; don't call it directly.
         messenger.send('AppRunner_startIfReady', taskChain = 'default')
         messenger.send('AppRunner_startIfReady', taskChain = 'default')
 
 
+    def __readHostXml(self, xhost):
+        """ Reads the data in the indicated <host> entry. """
+
+        url = xhost.Attribute('url')
+        host = self.getHost(url)
+        host.readHostXml(xhost)
+
+        # Scan for a matching <alt_host>.  If found, it means we
+        # should use the alternate URL instead of the original URL.
+        if self.altHost:
+            xalthost = xhost.FirstChildElement('alt_host')
+            while xalthost:
+                keyword = xalthost.Attribute('keyword')
+                if keyword == self.altHost:
+                    origUrl = xhost.Attribute('url')
+                    newUrl = xalthost.Attribute('url')
+                    self.altHostMap[origUrl] = newUrl
+                    break
+                
+                xalthost = xalthost.NextSiblingElement('alt_host')
+    
     def loadMultifilePrcFiles(self, mf, root):
     def loadMultifilePrcFiles(self, mf, root):
         """ Loads any prc files in the root of the indicated
         """ Loads any prc files in the root of the indicated
         Multifile, which is presumbed to have been mounted already
         Multifile, which is presumbed to have been mounted already
@@ -765,6 +836,7 @@ def dummyAppRunner(tokens = [], argv = None):
     if argv is None:
     if argv is None:
         argv = sys.argv
         argv = sys.argv
     appRunner.argv = argv
     appRunner.argv = argv
+    appRunner.altHost = appRunner.tokenDict.get('alt_host', None)
 
 
     appRunner.p3dInfo = None
     appRunner.p3dInfo = None
     appRunner.p3dPackage = None
     appRunner.p3dPackage = None

+ 9 - 7
direct/src/p3d/FileSpec.py

@@ -119,12 +119,7 @@ class FileSpec:
         # The hash is OK after all.  Change the file's timestamp back
         # The hash is OK after all.  Change the file's timestamp back
         # to what we expect it to be, so we can quick-verify it
         # to what we expect it to be, so we can quick-verify it
         # successfully next time.
         # successfully next time.
-
-        # On Windows, we have to change the file to read-write before
-        # we can successfully update its timestamp.
-        os.chmod(pathname.toOsSpecific(), 0755)
-        os.utime(pathname.toOsSpecific(), (st.st_atime, self.timestamp))
-        os.chmod(pathname.toOsSpecific(), 0555)
+        self.__updateTimestamp(pathname, st)
 
 
         return True
         return True
         
         
@@ -162,10 +157,17 @@ class FileSpec:
         # to what we expect it to be, so we can quick-verify it
         # to what we expect it to be, so we can quick-verify it
         # successfully next time.
         # successfully next time.
         if st.st_mtime != self.timestamp:
         if st.st_mtime != self.timestamp:
-            os.utime(pathname.toOsSpecific(), (st.st_atime, self.timestamp))
+            self.__updateTimestamp(pathname, st)
 
 
         return True
         return True
 
 
+    def __updateTimestamp(self, pathname, st):
+        # On Windows, we have to change the file to read-write before
+        # we can successfully update its timestamp.
+        os.chmod(pathname.toOsSpecific(), 0755)
+        os.utime(pathname.toOsSpecific(), (st.st_atime, self.timestamp))
+        os.chmod(pathname.toOsSpecific(), 0555)
+
     def checkHash(self, packageDir, pathname, st):
     def checkHash(self, packageDir, pathname, st):
         """ Returns true if the file has the expected md5 hash, false
         """ Returns true if the file has the expected md5 hash, false
         otherwise.  As a side effect, stores a FileSpec corresponding
         otherwise.  As a side effect, stores a FileSpec corresponding

+ 60 - 4
direct/src/p3d/HostInfo.py

@@ -23,7 +23,16 @@ class HostInfo:
 
 
         # descriptiveName will be filled in later, when the
         # descriptiveName will be filled in later, when the
         # contents file is read.
         # contents file is read.
-        self.descriptiveName = ''
+        self.descriptiveName = None
+
+        # A list of known mirrors for this host.
+        self.mirrors = []
+
+        # A map of keyword -> altHost URL's.  An altHost is different
+        # than a mirror; an altHost is an alternate URL to download a
+        # different (e.g. testing) version of this host's contents.
+        # It is rarely used.
+        self.altHosts = {}
 
 
         # This is a dictionary of packages by (name, version).  It
         # This is a dictionary of packages by (name, version).  It
         # will be filled in when the contents file is read.
         # will be filled in when the contents file is read.
@@ -52,7 +61,7 @@ class HostInfo:
         request = DocumentSpec(url)
         request = DocumentSpec(url)
         request.setCacheControl(DocumentSpec.CCNoCache)
         request.setCacheControl(DocumentSpec.CCNoCache)
 
 
-        print "Downloading %s" % (request)
+        print "Downloading contents file %s" % (request)
 
 
         rf = Ramfile()
         rf = Ramfile()
         channel = http.makeChannel(False)
         channel = http.makeChannel(False)
@@ -76,7 +85,8 @@ class HostInfo:
 
 
     def readContentsFile(self):
     def readContentsFile(self):
         """ Reads the contents.xml file for this particular host.
         """ Reads the contents.xml file for this particular host.
-        Presumably this has already been downloaded and installed. """
+        Raises ValueError if the contents file is not already on disk
+        or is unreadable. """
 
 
         if self.hasContentsFile:
         if self.hasContentsFile:
             # No need to read it again.
             # No need to read it again.
@@ -92,7 +102,8 @@ class HostInfo:
         if not xcontents:
         if not xcontents:
             raise ValueError
             raise ValueError
 
 
-        self.descriptiveName = xcontents.Attribute('descriptive_name')
+        # Look for our own entry in the hosts table.
+        self.__findHostXml(xcontents)
 
 
         # Get the list of packages available for download and/or import.
         # Get the list of packages available for download and/or import.
         xpackage = xcontents.FirstChildElement('package')
         xpackage = xcontents.FirstChildElement('package')
@@ -115,6 +126,51 @@ class HostInfo:
 
 
         self.hasContentsFile = True
         self.hasContentsFile = True
 
 
+    def __findHostXml(self, xcontents):
+        """ Looks for the <host> or <alt_host> entry in the
+        contents.xml that corresponds to the URL that we actually
+        downloaded from. """
+        
+        xhost = xcontents.FirstChildElement('host')
+        while xhost:
+            url = xhost.Attribute('url')
+            if url == self.hostUrl:
+                self.readHostXml(xhost)
+                return
+
+            xalthost = xhost.FirstChildElement('alt_host')
+            while xalthost:
+                url = xalthost.Attribute('url')
+                if url == self.hostUrl:
+                    self.readHostXml(xalthost)
+                    return
+                xalthost = xalthost.NextSiblingElement('alt_host')
+            
+            xhost = xhost.NextSiblingElement('host')
+
+    def readHostXml(self, xhost):
+        """ Reads a <host> or <alt_host> entry and applies the data to
+        this object. """
+
+        descriptiveName = xhost.Attribute('descriptive_name')
+        if descriptiveName and not self.descriptiveName:
+            self.descriptiveName = descriptiveName
+            
+        xmirror = xhost.FirstChildElement('mirror')
+        while xmirror:
+            url = xmirror.Attribute('url')
+            if url and url not in self.mirrors:
+                self.mirrors.append(url)
+            xmirror = xmirror.NextSiblingElement('mirror')
+
+        xalthost = xhost.FirstChildElement('alt_host')
+        while xalthost:
+            keyword = xalthost.Attribute('keyword')
+            url = xalthost.Attribute('url')
+            if url and keyword:
+                self.altHosts[keyword] = url
+            xalthost = xalthost.NextSiblingElement('alt_host')
+
     def __makePackage(self, name, platform, version):
     def __makePackage(self, name, platform, version):
         """ Creates a new PackageInfo entry for the given name,
         """ Creates a new PackageInfo entry for the given name,
         version, and platform.  If there is already a matching
         version, and platform.  If there is already a matching

+ 11 - 5
direct/src/p3d/PackageInfo.py

@@ -111,6 +111,12 @@ class PackageInfo:
             filename = Filename(self.packageDir, self.descFileBasename)
             filename = Filename(self.packageDir, self.descFileBasename)
             if self.descFile.quickVerify(self.packageDir, pathname = filename):
             if self.descFile.quickVerify(self.packageDir, pathname = filename):
                 self.readDescFile()
                 self.readDescFile()
+                if self.hasDescFile:
+                    # Successfully read.  We don't need to call
+                    # checkArchiveStatus again, since readDescFile()
+                    # has just done it.
+                    self.hasPackage = True
+                    return True
 
 
         if self.hasDescFile:
         if self.hasDescFile:
             if self.__checkArchiveStatus():
             if self.__checkArchiveStatus():
@@ -131,7 +137,7 @@ class PackageInfo:
             return True
             return True
 
 
         url = URLSpec(self.descFileUrl)
         url = URLSpec(self.descFileUrl)
-        print "Downloading %s" % (url)
+        print "Downloading desc file %s" % (url)
 
 
         rf = Ramfile()
         rf = Ramfile()
         channel = http.getDocument(url)
         channel = http.getDocument(url)
@@ -336,9 +342,9 @@ class PackageInfo:
                     allExtractsOk = False
                     allExtractsOk = False
                     break
                     break
 
 
-        if allExtractsOk:
-            print "All %s extracts of %s seem good." % (
-                len(self.extracts), self.packageName)
+##         if allExtractsOk:
+##             print "All %s extracts of %s seem good." % (
+##                 len(self.extracts), self.packageName)
 
 
         return allExtractsOk
         return allExtractsOk
 
 
@@ -432,7 +438,7 @@ class PackageInfo:
         url = self.descFileUrl.rsplit('/', 1)[0]
         url = self.descFileUrl.rsplit('/', 1)[0]
         url += '/' + fileSpec.filename
         url += '/' + fileSpec.filename
         url = DocumentSpec(url)
         url = DocumentSpec(url)
-        print "Downloading %s" % (url)
+        print "Downloading package file %s" % (url)
 
 
         targetPathname = Filename(self.packageDir, fileSpec.filename)
         targetPathname = Filename(self.packageDir, fileSpec.filename)
         targetPathname.setBinary()
         targetPathname.setBinary()

+ 2 - 2
direct/src/p3d/PackageInstaller.py

@@ -223,7 +223,7 @@ class PackageInstaller(DirectObject):
         if self.state != self.S_initial:
         if self.state != self.S_initial:
             raise ValueError, 'addPackage called after donePackages'
             raise ValueError, 'addPackage called after donePackages'
 
 
-        host = self.appRunner.getHost(hostUrl)
+        host = self.appRunner.getHostWithAlt(hostUrl)
         pp = self.PendingPackage(packageName, version, host)
         pp = self.PendingPackage(packageName, version, host)
 
 
         self.packageLock.acquire()
         self.packageLock.acquire()
@@ -374,7 +374,7 @@ class PackageInstaller(DirectObject):
     def __packageStarted(self, pp):
     def __packageStarted(self, pp):
         """ This method is called when a single package is beginning
         """ This method is called when a single package is beginning
         to download. """
         to download. """
-        print "Downloading %s" % (pp.packageName)
+        print "Downloading package %s" % (pp.packageName)
         self.__callDownloadStarted()
         self.__callDownloadStarted()
         self.__callPackageStarted(pp)
         self.__callPackageStarted(pp)
 
 

+ 118 - 49
direct/src/p3d/Packager.py

@@ -207,6 +207,56 @@ class Packager:
             
             
             return xpackage
             return xpackage
 
 
+    class HostEntry:
+        def __init__(self, url = None, descriptiveName = None, mirrors = None):
+            self.url = url
+            self.descriptiveName = descriptiveName
+            self.mirrors = mirrors or []
+            self.altHosts = {}
+
+        def loadXml(self, xhost, packager):
+            self.url = xhost.Attribute('url')
+            self.descriptiveName = xhost.Attribute('descriptive_name')
+            self.mirrors = []
+            xmirror = xhost.FirstChildElement('mirror')
+            while xmirror:
+                url = xmirror.Attribute('url')
+                self.mirrors.append(url)
+                xmirror = xmirror.NextSiblingElement('mirror')
+
+            xalthost = xhost.FirstChildElement('alt_host')
+            while xalthost:
+                url = xalthost.Attribute('url')
+                he = packager.addHost(url)
+                he.loadXml(xalthost, packager)
+                xalthost = xalthost.NextSiblingElement('alt_host')
+
+        def makeXml(self, packager = None):
+            """ Returns a new TiXmlElement. """
+            xhost = TiXmlElement('host')
+            xhost.SetAttribute('url', self.url)
+            if self.descriptiveName:
+                xhost.SetAttribute('descriptive_name', self.descriptiveName)
+
+            for mirror in self.mirrors:
+                xmirror = TiXmlElement('mirror')
+                xmirror.SetAttribute('url', mirror)
+                xhost.InsertEndChild(xmirror)
+
+            if packager:
+                altHosts = self.altHosts.items()
+                altHosts.sort()
+                for keyword, alt in altHosts:
+                    he = packager.hosts.get(alt, None)
+                    if he:
+                        xalthost = he.makeXml()
+                        xalthost.SetValue('alt_host')
+                        xalthost.SetAttribute('keyword', keyword)
+                        xhost.InsertEndChild(xalthost)
+
+            return xhost
+
+
     class Package:
     class Package:
         """ This is the full information on a particular package we
         """ This is the full information on a particular package we
         are constructing.  Don't confuse it with PackageEntry, above,
         are constructing.  Don't confuse it with PackageEntry, above,
@@ -921,20 +971,21 @@ class Packager:
 
 
             self.__addConfigs(xpackage)
             self.__addConfigs(xpackage)
 
 
-            requireThisHost = False
+            requireHosts = {}
             for package in self.requires:
             for package in self.requires:
                 xrequires = TiXmlElement('requires')
                 xrequires = TiXmlElement('requires')
                 xrequires.SetAttribute('name', package.packageName)
                 xrequires.SetAttribute('name', package.packageName)
                 if package.version:
                 if package.version:
                     xrequires.SetAttribute('version', package.version)
                     xrequires.SetAttribute('version', package.version)
                 xrequires.SetAttribute('host', package.host)
                 xrequires.SetAttribute('host', package.host)
-                if package.host == self.packager.host:
-                    requireThisHost = True
+                requireHosts[package.host] = True
                 xpackage.InsertEndChild(xrequires)
                 xpackage.InsertEndChild(xrequires)
 
 
-            if requireThisHost:
-                xhost = self.packager.makeHostXml()
-                xpackage.InsertEndChild(xhost)
+            for host in requireHosts.keys():
+                he = self.packager.hosts.get(host, None)
+                if he:
+                    xhost = he.makeXml(packager = self.packager)
+                    xpackage.InsertEndChild(xhost)
 
 
             doc.InsertEndChild(xpackage)
             doc.InsertEndChild(xpackage)
 
 
@@ -1485,10 +1536,9 @@ class Packager:
 
 
         # The download URL at which these packages will eventually be
         # The download URL at which these packages will eventually be
         # hosted.
         # hosted.
+        self.hosts = {}
         self.host = PandaSystem.getPackageHostUrl()
         self.host = PandaSystem.getPackageHostUrl()
-        self.hostDescriptiveName = None
-        self.hostMirrors = []
-        self.altHosts = {}
+        self.addHost(self.host)
 
 
         # A search list for previously-built local packages.
         # A search list for previously-built local packages.
         self.installSearch = ConfigVariableSearchPath('pdef-path')
         self.installSearch = ConfigVariableSearchPath('pdef-path')
@@ -1650,20 +1700,49 @@ class Packager:
         # file.
         # file.
         self.contents = {}
         self.contents = {}
 
 
-    def setHost(self, host, descriptiveName = None, mirrors = []):
+    def setHost(self, host, descriptiveName = None, mirrors = None):
         """ Specifies the URL that will ultimately host these
         """ Specifies the URL that will ultimately host these
         contents. """
         contents. """
-        
+
         self.host = host
         self.host = host
-        self.hostDescriptiveName = descriptiveName
-        self.hostMirrors = mirrors
+        self.addHost(host, descriptiveName, mirrors)
+
+    def addHost(self, host, descriptiveName = None, mirrors = None):
+        """ Adds a host to the list of known download hosts.  This
+        information will be written into any p3d files that reference
+        this host; this can be used to pre-define the possible mirrors
+        for a given host, for instance.  Returns the newly-created
+        HostEntry object."""
+
+        he = self.hosts.get(host, None)
+        if he is None:
+            # Define a new host entry
+            he = self.HostEntry(host, descriptiveName, mirrors)
+            self.hosts[host] = he
+        else:
+            # Update an existing host entry
+            if descriptiveName:
+                he.descriptiveName = descriptiveName
+            if mirrors:
+                he.mirrors = mirrors
+
+        return he
+        
+    def addAltHost(self, keyword, altHost, origHost = None,
+                   descriptiveName = None, mirrors = None):
+        """ Adds an alternate host to any already-known host.  This
+        defines an alternate server that may be contacted, if
+        specified on the HTML page, which hosts a different version of
+        the server's contents.  (This is different from a mirror,
+        which hosts an identical version of the server's contents.)
+        """
 
 
-    def addAltHost(self, keyword, host, descriptiveName = None, mirrors = []):
-        """ Adds an alternate host from which an alternate version of
-        these contents may be downloaded, if specified on the HTML
-        page. """
+        if not origHost:
+            origHost = self.host
 
 
-        self.altHosts[keyword] = (host, descriptiveName, mirrors)
+        self.addHost(altHost, descriptiveName, mirrors)
+        he = self.addHost(origHost)
+        he.altHosts[keyword] = altHost
 
 
     def addWindowsSearchPath(self, searchPath, varname):
     def addWindowsSearchPath(self, searchPath, varname):
         """ Expands $varname, interpreting as a Windows-style search
         """ Expands $varname, interpreting as a Windows-style search
@@ -2576,6 +2655,7 @@ class Packager:
         """ Reads the contents.xml file at the beginning of
         """ Reads the contents.xml file at the beginning of
         processing. """
         processing. """
 
 
+        self.hosts = {}
         self.contents = {}
         self.contents = {}
         self.contentsChanged = False
         self.contentsChanged = False
 
 
@@ -2587,8 +2667,16 @@ class Packager:
 
 
         xcontents = doc.FirstChildElement('contents')
         xcontents = doc.FirstChildElement('contents')
         if xcontents:
         if xcontents:
-            if self.hostDescriptiveName is None:
-                self.hostDescriptiveName = xcontents.Attribute('descriptive_name')
+            xhost = xcontents.FirstChildElement('host')
+            while xhost:
+                he = self.HostEntry()
+                he.loadXml(xhost, self)
+                self.hosts[he.url] = he
+                xhost = xhost.NextSiblingElement('host')
+
+            host = xcontents.Attribute('host')
+            if host:
+                self.host = host
             
             
             xpackage = xcontents.FirstChildElement('package')
             xpackage = xcontents.FirstChildElement('package')
             while xpackage:
             while xpackage:
@@ -2597,6 +2685,10 @@ class Packager:
                 self.contents[pe.getKey()] = pe
                 self.contents[pe.getKey()] = pe
                 xpackage = xpackage.NextSiblingElement('package')
                 xpackage = xpackage.NextSiblingElement('package')
 
 
+        # Since we've blown away the self.hosts map, we have to make
+        # sure that our own host at least is added to the map.
+        self.addHost(self.host)
+
     def writeContentsFile(self):
     def writeContentsFile(self):
         """ Rewrites the contents.xml file at the end of
         """ Rewrites the contents.xml file at the end of
         processing. """
         processing. """
@@ -2611,9 +2703,12 @@ class Packager:
         doc.InsertEndChild(decl)
         doc.InsertEndChild(decl)
 
 
         xcontents = TiXmlElement('contents')
         xcontents = TiXmlElement('contents')
-
-        xhost = self.makeHostXml()
-        xcontents.InsertEndChild(xhost)
+        if self.host:
+            xcontents.SetAttribute('host', self.host)
+            he = self.hosts.get(self.host, None)
+            if he:
+                xhost = he.makeXml(packager = self)
+                xcontents.InsertEndChild(xhost)
 
 
         contents = self.contents.items()
         contents = self.contents.items()
         contents.sort()
         contents.sort()
@@ -2623,32 +2718,6 @@ class Packager:
 
 
         doc.InsertEndChild(xcontents)
         doc.InsertEndChild(xcontents)
         doc.SaveFile()
         doc.SaveFile()
-
-    def makeHostXml(self):
-        """ Constructs the <host> entry for this host. """
-        xhost = self.makeHostXmlLine('host', self.host, self.hostDescriptiveName, self.hostMirrors)
-
-        for keyword, (host, descriptiveName, mirrors) in self.altHosts.items():
-            xalthost = self.makeHostXmlLine('alt_host', host, descriptiveName, mirrors)
-            xalthost.SetAttribute('keyword', keyword)
-            xhost.InsertEndChild(xalthost)
-        return xhost
-
-    def makeHostXmlLine(self, element, host, descriptiveName, mirrors):
-        """ Constructs the <host> or <alt_host> entry for the
-        indicated host and its mirrors. """
-
-        xhost = TiXmlElement(element)
-        xhost.SetAttribute('url', host)
-        if descriptiveName:
-            xhost.SetAttribute('descriptive_name', descriptiveName)
-
-        for mirror in mirrors:
-            xmirror = TiXmlElement('mirror')
-            xmirror.SetAttribute('url', mirror)
-            xhost.InsertEndChild(xmirror)
-
-        return xhost
         
         
 
 
 # The following class and function definitions represent a few sneaky
 # The following class and function definitions represent a few sneaky

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

@@ -149,7 +149,6 @@ read_contents_file(const string &contents_filename) {
         const char *keyword = xalthost->Attribute("keyword");
         const char *keyword = xalthost->Attribute("keyword");
         const char *url = xalthost->Attribute("url");
         const char *url = xalthost->Attribute("url");
         if (keyword != NULL && url != NULL) {
         if (keyword != NULL && url != NULL) {
-          cerr << "got alt host " << keyword << ": " << url << "\n";
           _alt_hosts[keyword] = url;
           _alt_hosts[keyword] = url;
         }
         }
         xalthost = xalthost->NextSiblingElement("alt_host");
         xalthost = xalthost->NextSiblingElement("alt_host");

+ 47 - 1
direct/src/plugin/p3dInstance.cxx

@@ -1255,7 +1255,19 @@ scan_app_desc_file(TiXmlDocument *doc) {
         version = "";
         version = "";
       }
       }
       P3DHost *host = inst_mgr->get_host(host_url);
       P3DHost *host = inst_mgr->get_host(host_url);
-      P3DPackage *package = host->get_package(name, version, alt_host);
+      string this_alt_host = alt_host;
+
+      // Look up in the p3d_info.xml file to see if this p3d file has
+      // a specific alt_host indication for this host_url.
+      string alt_host_url = find_alt_host_url(xpackage, host_url, alt_host);
+      if (!alt_host_url.empty()) {
+        // If it does, we go ahead and switch to that host now,
+        // instead of bothering to contact the original host.
+        host = inst_mgr->get_host(alt_host_url);
+        this_alt_host.clear();
+      }
+        
+      P3DPackage *package = host->get_package(name, version, this_alt_host);
       add_package(package);
       add_package(package);
     }
     }
 
 
@@ -1263,6 +1275,40 @@ scan_app_desc_file(TiXmlDocument *doc) {
   }
   }
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DInstance::find_alt_host_url
+//       Access: Private
+//  Description: Looks in the p3d_info.xml file for the alt_host
+//               associated with the indicated host_url, if any.
+//               Returns empty string if there is no match.
+////////////////////////////////////////////////////////////////////
+string P3DInstance::
+find_alt_host_url(TiXmlElement *xpackage, 
+                  const string &host_url, const string &alt_host) {
+  TiXmlElement *xhost = xpackage->FirstChildElement("host");
+  while (xhost != NULL) {
+    const char *url = xhost->Attribute("url");
+    if (url != NULL && host_url == url) {
+      // This matches the host.  Now do we have a matching alt_host
+      // keyword for this host?
+      TiXmlElement *xalt_host = xhost->FirstChildElement("alt_host");
+      while (xalt_host != NULL) {
+        const char *keyword = xalt_host->Attribute("keyword");
+        if (keyword != NULL && alt_host == keyword) {
+          const char *alt_host_url = xalt_host->Attribute("url");
+          if (alt_host_url != NULL) {
+            return alt_host_url;
+          }
+        }
+        xalt_host = xalt_host->NextSiblingElement("alt_host");
+      }
+    }
+    xhost = xhost->NextSiblingElement("host");
+  }
+
+  return string();
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DInstance::send_browser_script_object
 //     Function: P3DInstance::send_browser_script_object
 //       Access: Private
 //       Access: Private

+ 2 - 0
direct/src/plugin/p3dInstance.h

@@ -153,6 +153,8 @@ private:
   void mark_p3d_untrusted();
   void mark_p3d_untrusted();
   void mark_p3d_trusted();
   void mark_p3d_trusted();
   void scan_app_desc_file(TiXmlDocument *doc);
   void scan_app_desc_file(TiXmlDocument *doc);
+  string find_alt_host_url(TiXmlElement *xpackage, 
+                           const string &host_url, const string &alt_host);
 
 
   void send_browser_script_object();
   void send_browser_script_object();
   P3D_request *make_p3d_request(TiXmlElement *xrequest);
   P3D_request *make_p3d_request(TiXmlElement *xrequest);