Browse Source

more max_age stuff

David Rose 15 years ago
parent
commit
1b02e24408

+ 33 - 13
direct/src/p3d/AppRunner.py

@@ -74,6 +74,14 @@ class AppRunner(DirectObject):
 
     # Default values for parameters that are absent from the config file:
     maxDiskUsage = 2048 * 1048576  # 2 GB
+
+    # Values for verifyContents, from p3d_plugin.h
+    P3DVCNone = 0
+    P3DVCNormal = 1
+    P3DVCForce = 2
+
+    # Also from p3d_plugin.h
+    P3D_CONTENTS_DEFAULT_MAX_AGE = 5
     
     def __init__(self):
         DirectObject.__init__(self)
@@ -96,6 +104,8 @@ class AppRunner(DirectObject):
         self.initialAppImport = False
         self.trueFileIO = False
 
+        self.verifyContents = self.P3DVCNone
+
         self.sessionId = 0
         self.packedAppEnvironmentInitialized = False
         self.gotWindow = False
@@ -272,6 +282,7 @@ class AppRunner(DirectObject):
         parameter, nested, is a list of packages that we are
         recursively calling this from, to avoid recursive loops. """
 
+        package.checkStatus()
         if not package.downloadDescFile(self.http):
             return False
 
@@ -334,7 +345,7 @@ class AppRunner(DirectObject):
         # No shenanigans, just return the requested host.
         return host
         
-    def getHost(self, hostUrl):
+    def getHost(self, hostUrl, hostDir = None):
         """ Returns a new HostInfo object corresponding to the
         indicated host URL.  If we have already seen this URL
         previously, returns the same object.
@@ -348,7 +359,7 @@ class AppRunner(DirectObject):
 
         host = self.hosts.get(hostUrl, None)
         if not host:
-            host = HostInfo(hostUrl, appRunner = self)
+            host = HostInfo(hostUrl, appRunner = self, hostDir = hostDir)
             self.hosts[hostUrl] = host
         return host
 
@@ -361,9 +372,10 @@ class AppRunner(DirectObject):
         host directory cannot be read or doesn't seem consistent. """
 
         host = HostInfo(None, hostDir = hostDir, appRunner = self)
-        if not host.readContentsFile():
-            # Couldn't read the contents.xml file
-            return None
+        if not host.hasContentsFile:
+            if not host.readContentsFile():
+                # Couldn't read the contents.xml file
+                return None
 
         if not host.hostUrl:
             # The contents.xml file there didn't seem to indicate the
@@ -754,7 +766,8 @@ class AppRunner(DirectObject):
                                needsResponse = False)
         self.deferredEvals = []
 
-    def setInstanceInfo(self, rootDir, logDirectory, superMirrorUrl):
+    def setInstanceInfo(self, rootDir, logDirectory, superMirrorUrl,
+                        verifyContents):
         """ Called by the browser to set some global information about
         the instance. """
 
@@ -772,6 +785,10 @@ class AppRunner(DirectObject):
         # The "super mirror" URL, generally used only by panda3d.exe.
         self.superMirrorUrl = superMirrorUrl
 
+        # How anxious should we be about contacting the server for
+        # the latest code?
+        self.verifyContents = verifyContents
+
         # Now that we have rootDir, we can read the config file.
         self.readConfigXml()
         
@@ -782,14 +799,17 @@ class AppRunner(DirectObject):
         If for some reason the package isn't already downloaded, this
         will download it on the spot.  Raises OSError on failure. """
 
-        host = self.getHost(hostUrl)
-        if hostDir and not host.hostDir:
-            host.hostDir = Filename.fromOsSpecific(hostDir)
+        host = self.getHost(hostUrl, hostDir = hostDir)
+
+        if not host.hasContentsFile:
+            # Always pre-read these hosts' contents.xml files, even if
+            # we have P3DVCForce in effect, since presumably we've
+            # already forced them on the plugin side.
+            host.readContentsFile()
 
-        if not host.readContentsFile():
-            if not host.downloadContentsFile(self.http):
-                message = "Host %s cannot be downloaded, cannot preload %s." % (hostUrl, name)
-                raise OSError, message
+        if not host.downloadContentsFile(self.http):
+            message = "Host %s cannot be downloaded, cannot preload %s." % (hostUrl, name)
+            raise OSError, message
 
         if not platform:
             platform = None

+ 15 - 12
direct/src/p3d/DeploymentTools.py

@@ -30,10 +30,11 @@ class Standalone:
         self.host = HostInfo(PandaSystem.getPackageHostUrl(), appRunner = base.appRunner, hostDir = hostDir, asMirror = False, perPlatform = True)
         
         self.http = HTTPClient.getGlobalPtr()
-        if not self.host.readContentsFile():
-            if not self.host.downloadContentsFile(self.http):
-                Standalone.notify.error("couldn't read host")
-                return False
+        if not self.host.hasContentsFile:
+            if not self.host.readContentsFile():
+                if not self.host.downloadContentsFile(self.http):
+                    Standalone.notify.error("couldn't read host")
+                    return False
     
     def buildAll(self, outputDir = "."):
         """ Builds standalone executables for every known platform,
@@ -194,10 +195,11 @@ class Installer:
             return
         
         host = HostInfo(self.hostUrl, appRunner = base.appRunner, rootDir = rootDir, asMirror = True, perPlatform = False)
-        if not host.readContentsFile():
-            if not host.downloadContentsFile(self.http):
-                Installer.notify.error("couldn't read host")
-                return
+        if not host.hasContentsFile:
+            if not host.readContentsFile():
+                if not host.downloadContentsFile(self.http):
+                    Installer.notify.error("couldn't read host")
+                    return
         
         for name, version in self.requirements:
             package = host.getPackage(name, version, platform)
@@ -210,10 +212,11 @@ class Installer:
         
         # Also install the 'images' package from the same host that p3dembed was downloaded from.
         host = HostInfo(self.standalone.host.hostUrl, appRunner = base.appRunner, rootDir = rootDir, asMirror = False, perPlatform = False)
-        if not host.readContentsFile():
-            if not host.downloadContentsFile(self.http):
-                Installer.notify.error("couldn't read host")
-                return
+        if not host.hasContentsFile:
+            if not host.readContentsFile():
+                if not host.downloadContentsFile(self.http):
+                    Installer.notify.error("couldn't read host")
+                    return
         
         for package in host.getPackages(name = "images"):
             if not package.downloadDescFile(self.http):

+ 10 - 0
direct/src/p3d/FileSpec.py

@@ -9,6 +9,10 @@ class FileSpec:
 
     def __init__(self):
         self.actualFile = None
+        self.filename = None
+        self.size = 0
+        self.timestamp = 0
+        self.hash = None
 
     def fromFile(self, packageDir, filename, pathname = None, st = None):
         """ Reads the file information from the indicated file.  If st
@@ -28,9 +32,15 @@ class FileSpec:
         self.size = st.st_size
         self.timestamp = st.st_mtime
 
+        self.readHash(pathname)
+
+    def readHash(self, pathname):
+        """ Reads the hash only from the indicated pathname. """
+
         hv = HashVal()
         hv.hashFile(pathname)
         self.hash = hv.asHex()
+                 
 
     def loadXml(self, xelement):
         """ Reads the file information from the indicated XML

+ 102 - 22
direct/src/p3d/HostInfo.py

@@ -33,7 +33,7 @@ class HostInfo:
         the host directory directly, without an intervening
         platform-specific directory name.  If asMirror is True, then
         the default is perPlatform = True. """
-        
+
         assert appRunner or rootDir or hostDir
 
         self.__setHostUrl(hostUrl)
@@ -41,7 +41,10 @@ class HostInfo:
         self.rootDir = rootDir
         if rootDir is None and appRunner:
             self.rootDir = appRunner.rootDir
-        
+
+        if hostDir and not isinstance(hostDir, Filename):
+            hostDir = Filename.fromOsSpecific(hostDir)
+            
         self.hostDir = hostDir
         self.asMirror = asMirror
         self.perPlatform = perPlatform
@@ -52,6 +55,13 @@ class HostInfo:
         # successfully read.
         self.hasContentsFile = False
 
+        # This is the time value at which the current contents file is
+        # no longer valid.
+        self.contentsExpiration = 0
+
+        # Contains the md5 hash of the original contents.xml file.
+        self.contentsSpec = FileSpec()
+
         # descriptiveName will be filled in later, when the
         # contents file is read.
         self.descriptiveName = None
@@ -70,6 +80,11 @@ class HostInfo:
         # will be filled in when the contents file is read.
         self.packages = {}
 
+        if self.appRunner and self.appRunner.verifyContents != self.appRunner.P3DVCForce:
+            # Attempt to pre-read the existing contents.xml; maybe it
+            # will be current enough for our purposes.
+            self.readContentsFile()
+
     def __setHostUrl(self, hostUrl):
         """ Assigns self.hostUrl, and related values. """
         self.hostUrl = hostUrl
@@ -96,7 +111,7 @@ class HostInfo:
         synchronously, and then reads it.  Returns true on success,
         false on failure. """
 
-        if self.hasContentsFile:
+        if self.hasCurrentContentsFile():
             # We've already got one.
             return True
 
@@ -145,11 +160,12 @@ class HostInfo:
             f.write(rf.getData())
             f.close()
 
-            if not self.readContentsFile(tempFilename):
+            if not self.readContentsFile(tempFilename, freshDownload = True):
                 self.notify.warning("Failure reading %s" % (url))
                 tempFilename.unlink()
                 return False
 
+            tempFilename.unlink()
             return True
 
         # Couldn't download the file.  Maybe we should look for a
@@ -167,9 +183,12 @@ class HostInfo:
 
         # Get the hash of the original file.
         assert self.hostDir
-        filename = Filename(self.hostDir, 'contents.xml')
         hv1 = HashVal()
-        hv1.hashFile(filename)
+        if self.contentsSpec.hash:
+            hv1.setFromHex(self.contentsSpec.hash)
+        else:
+            filename = Filename(self.hostDir, 'contents.xml')
+            hv1.hashFile(filename)
 
         # Now download it again.
         self.hasContentsFile = False
@@ -186,8 +205,18 @@ class HostInfo:
             self.notify.info("%s has not changed." % (url))
             return False
 
+    def hasCurrentContentsFile(self):
+        """ Returns true if a contents.xml file has been successfully
+        read for this host and is still current, false otherwise. """
+        if not self.appRunner or self.appRunner.verifyContents == self.appRunner.P3DVCNone:
+            # If we're not asking to verify contents, then
+            # contents.xml files never expires.
+            return self.hasContentsFile
 
-    def readContentsFile(self, tempFilename = None):
+        now = int(time.time())
+        return now < self.contentsExpiration and self.hasContentsFile
+        
+    def readContentsFile(self, tempFilename = None, freshDownload = False):
         """ Reads the contents.xml file for this particular host, once
         it has been downloaded into the indicated temporary file.
         Returns true on success, false if the contents file is not
@@ -198,29 +227,77 @@ class HostInfo:
         there already.  If tempFilename is not specified, the standard
         filename is read if it is known. """
 
-        if self.hasContentsFile:
-            # No need to read it again.
-            return True
-
         if not hasattr(PandaModules, 'TiXmlDocument'):
             return False
 
         if not tempFilename:
-            if not self.hostDir:
+            if self.hostDir:
                 # If the filename is not specified, we can infer it
-                # only if we already know our hostDir.
-                return False
-            
-            tempFilename = Filename(self.hostDir, 'contents.xml')
-        
+                # if we already know our hostDir
+                hostDir = self.hostDir
+            else:
+                # Otherwise, we have to guess the hostDir.
+                hostDir = self.__determineHostDir(None, self.hostUrl)
+
+            tempFilename = Filename(hostDir, 'contents.xml')
+
         doc = PandaModules.TiXmlDocument(tempFilename.toOsSpecific())
         if not doc.LoadFile():
             return False
-        
+
         xcontents = doc.FirstChildElement('contents')
         if not xcontents:
             return False
 
+        maxAge = xcontents.Attribute('max_age')
+        if maxAge:
+            try:
+                maxAge = int(maxAge)
+            except:
+                maxAge = None
+        if maxAge is None:
+            # Default max_age if unspecified (see p3d_plugin.h).
+            from direct.p3d.AppRunner import AppRunner
+            maxAge = AppRunner.P3D_CONTENTS_DEFAULT_MAX_AGE
+
+        # Get the latest possible expiration time, based on the max_age
+        # indication.  Any expiration time later than this is in error.
+        now = int(time.time())
+        self.contentsExpiration = now + maxAge
+
+        if freshDownload:
+            self.contentsSpec.readHash(tempFilename)
+
+            # Update the XML with the new download information.
+            xorig = xcontents.FirstChildElement('orig')
+            while xorig:
+                xcontents.RemoveChild(xorig)
+                xorig = xcontents.FirstChildElement('orig')
+
+            xorig = PandaModules.TiXmlElement('orig')
+            self.contentsSpec.storeXml(xorig)
+            xorig.SetAttribute('expiration', str(self.contentsExpiration))
+
+            xcontents.InsertEndChild(xorig)
+            
+        else:
+            # Read the download hash and expiration time from the XML.
+            expiration = None
+            xorig = xcontents.FirstChildElement('orig')
+            if xorig:
+                self.contentsSpec.loadXml(xorig)
+                expiration = xorig.Attribute('expiration')
+                if expiration:
+                    try:
+                        expiration = int(expiration)
+                    except:
+                        expiration = None
+            if not self.contentsSpec.hash:
+                self.contentsSpec.readHash(tempFilename)
+
+            if expiration is not None:
+                self.contentsExpiration = min(self.contentsExpiration, expiration)
+
         # Look for our own entry in the hosts table.
         if self.hostUrl:
             self.__findHostXml(xcontents)
@@ -257,12 +334,15 @@ class HostInfo:
 
         self.hasContentsFile = True
 
-        # Now copy the contents.xml file into the standard location.
+        # Now save the contents.xml file into the standard location.
         assert self.hostDir
         filename = Filename(self.hostDir, 'contents.xml')
-        if filename != tempFilename:
-            filename.makeDir()
-            tempFilename.copyTo(filename)
+        filename.makeDir()
+        if freshDownload:
+            doc.SaveFile(filename.toOsSpecific())
+        else:
+            if filename != tempFilename:
+                tempFilename.copyTo(filename)
 
         return True
 

+ 10 - 0
direct/src/p3d/PackageInfo.py

@@ -217,6 +217,16 @@ class PackageInfo:
 
         return self.hasPackage
 
+    def hasCurrentDescFile(self):
+        """ Returns true if a desc file file has been successfully
+        read for this package and is still current, false
+        otherwise. """
+
+        if not self.host.hasCurrentContentsFile():
+            return False
+
+        return self.hasDescFile
+
     def downloadDescFile(self, http):
         """ Downloads the desc file for this particular package,
         synchronously, and then reads it.  Returns true on success,

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

@@ -98,7 +98,7 @@ class PackageInstaller(DirectObject):
             """ 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 not self.host.hasCurrentContentsFile():
                 # If the contents file isn't ready yet, we can't check
                 # the desc file yet.
                 return False

+ 2 - 2
direct/src/plugin/load_plugin.cxx

@@ -135,7 +135,7 @@ static void unload_dso();
 bool
 load_plugin(const string &p3d_plugin_filename, 
             const string &contents_filename, const string &host_url, 
-            bool verify_contents, const string &platform,
+            P3D_verify_contents verify_contents, const string &platform,
             const string &log_directory, const string &log_basename,
             bool trusted_environment, bool console_environment,
             const string &root_dir, ostream &logfile) {
@@ -274,7 +274,7 @@ load_plugin(const string &p3d_plugin_filename,
 ////////////////////////////////////////////////////////////////////
 bool
 init_plugin(const string &contents_filename, const string &host_url, 
-            bool verify_contents, const string &platform,
+            P3D_verify_contents verify_contents, const string &platform,
             const string &log_directory, const string &log_basename,
             bool trusted_environment, bool console_environment,
             const string &root_dir, ostream &logfile) {

+ 2 - 2
direct/src/plugin/load_plugin.h

@@ -64,13 +64,13 @@ string get_plugin_basename();
 bool 
 load_plugin(const string &p3d_plugin_filename, 
             const string &contents_filename, const string &host_url,
-            bool verify_contents, const string &platform,
+            P3D_verify_contents verify_contents, const string &platform,
             const string &log_directory, const string &log_basename,
             bool trusted_environment, bool console_environment,
             const string &root_dir, ostream &logfile);
 bool
 init_plugin(const string &contents_filename, const string &host_url, 
-            bool verify_contents, const string &platform,
+            P3D_verify_contents verify_contents, const string &platform,
             const string &log_directory, const string &log_basename,
             bool trusted_environment, bool console_environment,
             const string &root_dir, ostream &logfile);

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

@@ -116,7 +116,7 @@ get_alt_host(const string &alt_host) {
 ////////////////////////////////////////////////////////////////////
 bool P3DHost::
 has_current_contents_file(P3DInstanceManager *inst_mgr) const {
-  if (!inst_mgr->get_verify_contents()) {
+  if (inst_mgr->get_verify_contents() == P3D_VC_none) {
     // If we're not asking to verify contents, then contents.xml files
     // never expire.
     return has_contents_file();

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

@@ -1336,6 +1336,7 @@ make_xml() {
   P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
   xinstance->SetAttribute("root_dir", inst_mgr->get_root_dir());
   xinstance->SetAttribute("log_directory", inst_mgr->get_log_directory());
+  xinstance->SetAttribute("verify_contents", (int)inst_mgr->get_verify_contents());
 
   if (!inst_mgr->get_super_mirror().empty()) {
     xinstance->SetAttribute("super_mirror", inst_mgr->get_super_mirror());

+ 13 - 10
direct/src/plugin/p3dInstanceManager.I

@@ -42,12 +42,13 @@ reconsider_runtime_environment() {
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DInstanceManager::verify_contents
 //       Access: Public
-//  Description: Returns the verify_contents flag.  When this is set
-//               false, it indicates that we don't need to contact the
-//               server to verify that a contents.xml file is fresh
-//               before using it; we should just use it as it is.
+//  Description: Returns the verify_contents setting.  When this is
+//               set to P3D_VC_none, it indicates that we don't need
+//               to contact the server to verify that a contents.xml
+//               file is fresh before using it; we should just use it
+//               as it is.
 ////////////////////////////////////////////////////////////////////
-inline bool P3DInstanceManager::
+inline P3D_verify_contents P3DInstanceManager::
 get_verify_contents() const {
   return _verify_contents;
 }
@@ -55,14 +56,16 @@ get_verify_contents() const {
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DInstanceManager::reset_verify_contents
 //       Access: Public
-//  Description: Resets the verify_contents flag to true.  This should
-//               be done whenever we discover anything needs to be
-//               downloaded.  At this point, we might as well verify
-//               everything.
+//  Description: Resets the verify_contents flag to P3D_VC_normal, if
+//               it is P3D_VC_none.  This should be done whenever we
+//               discover anything needs to be downloaded.  At this
+//               point, we might as well verify everything.
 ////////////////////////////////////////////////////////////////////
 inline void P3DInstanceManager::
 reset_verify_contents() {
-  _verify_contents = true;
+  if (_verify_contents == P3D_VC_none) {
+    _verify_contents = P3D_VC_normal;
+  }
 }
 
 

+ 1 - 1
direct/src/plugin/p3dInstanceManager.cxx

@@ -191,7 +191,7 @@ P3DInstanceManager::
 ////////////////////////////////////////////////////////////////////
 bool P3DInstanceManager::
 initialize(int api_version, const string &contents_filename, 
-           const string &host_url, bool verify_contents,
+           const string &host_url, P3D_verify_contents verify_contents,
            const string &platform, const string &log_directory,
            const string &log_basename, bool trusted_environment,
            bool console_environment, const string &root_dir) {

+ 3 - 3
direct/src/plugin/p3dInstanceManager.h

@@ -52,7 +52,7 @@ private:
 public:
   bool initialize(int api_version, const string &contents_filename,
                   const string &host_url,
-                  bool verify_contents,
+                  P3D_verify_contents verify_contents,
                   const string &platform,
                   const string &log_directory,
                   const string &log_basename,
@@ -62,7 +62,7 @@ public:
 
   inline bool is_initialized() const;
   inline void reconsider_runtime_environment();
-  inline bool get_verify_contents() const;
+  inline P3D_verify_contents get_verify_contents() const;
   inline void reset_verify_contents();
 
   inline int get_api_version() const;
@@ -163,7 +163,7 @@ private:
   string _host_url;
   string _root_dir;
   string _certs_dir;
-  bool _verify_contents;
+  P3D_verify_contents _verify_contents;
   string _platform;
   string _log_directory;
   string _log_basename;

+ 1 - 1
direct/src/plugin/p3dPackage.cxx

@@ -392,7 +392,7 @@ begin_info_download() {
 void P3DPackage::
 download_contents_file() {
   P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
-  if (!_host->has_contents_file()) {
+  if (!_host->has_contents_file() && inst_mgr->get_verify_contents() != P3D_VC_force) {
     // First, read whatever contents file is already on disk.  Maybe
     // it's current enough.
     _host->read_contents_file();

+ 5 - 2
direct/src/plugin/p3dPythonRun.cxx

@@ -1219,14 +1219,17 @@ set_instance_info(P3DCInstance *inst, TiXmlElement *xinstance) {
     log_directory = "";
   }
 
+  int verify_contents = 0;
+  xinstance->Attribute("verify_contents", &verify_contents);
+
   const char *super_mirror = xinstance->Attribute("super_mirror");
   if (super_mirror == NULL) {
     super_mirror = "";
   }
 
   PyObject *result = PyObject_CallMethod
-    (_runner, (char *)"setInstanceInfo", (char *)"sss", root_dir, 
-     log_directory, super_mirror);
+    (_runner, (char *)"setInstanceInfo", (char *)"sssi", root_dir, 
+     log_directory, super_mirror, verify_contents);
 
   if (result == NULL) {
     PyErr_Print();

+ 11 - 1
direct/src/plugin/p3d_plugin.cxx

@@ -35,7 +35,7 @@ LOCK _api_lock;
 
 bool 
 P3D_initialize(int api_version, const char *contents_filename,
-               const char *host_url, bool verify_contents,
+               const char *host_url, P3D_verify_contents verify_contents,
                const char *platform, const char *log_directory,
                const char *log_basename, bool trusted_environment,
                bool console_environment, const char *root_dir) {
@@ -44,6 +44,16 @@ P3D_initialize(int api_version, const char *contents_filename,
     return false;
   }
 
+  if (api_version < 13) {
+    // Prior to version 13, verify_contents was a bool.  Convert
+    // "true" to P3D_VC_normal and "false" to P3D_VC_none.
+    if ((int)verify_contents != 0) {
+      verify_contents = P3D_VC_normal;
+    } else {
+      verify_contents = P3D_VC_none;
+    }
+  }
+
   if (!initialized_lock) {
     INIT_LOCK(_api_lock);
     initialized_lock = true;

+ 16 - 9
direct/src/plugin/p3d_plugin.h

@@ -79,13 +79,20 @@ extern "C" {
    (below). This number will be incremented whenever there are changes
    to any of the interface specifications defined in this header
    file. */
-#define P3D_API_VERSION 12
+#define P3D_API_VERSION 13
 
 /************************ GLOBAL FUNCTIONS **************************/
 
 /* The following interfaces are global to the core API space, as
    opposed to being specific to a particular instance. */
 
+/* This is passed for verify_contents, below. */
+typedef enum {
+  P3D_VC_none,
+  P3D_VC_normal,
+  P3D_VC_force,
+} P3D_verify_contents;
+
 /* This function should be called immediately after the core API is
    loaded.  You should pass P3D_API_VERSION as the first parameter, so
    the DLL can verify that it has been built with the same version of
@@ -98,13 +105,13 @@ extern "C" {
    If host_url is not NULL or empty, it specifies the root URL of
    the download server that provided the contents_filename.
 
-   If verify_contents is true, it means that the download server will
-   be contacted to verify that contents.xml is current, before
-   continuing, for any contents.xml file that is loaded.  If it is
-   false, it means that the contents.xml will be loaded without
-   checking the download server, if possible.  This can be used to
-   minimize unnecessary network operations for standalone
-   applications.  For a web plugin, it should be set true.
+   If verify_contents is P3D_VC_none, then the server will not be
+   contacted unless the current contents.xml cannot be read at all.
+   If it is P3D_VC_normal, the server will be contacted whenever the
+   contents.xml has expired.  If it is P3D_VC_force, each server will
+   be contacted initially in all cases, and subseqeuntly only whenever
+   contents.xml has expired for that server.  Normally, a web plugin
+   should set this to P3D_VC_normal.
 
    If platform is not NULL or empty, it specifies the current platform
    string; otherwise, the compiled-in default is used.  This should
@@ -143,7 +150,7 @@ extern "C" {
    immediately unload the DLL and (if possible) download a new one. */
 typedef bool 
 P3D_initialize_func(int api_version, const char *contents_filename,
-                    const char *host_url, bool verify_contents,
+                    const char *host_url, P3D_verify_contents verify_contents,
                     const char *platform,
                     const char *log_directory, const char *log_basename,
                     bool trusted_environment, bool console_environment,

+ 1 - 1
direct/src/plugin_activex/PPInstance.cpp

@@ -499,7 +499,7 @@ int PPInstance::LoadPlugin( const std::string& dllFilename )
       nout << "Attempting to load core API from " << pathname << "\n";
       string contents_filename = m_rootDir + "/contents.xml";
       if (!load_plugin(pathname, contents_filename, PANDA_PACKAGE_HOST_URL,
-                       true, "", "", "", false, false, 
+                       P3D_VC_normal, "", "", "", false, false, 
                        m_rootDir, nout)) {
         nout << "Unable to launch core API in " << pathname << "\n";
         error = 1;

+ 1 - 1
direct/src/plugin_npapi/ppInstance.cxx

@@ -1554,7 +1554,7 @@ do_load_plugin() {
   nout << "Attempting to load core API from " << pathname << "\n";
   string contents_filename = _root_dir + "/contents.xml";
   if (!load_plugin(pathname, contents_filename, PANDA_PACKAGE_HOST_URL,
-                   true, "", "", "", false, false, 
+                   P3D_VC_normal, "", "", "", false, false, 
                    _root_dir, nout)) {
     nout << "Unable to launch core API in " << pathname << "\n";
     set_failed();

+ 1 - 1
direct/src/plugin_standalone/p3dEmbed.cxx

@@ -112,7 +112,7 @@ run_embedded(streampos read_offset, int argc, char *argv[]) {
         } else if (keyword == "root_dir") {
           root_dir = value;
         } else if (keyword == "verify_contents") {
-          _verify_contents = (bool) atoi(value.c_str());
+          _verify_contents = (P3D_verify_contents)atoi(value.c_str());
         }
       }
       curstr = "";

+ 28 - 13
direct/src/plugin_standalone/panda3d.cxx

@@ -62,7 +62,7 @@ run_command_line(int argc, char *argv[]) {
   // We prefix a "+" sign to tell gnu getopt not to parse options
   // following the first not-option parameter.  (These will be passed
   // into the sub-process.)
-  const char *optstr = "+mu:M:Sp:fw:t:s:o:l:iVUPh";
+  const char *optstr = "+mu:M:Sp:nfw:t:s:o:l:iVUPh";
 
   bool allow_multiple = false;
 
@@ -90,8 +90,12 @@ run_command_line(int argc, char *argv[]) {
       _this_platform = optarg;
       break;
 
+    case 'n':
+      _verify_contents = P3D_VC_normal;
+      break;
+
     case 'f':
-      _verify_contents = true;
+      _verify_contents = P3D_VC_force;
       break;
 
     case 'w':
@@ -356,10 +360,12 @@ get_plugin() {
   bool success = false;
 
   Filename contents_filename = Filename(Filename::from_os_specific(_root_dir), "contents.xml");
-  if (read_contents_file(contents_filename, false)) {
-    if (!_verify_contents || time(NULL) < _contents_expiration) {
-      // Got the file, and it's good.
-      success = true;
+  if (_verify_contents != P3D_VC_force) {
+    if (read_contents_file(contents_filename, false)) {
+      if (_verify_contents == P3D_VC_none || time(NULL) < _contents_expiration) {
+        // Got the file, and it's good.
+        success = true;
+      }
     }
   }
 
@@ -409,7 +415,9 @@ get_plugin() {
       
       // Since we have to download some of it, might as well ask the core
       // API to check all of it.
-      _verify_contents = true;
+      if (_verify_contents == P3D_VC_none) {
+        _verify_contents = P3D_VC_normal;
+      }
       
       // First, download it to a temporary file.
       Filename tempfile = Filename::temporary("", "p3d_");
@@ -529,7 +537,6 @@ read_contents_file(const Filename &contents_filename, bool fresh_download) {
       return false;
     }
     tempfile.rename_to(standard_filename);
-    nout << "rewrote " << standard_filename << "\n";
 
   } else {
     if (contents_filename != standard_filename) {
@@ -538,7 +545,6 @@ read_contents_file(const Filename &contents_filename, bool fresh_download) {
         contents_filename.unlink();
         return false;
       }
-      nout << "moved to " << standard_filename << "\n";
     }
   }
 
@@ -742,7 +748,9 @@ get_core_api() {
 
     // Since we had to download some of it, might as well ask the core
     // API to check all of it.
-    _verify_contents = true;
+    if (_verify_contents == P3D_VC_none) {
+      _verify_contents = P3D_VC_normal;
+    }
   }
 
   // Now we've got the DLL.  Load it.
@@ -844,10 +852,17 @@ usage() {
     << "    to be written.  If this is not specified, the default is to send\n"
     << "    the application output to the console.\n\n"
 
+    << "  -n\n"
+    << "    Allow a network connect to the Panda3D download server, to check\n"
+    << "    if a new version is available (but only if the current version\n"
+    << "    appears to be out-of-date).  The default behavior, if both -n\n"
+    << "    and -f are omitted, is not to contact the server at all, unless\n"
+    << "    the local contents do not exist or cannot be read.\n\n"
+
     << "  -f\n"
-    << "    Force an initial contact of the Panda3D download server, to check\n"
-    << "    if a new version is available.  Normally, this is done only\n"
-    << "    if contents.xml cannot be read.\n\n"
+    << "    Force an initial contact of the Panda3D download server, even\n"
+    << "    if the local contents appear to be current.  This is mainly\n"
+    << "    useful when testing local republishes.\n\n"
 
     << "  -i\n"
     << "    Runs the application interactively.  This requires that the application\n"

+ 1 - 1
direct/src/plugin_standalone/panda3dBase.cxx

@@ -64,7 +64,7 @@ Panda3DBase(bool console_environment) {
   _exit_with_last_instance = true;
   _host_url = PANDA_PACKAGE_HOST_URL;
   _this_platform = DTOOL_PLATFORM;
-  _verify_contents = false;
+  _verify_contents = P3D_VC_none;
   _contents_expiration = 0;
 
   // Seed the lame random number generator in rand(); we use it to

+ 1 - 1
direct/src/plugin_standalone/panda3dBase.h

@@ -73,7 +73,7 @@ protected:
   string _log_dirname;
   string _log_basename;
   string _this_platform;
-  bool _verify_contents;
+  P3D_verify_contents _verify_contents;
   time_t _contents_expiration;
 
   P3D_window_type _window_type;