Browse Source

PackageInstaller fixes

David Rose 16 years ago
parent
commit
fcc7b5f9c2

+ 8 - 12
direct/src/p3d/AppRunner.py

@@ -363,15 +363,17 @@ class AppRunner(DirectObject):
     def addPackageInfo(self, name, platform, version, hostUrl):
         """ Called by the browser to list all of the "required"
         packages that were preloaded before starting the
-        application. """
+        application.  If for some reason the package isn't already
+        downloaded, this will download it on the spot. """
 
         host = self.getHost(hostUrl)
 
         try:
             host.readContentsFile()
         except ValueError:
-            print "Host %s has not been downloaded, cannot preload %s." % (hostUrl, name)
-            return
+            if not host.downloadContentsFile(self.http):
+                print "Host %s cannot be downloaded, cannot preload %s." % (hostUrl, name)
+                return
 
         if not platform:
             platform = None
@@ -380,15 +382,9 @@ class AppRunner(DirectObject):
             print "Couldn't find %s %s on %s" % (name, version, hostUrl)
             return
 
-        self.installedPackages.append(package)
-
-        if package.checkStatus():
-            # The package should have been loaded already.  If it has,
-            # go ahead and mount it.
-            package.installPackage(self)
-        else:
-            print "%s %s is not preloaded." % (
-                package.packageName, package.packageVersion)
+        package.downloadDescFile(self.http)
+        package.downloadPackage(self.http)
+        package.installPackage(self)
 
     def setP3DFilename(self, p3dFilename, tokens, argv, instanceId,
                        interactiveConsole):

+ 25 - 3
direct/src/p3d/PackageInfo.py

@@ -1,4 +1,4 @@
-from pandac.PandaModules import Filename, URLSpec, DocumentSpec, Ramfile, TiXmlDocument, Multifile, Decompressor, EUOk, EUSuccess, VirtualFileSystem, Thread, getModelPath, Patchfile
+from pandac.PandaModules import Filename, URLSpec, DocumentSpec, Ramfile, TiXmlDocument, Multifile, Decompressor, EUOk, EUSuccess, VirtualFileSystem, Thread, getModelPath, Patchfile, ExecutionEnvironment
 from direct.p3d.FileSpec import FileSpec
 from direct.showbase import VFSImporter
 import os
@@ -72,6 +72,10 @@ class PackageInfo:
         # downloaded and unpackaged.
         self.hasPackage = False
 
+        # This is set true when the package has been "installed",
+        # meaning it's been added to the paths and all.
+        self.installed = False
+
     def getDownloadEffort(self):
         """ Returns the relative amount of effort it will take to
         download this package.  The units are meaningless, except
@@ -348,7 +352,10 @@ class PackageInfo:
         patchMaker.buildPatchChains()
         fromPv = patchMaker.getPackageVersion(package.getGenericKey(fileSpec))
         toPv = package.currentPv
-        patchChain = toPv.getPatchChain(fromPv)
+
+        patchChain = None
+        if toPv and fromPv:
+            patchChain = toPv.getPatchChain(fromPv)
 
         if patchChain is None:
             # No path.
@@ -383,6 +390,8 @@ class PackageInfo:
         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():
@@ -522,7 +531,11 @@ class PackageInfo:
         available for use. """
 
         assert self.hasPackage
-        
+        if self.installed:
+            # Already installed.
+            return True
+        assert self not in appRunner.installedPackages
+
         mfPathname = Filename(self.packageDir, self.uncompressedArchive.filename)
         mf = Multifile()
         if not mf.openRead(mfPathname):
@@ -562,6 +575,10 @@ class PackageInfo:
         # model-path, so it shouldn't be already there.
         getModelPath().prependDirectory(self.packageDir)
 
+        # Set the environment variable to reference the package root.
+        envvar = '%s_ROOT' % (self.packageName.upper())
+        ExecutionEnvironment.setEnvironmentVariable(envvar, self.packageDir.toOsSpecific())
+
         # Also, find any toplevel Python packages, and add these as
         # shared packages.  This will allow different packages
         # installed in different directories to share Python files as
@@ -577,3 +594,8 @@ class PackageInfo:
         # Fix up any shared directories so we can load packages from
         # disparate locations.
         VFSImporter.reloadSharedPackages()
+
+        self.installed = True
+        appRunner.installedPackages.append(self)
+
+        return True

+ 84 - 26
direct/src/p3d/PackageInstaller.py

@@ -2,6 +2,7 @@ from direct.showbase.DirectObject import DirectObject
 from direct.stdpy.threading import Lock
 from direct.showbase.MessengerGlobal import messenger
 from direct.task.TaskManagerGlobal import taskMgr
+from direct.p3d.PackageInfo import PackageInfo
 
 class PackageInstaller(DirectObject):
 
@@ -43,12 +44,23 @@ class PackageInstaller(DirectObject):
             self.version = version
             self.host = host
 
-            # Filled in by getDescFile().
-            self.package = None
+            # This will be filled in properly by checkDescFile() or
+            # getDescFile(); in the meantime, set a placeholder.
+            self.package = PackageInfo(host, packageName, version)
 
+            # Set true when the package has finished downloading,
+            # either successfully or unsuccessfully.
             self.done = False
+
+            # Set true or false when self.done has been set.
             self.success = False
 
+            # Set true when the packageFinished() callback has been
+            # delivered.
+            self.notified = False
+
+            # These are used to ensure the callbacks only get
+            # delivered once for a particular package.
             self.calledPackageStarted = False
             self.calledPackageFinished = False
 
@@ -59,7 +71,8 @@ class PackageInstaller(DirectObject):
             # which is weighted differently into one grand total.  So,
             # the total doesn't really represent bytes; it's a
             # unitless number, which means something only as a ratio
-            # to other packages.
+            # to other packages.  Filled in by checkDescFile() or
+            # getDescFile().
             self.downloadEffort = 0
 
         def getProgress(self):
@@ -68,6 +81,33 @@ class PackageInstaller(DirectObject):
 
             return self.package.downloadProgress
 
+        def checkDescFile(self):
+            """ Returns true if the desc file is already downloaded
+            and good, or false if it needs to be downloaded. """
+
+            if not self.host.hasContentsFile:
+                # If the contents file isn't ready yet, we can't check
+                # the desc file yet.
+                return False
+
+            # All right, get the package info now.
+            package = self.host.getPackage(self.packageName, self.version)
+            if not package:
+                print "Package %s %s not known on %s" % (
+                    self.packageName, self.version, self.host.hostUrl)
+                return False
+
+            self.package = package
+            self.package.checkStatus()
+
+            if not self.package.hasDescFile:
+                return False
+
+            self.downloadEffort = self.package.getDownloadEffort()
+
+            return True
+            
+
         def getDescFile(self, http):
             """ Synchronously downloads the desc files required for
             the package. """
@@ -76,12 +116,13 @@ class PackageInstaller(DirectObject):
                 return False
 
             # All right, get the package info now.
-            self.package = self.host.getPackage(self.packageName, self.version)
-            if not self.package:
+            package = self.host.getPackage(self.packageName, self.version)
+            if not package:
                 print "Package %s %s not known on %s" % (
                     self.packageName, self.version, self.host.hostUrl)
                 return False
 
+            self.package = package
             if not self.package.downloadDescFile(http):
                 return False
 
@@ -125,6 +166,10 @@ class PackageInstaller(DirectObject):
         self.needsDownload = []
         self.downloadTask = None
 
+        # A list of packages that were already done at the time they
+        # were passed to addPackage().
+        self.earlyDone = []
+
         # A list of packages that have been successfully installed, or
         # packages that have failed.
         self.done = []
@@ -184,11 +229,23 @@ class PackageInstaller(DirectObject):
         self.packageLock.acquire()
         try:
             self.packages.append(pp)
-            self.needsDescFile.append(pp)
-            if not self.descFileTask:
-                self.descFileTask = taskMgr.add(
-                    self.__getDescFileTask, 'getDescFile',
-                    taskChain = self.taskChain)
+            if not pp.checkDescFile():
+                # Still need to download the desc file.
+                self.needsDescFile.append(pp)
+                if not self.descFileTask:
+                    self.descFileTask = taskMgr.add(
+                        self.__getDescFileTask, 'getDescFile',
+                        taskChain = self.taskChain)
+
+            elif not pp.package.hasPackage:
+                # The desc file is good, but the package itself needs
+                # to be downloaded.
+                self.needsDownload.append(pp)
+
+            else:
+                # The package is already fully downloaded.
+                self.earlyDone.append(pp)
+                    
         finally:
             self.packageLock.release()
 
@@ -197,15 +254,19 @@ class PackageInstaller(DirectObject):
         installed, call donePackages() to mark the end of the list.
         This is necessary to determine what the complete set of
         packages is (and therefore how large the total download size
-        is).  Until this is called, no low-level callbacks will be
-        made as the packages are downloading. """
+        is).  None of the low-level callbacks will be made before this
+        call. """
 
         if self.state != self.S_initial:
             # We've already been here.
             return
 
-        working = True
-        
+        # Throw the messages for packages that were already done
+        # before we started.
+        for pp in self.earlyDone:
+            self.__donePackage(pp, True)
+        self.earlyDone = []
+
         self.packageLock.acquire()
         try:
             if self.state != self.S_initial:
@@ -213,12 +274,13 @@ class PackageInstaller(DirectObject):
             self.state = self.S_ready
             if not self.needsDescFile:
                 # All package desc files are already available; so begin.
-                working = self.__prepareToStart()
+                self.__prepareToStart()
         finally:
             self.packageLock.release()
 
-        if not working:
-            self.downloadFinished(True)
+        if not self.packages:
+            # Trivial no-op.
+            self.__callDownloadFinished(True)
 
     def downloadStarted(self):
         """ This callback is made at some point after donePackages()
@@ -321,20 +383,15 @@ class PackageInstaller(DirectObject):
         downloaded and installed, or has failed. """
         print "Downloaded %s: %s" % (pp.packageName, pp.success)
         self.__callPackageFinished(pp, pp.success)
+        pp.notified = True
 
-        if not pp.calledPackageStarted:
-            # Trivially done; this one was done before it got started.
-            return
-
-        assert self.state == self.S_started
         # See if there are more packages to go.
         success = True
         allDone = True
         self.packageLock.acquire()
         try:
-            assert self.state == self.S_started
             for pp in self.packages:
-                if pp.done:
+                if pp.notified:
                     success = success and pp.success
                 else:
                     allDone = False
@@ -468,8 +525,6 @@ class PackageInstaller(DirectObject):
             self.__donePackage(pp, False)
             return task.cont
 
-        pp.package.installPackage(self.appRunner)
-
         # Successfully downloaded and installed.
         self.__donePackage(pp, True)
         
@@ -480,6 +535,9 @@ class PackageInstaller(DirectObject):
         or otherwise. """
         assert not pp.done
 
+        if success:
+            pp.package.installPackage(self.appRunner)
+
         self.packageLock.acquire()
         try:
             pp.done = True

+ 1 - 0
direct/src/p3d/Packager.py

@@ -1392,6 +1392,7 @@ class Packager:
                     file.newName = file.newName[:-1] + 'e'
                 
                 preFilename = Filename.temporary('', 'p3d_', '.pre')
+                tempFilename.setText()
                 encryptFile(tempFilename, preFilename, self.packager.prcEncryptionKey)
                 tempFilename.unlink()
                 tempFilename = preFilename

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

@@ -7,8 +7,8 @@ tree of .py files and models, into a p3d file for convenient
 distribution.  The resulting p3d file can be run by the Panda3D
 runtime executable, or by the Panda3D web browser plugin.
 
-Also see ppackage, which can be used to build p3d files more
-generally, using a pdef description file.
+Also see ppackage, a more powerful (but more complex) tool that can
+also be used to build p3d applications, using a pdef description file.
 
 Usage:
 

+ 3 - 7
direct/src/p3d/ppackage.py

@@ -12,13 +12,9 @@ file, for execution by the Panda3D plugin or runtime.  (But also see
 packp3d, which is designed to be a simpler interface for building
 applications.)
 
-In addition to building a package in the first place, this script will
-also generate downloadable patches of the package against previous
-versions, and manage the whole tree of patches in a directory
-structure suitable for hosting on a web server somewhere.  (This part
-is not yet completed.)
-
-This script is actually a wrapper around Panda's Packager.py.
+This script is actually a wrapper around Panda's Packager.py, which
+can be invoked directly by Python code that requires a programmatic
+interface to building packages.
 
 Usage:
 

+ 4 - 2
direct/src/showbase/ShowBase.py

@@ -54,7 +54,7 @@ class ShowBase(DirectObject.DirectObject):
 
     notify = directNotify.newCategory("ShowBase")
 
-    def __init__(self, fStartDirect = True):
+    def __init__(self, fStartDirect = True, windowType = None):
         __builtin__.__dev__ = config.GetBool('want-dev', 0)
         if config.GetBool('want-variable-dump', 0):
             ExceptionVarDump.install()
@@ -139,7 +139,9 @@ class ShowBase(DirectObject.DirectObject):
         # we get a window-event.
         self.__oldAspectRatio = None
 
-        self.windowType = self.config.GetString('window-type', 'onscreen')
+        self.windowType = windowType
+        if self.windowType is None:
+            self.windowType = self.config.GetString('window-type', 'onscreen')
         self.requireWindow = self.config.GetBool('require-window', 1)
 
         # base.win is the main, or only window; base.winList is a list of