Browse Source

more cache-busting, keep install directory clean

David Rose 16 years ago
parent
commit
bd71beaf76

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

@@ -1,6 +1,7 @@
-from pandac.PandaModules import TiXmlDocument, HashVal, Filename, PandaSystem, URLSpec, Ramfile
+from pandac.PandaModules import TiXmlDocument, HashVal, Filename, PandaSystem, DocumentSpec, Ramfile
 from direct.p3d.PackageInfo import PackageInfo
 from direct.p3d.PackageInfo import PackageInfo
 from direct.p3d.FileSpec import FileSpec
 from direct.p3d.FileSpec import FileSpec
+import time
 
 
 class HostInfo:
 class HostInfo:
     """ This class represents a particular download host serving up
     """ This class represents a particular download host serving up
@@ -40,11 +41,22 @@ class HostInfo:
             # We've already got one.
             # We've already got one.
             return True
             return True
 
 
-        url = URLSpec(self.hostUrlPrefix + 'contents.xml')
-        print "Downloading %s" % (url)
+        url = self.hostUrlPrefix + 'contents.xml'
+        # Append a uniquifying query string to the URL to force the
+        # download to go all the way through any caches.  We use the
+        # time in seconds; that's unique enough.
+        url += '?' + str(int(time.time()))
+
+        # We might as well explicitly request the cache to be disabled
+        # too, since we have an interface for that via HTTPChannel.
+        request = DocumentSpec(url)
+        request.setCacheControl(DocumentSpec.CCNoCache)
+
+        print "Downloading %s" % (request)
 
 
         rf = Ramfile()
         rf = Ramfile()
-        channel = http.getDocument(url)
+        channel = http.makeChannel(False)
+        channel.getDocument(request)
         if not channel.downloadToRam(rf):
         if not channel.downloadToRam(rf):
             print "Unable to download %s" % (url)
             print "Unable to download %s" % (url)
 
 
@@ -145,6 +157,24 @@ class HostInfo:
 
 
         return package
         return package
 
 
+    def getPackages(self, name, platform = None):
+        """ Returns a list of PackageInfo objects that match the
+        indicated name and/or platform, with no particular regards to
+        version. """
+
+        assert self.hasContentsFile
+
+        packages = []
+        for (pn, version), platforms in self.packages.items():
+            if pn != name:
+                continue
+
+            package = self.getPackage(name, version, platform = platform)
+            if package:
+                packages.append(package)
+
+        return packages
+
     def __determineHostDir(self, appRunner):
     def __determineHostDir(self, appRunner):
         """ Hashes the host URL into a (mostly) unique directory
         """ Hashes the host URL into a (mostly) unique directory
         string, which will be the root of the host's install tree.
         string, which will be the root of the host's install tree.

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

@@ -141,10 +141,14 @@ class PackageInfo:
 
 
         filename = Filename(self.packageDir, self.descFileBasename)
         filename = Filename(self.packageDir, self.descFileBasename)
         filename.makeDir()
         filename.makeDir()
+        filename.unlink()
         f = open(filename.toOsSpecific(), 'wb')
         f = open(filename.toOsSpecific(), 'wb')
         f.write(rf.getData())
         f.write(rf.getData())
         f.close()
         f.close()
 
 
+        # Now that we've written the desc file, make it read-only.
+        os.chmod(filename.toOsSpecific(), 0444)
+
         try:
         try:
             self.readDescFile()
             self.readDescFile()
         except ValueError:
         except ValueError:
@@ -274,12 +278,52 @@ class PackageInfo:
             # There are no patches to download, oh well.  Stick with
             # There are no patches to download, oh well.  Stick with
             # plan B as the only plan.
             # plan B as the only plan.
             self.installPlans = [planB]
             self.installPlans = [planB]
+
+    def __scanDirectoryRecursively(self, dirname):
+        """ Generates a list of Filename objects: all of the files
+        (not directories) within and below the indicated dirname. """
         
         
+        contents = []
+        for dirpath, dirnames, filenames in os.walk(dirname.toOsSpecific()):
+            dirpath = Filename.fromOsSpecific(dirpath)
+            if dirpath == dirname:
+                dirpath = Filename('')
+            else:
+                dirpath.makeRelativeTo(dirname)
+            for filename in filenames:
+                contents.append(Filename(dirpath, filename))
+        return contents
+
+    def __removeFileFromList(self, contents, filename):
+        """ Removes the indicated filename from the given list, if it is
+        present.  """
+        try:
+            contents.remove(Filename(filename))
+        except ValueError:
+            pass
 
 
     def __checkArchiveStatus(self):
     def __checkArchiveStatus(self):
         """ Returns true if the archive and all extractable files are
         """ Returns true if the archive and all extractable files are
         already correct on disk, false otherwise. """
         already correct on disk, false otherwise. """
-        
+
+        # Get a list of all of the files in the directory, so we can
+        # remove files that don't belong.
+        contents = self.__scanDirectoryRecursively(self.packageDir)
+        self.__removeFileFromList(contents, self.uncompressedArchive.filename)
+        self.__removeFileFromList(contents, self.descFileBasename)
+        for file in self.extracts:
+            self.__removeFileFromList(contents, file.filename)
+
+        # Now, any files that are still in the contents list don't
+        # belong.  It's important to remove these files before we
+        # start verifying the files that we expect to find here, in
+        # case there is a problem with ambiguous filenames or
+        # something (e.g. case insensitivity).
+        for filename in contents:
+            print "Removing %s" % (filename)
+            pathname = Filename(self.packageDir, filename)
+            pathname.unlink()
+            
         allExtractsOk = True
         allExtractsOk = True
         if not self.uncompressedArchive.quickVerify(self.packageDir):
         if not self.uncompressedArchive.quickVerify(self.packageDir):
             #print "File is incorrect: %s" % (self.uncompressedArchive.filename)
             #print "File is incorrect: %s" % (self.uncompressedArchive.filename)
@@ -292,6 +336,10 @@ class PackageInfo:
                     allExtractsOk = False
                     allExtractsOk = False
                     break
                     break
 
 
+        if allExtractsOk:
+            print "All %s extracts of %s seem good." % (
+                len(self.extracts), self.packageName)
+
         return allExtractsOk
         return allExtractsOk
 
 
     def __updateStepProgress(self, step):
     def __updateStepProgress(self, step):
@@ -451,6 +499,7 @@ class PackageInfo:
 
 
         sourcePathname = Filename(self.packageDir, self.compressedArchive.filename)
         sourcePathname = Filename(self.packageDir, self.compressedArchive.filename)
         targetPathname = Filename(self.packageDir, self.uncompressedArchive.filename)
         targetPathname = Filename(self.packageDir, self.uncompressedArchive.filename)
+        targetPathname.unlink()
         print "Uncompressing %s to %s" % (sourcePathname, targetPathname)
         print "Uncompressing %s to %s" % (sourcePathname, targetPathname)
         decompressor = Decompressor()
         decompressor = Decompressor()
         decompressor.initiate(sourcePathname, targetPathname)
         decompressor.initiate(sourcePathname, targetPathname)
@@ -473,6 +522,9 @@ class PackageInfo:
                 self.uncompressedArchive.filename)
                 self.uncompressedArchive.filename)
             return False
             return False
 
 
+        # Now that we've verified the archive, make it read-only.
+        os.chmod(targetPathname.toOsSpecific(), 0444)
+
         # Now we can safely remove the compressed archive.
         # Now we can safely remove the compressed archive.
         sourcePathname.unlink()
         sourcePathname.unlink()
         return True
         return True
@@ -503,6 +555,7 @@ class PackageInfo:
                 continue
                 continue
 
 
             targetPathname = Filename(self.packageDir, file.filename)
             targetPathname = Filename(self.packageDir, file.filename)
+            targetPathname.unlink()
             if not mf.extractSubfile(i, targetPathname):
             if not mf.extractSubfile(i, targetPathname):
                 print "Couldn't extract: %s" % (file.filename)
                 print "Couldn't extract: %s" % (file.filename)
                 allExtractsOk = False
                 allExtractsOk = False
@@ -513,8 +566,8 @@ class PackageInfo:
                 allExtractsOk = False
                 allExtractsOk = False
                 continue
                 continue
 
 
-            # Make sure it's executable.
-            os.chmod(targetPathname.toOsSpecific(), 0755)
+            # Make sure it's executable, and not writable.
+            os.chmod(targetPathname.toOsSpecific(), 0555)
 
 
             step.bytesDone += file.size
             step.bytesDone += file.size
             self.__updateStepProgress(step)
             self.__updateStepProgress(step)

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

@@ -1993,6 +1993,15 @@ class Packager:
 
 
         host = appRunner.getHost(hostUrl)
         host = appRunner.getHost(hostUrl)
         package = host.getPackage(packageName, version, platform = platform)
         package = host.getPackage(packageName, version, platform = platform)
+        if not package and version is None:
+            # With no version specified, find the best matching version.
+            packages = host.getPackages(packageName, platform = platform)
+            self.__sortPackageInfos(packages)
+            for p in packages:
+                if p and self.__packageIsValid(p, requires):
+                    package = p
+                    break
+            
         if not package or not package.importDescFile:
         if not package or not package.importDescFile:
             return None
             return None
 
 
@@ -2022,6 +2031,19 @@ class Packager:
 
 
         return map(lambda t: t[1], tuples)
         return map(lambda t: t[1], tuples)
 
 
+    def __sortPackageInfos(self, packages):
+        """ Given a list of PackageInfos retrieved from a Host, sorts
+        them in reverse order by version, so that the highest-numbered
+        versions appear first in the list. """
+
+        tuples = []
+        for package in packages:
+            version = self.__makeVersionTuple(package.packageVersion)
+            tuples.append((version, file))
+        tuples.sort(reverse = True)
+
+        return map(lambda t: t[1], tuples)
+
     def __makeVersionTuple(self, version):
     def __makeVersionTuple(self, version):
         """ Converts a version string into a tuple for sorting, by
         """ Converts a version string into a tuple for sorting, by
         separating out numbers into separate numeric fields, so that
         separating out numbers into separate numeric fields, so that

+ 5 - 1
direct/src/p3d/packp3d.py

@@ -148,7 +148,11 @@ def makePackedApp(args):
         packager.setup()
         packager.setup()
         packager.beginPackage(appBase, p3dApplication = True)
         packager.beginPackage(appBase, p3dApplication = True)
         for requireName in requires:
         for requireName in requires:
-            packager.do_require(requireName)
+            tokens = requireName.split(',')
+            while len(tokens) < 3:
+                tokens.append(None)
+            name, version, host = tokens
+            packager.do_require(name, version = version, host = host)
 
 
         if autoStart:
         if autoStart:
             packager.do_config(auto_start = True)
             packager.do_config(auto_start = True)

+ 3 - 0
direct/src/plugin/mkdir_complete.cxx

@@ -124,6 +124,9 @@ mkdir_complete(const string &dirname, ostream &logfile) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool
 bool
 mkfile_complete(const string &filename, ostream &logfile) {
 mkfile_complete(const string &filename, ostream &logfile) {
+  // Make sure we delete any previously-existing file first.
+  unlink(filename.c_str());
+
 #ifdef _WIN32
 #ifdef _WIN32
   HANDLE file = CreateFile(filename.c_str(), GENERIC_READ | GENERIC_WRITE,
   HANDLE file = CreateFile(filename.c_str(), GENERIC_READ | GENERIC_WRITE,
                            FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
                            FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,

+ 83 - 0
direct/src/plugin/p3dInstanceManager.cxx

@@ -867,6 +867,89 @@ scan_directory(const string &dirname, vector<string> &contents) {
 #endif  // _WIN32
 #endif  // _WIN32
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DInstanceManager::scan_directory_recursively
+//       Access: Public, Static
+//  Description: Fills up the indicated vector with the list of all
+//               files (but not directories) rooted at the indicated
+//               dirname and below.  The filenames generated are
+//               relative to the root of the dirname, with slashes
+//               (not backslashes) as the directory separator
+//               character.
+//
+//               Returns true on success, false if the original
+//               dirname wasn't a directory or something like that.
+////////////////////////////////////////////////////////////////////
+bool P3DInstanceManager::
+scan_directory_recursively(const string &dirname, vector<string> &contents,
+                           const string &prefix) {
+  vector<string> dir_contents;
+  if (!scan_directory(dirname, dir_contents)) {
+    // Apparently dirname wasn't a directory.
+    return false;
+  }
+
+  // Walk through the contents of dirname.
+  vector<string>::const_iterator si;
+  for (si = dir_contents.begin(); si != dir_contents.end(); ++si) {
+    // Here's a particular file within dirname.  Is it another
+    // directory, or is it a regular file?
+    string pathname = dirname + "/" + (*si);
+    string rel_filename = prefix + (*si);
+    if (scan_directory_recursively(pathname, contents, rel_filename + "/")) {
+      // It's a directory, and it's just added its results to the
+      // contents.
+
+    } else {
+      // It's not a directory, so assume it's an ordinary file, and
+      // add it to the contents.
+      contents.push_back(rel_filename);
+    }
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DInstanceManager::remove_file_from_list
+//       Access: Public, Static
+//  Description: Removes the first instance of the indicated file
+//               from the given list.  Returns true if removed, false
+//               if it was not found.
+//
+//               On Windows, the directory separator characters are
+//               changed from backslash to forward slash before
+//               searching in the list; so it is assumed that the list
+//               contains filenames with a forward slash used as a
+//               separator.
+////////////////////////////////////////////////////////////////////
+bool P3DInstanceManager::
+remove_file_from_list(vector<string> &contents, const string &filename) {
+#ifdef _WIN32
+  // Convert backslashes to slashes.
+  string clean_filename;
+  for (string::iterator pi = filename.begin(); pi != filename.end(); ++pi) {
+    if ((*pi) == '\\') {
+      clean_filename += '/';
+    } else {
+      clean_filename += (*pi);
+    }
+  }
+#else
+  const string &clean_filename = filename;
+#endif  // _WIN32
+
+  vector<string>::iterator ci;
+  for (ci = contents.begin(); ci != contents.end(); ++ci) {
+    if ((*ci) == clean_filename) {
+      contents.erase(ci);
+      return true;
+    }
+  }
+
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DInstanceManager::get_global_ptr
 //     Function: P3DInstanceManager::get_global_ptr
 //       Access: Public, Static
 //       Access: Public, Static

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

@@ -108,6 +108,9 @@ public:
 
 
   static inline char encode_hexdigit(int c);
   static inline char encode_hexdigit(int c);
   static bool scan_directory(const string &dirname, vector<string> &contents);
   static bool scan_directory(const string &dirname, vector<string> &contents);
+  static bool scan_directory_recursively(const string &dirname, vector<string> &contents,
+                                         const string &prefix = "");
+  static bool remove_file_from_list(vector<string> &contents, const string &filename);
 private:
 private:
   // The notify thread.  This thread runs only for the purpose of
   // The notify thread.  This thread runs only for the purpose of
   // generating asynchronous notifications of requests, to callers who
   // generating asynchronous notifications of requests, to callers who

+ 3 - 0
direct/src/plugin/p3dMultifileReader.cxx

@@ -135,7 +135,10 @@ extract_all(const string &to_dir,
     utb.actime = time(NULL);
     utb.actime = time(NULL);
     utb.modtime = s._timestamp;
     utb.modtime = s._timestamp;
     utime(output_pathname.c_str(), &utb);
     utime(output_pathname.c_str(), &utb);
+
 #ifndef _WIN32
 #ifndef _WIN32
+    // Be sure to execute permissions on the file, in case it's a
+    // program or something.
     chmod(output_pathname.c_str(), 0555);
     chmod(output_pathname.c_str(), 0555);
 #endif
 #endif
 
 

+ 51 - 9
direct/src/plugin/p3dPackage.cxx

@@ -246,8 +246,14 @@ download_contents_file() {
     return;
     return;
   }
   }
 
 
-  string url = _host->get_host_url_prefix();
-  url += "contents.xml";
+  // Get the URL for contents.xml.
+  ostringstream strm;
+  strm << _host->get_host_url_prefix() << "contents.xml";
+  // Append a uniquifying query string to the URL to force the
+  // download to go all the way through any caches.  We use the time
+  // in seconds; that's unique enough.
+  strm << "?" << time(NULL);
+  string url = strm.str();
 
 
   // Download contents.xml to a temporary filename first, in case
   // Download contents.xml to a temporary filename first, in case
   // multiple packages are downloading it simultaneously.
   // multiple packages are downloading it simultaneously.
@@ -359,6 +365,11 @@ desc_file_download_finished(bool success) {
     return;
     return;
   }
   }
 
 
+#ifndef _WIN32
+  // Now that we've downloaded the desc file, make it read-only.
+  chmod(_desc_file_pathname.c_str(), 0444);
+#endif
+
   if (_package_solo) {
   if (_package_solo) {
     // No need to load it: the desc file *is* the package.
     // No need to load it: the desc file *is* the package.
     report_done(true);
     report_done(true);
@@ -426,6 +437,31 @@ got_desc_file(TiXmlDocument *doc, bool freshly_downloaded) {
     extract = extract->NextSiblingElement("extract");
     extract = extract->NextSiblingElement("extract");
   }
   }
 
 
+  // Get a list of all of the files in the directory, so we can remove
+  // files that don't belong.
+  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
+  vector<string> contents;
+  inst_mgr->scan_directory_recursively(_package_dir, contents);
+
+  inst_mgr->remove_file_from_list(contents, _desc_file_basename);
+  inst_mgr->remove_file_from_list(contents, _uncompressed_archive.get_filename());
+  Extracts::iterator ei;
+  for (ei = _extracts.begin(); ei != _extracts.end(); ++ei) {
+    inst_mgr->remove_file_from_list(contents, (*ei).get_filename());
+  }
+
+  // Now, any files that are still in the contents list don't belong.
+  // It's important to remove these files before we start verifying
+  // the files that we expect to find here, in case there is a problem
+  // with ambiguous filenames or something (e.g. case insensitivity).
+  vector<string>::iterator ci;
+  for (ci = contents.begin(); ci != contents.end(); ++ci) {
+    string filename = (*ci);
+    nout << "Removing " << filename << "\n";
+    string pathname = _package_dir + "/" + filename;
+    unlink(pathname.c_str());
+  }
+
   // Verify the uncompressed archive.
   // Verify the uncompressed archive.
   bool all_extracts_ok = true;
   bool all_extracts_ok = true;
   if (!_uncompressed_archive.quick_verify(_package_dir)) {
   if (!_uncompressed_archive.quick_verify(_package_dir)) {
@@ -434,10 +470,9 @@ got_desc_file(TiXmlDocument *doc, bool freshly_downloaded) {
   }
   }
 
 
   // Verify all of the extracts.
   // Verify all of the extracts.
-  Extracts::iterator ci;
-  for (ci = _extracts.begin(); ci != _extracts.end() && all_extracts_ok; ++ci) {
-    if (!(*ci).quick_verify(_package_dir)) {
-      nout << "File is incorrect: " << (*ci).get_filename() << "\n";
+  for (ei = _extracts.begin(); ei != _extracts.end() && all_extracts_ok; ++ei) {
+    if (!(*ei).quick_verify(_package_dir)) {
+      nout << "File is incorrect: " << (*ei).get_filename() << "\n";
       all_extracts_ok = false;
       all_extracts_ok = false;
     }
     }
   }
   }
@@ -446,6 +481,7 @@ got_desc_file(TiXmlDocument *doc, bool freshly_downloaded) {
     // Great, we're ready to begin.
     // Great, we're ready to begin.
     nout << "All " << _extracts.size() << " extracts of " << _package_name
     nout << "All " << _extracts.size() << " extracts of " << _package_name
          << " seem good.\n";
          << " seem good.\n";
+
     report_done(true);
     report_done(true);
 
 
   } else {
   } else {
@@ -687,6 +723,12 @@ uncompress_archive() {
     return;
     return;
   }
   }
 
 
+#ifndef _WIN32
+  // Now that we've verified the archive, make it read-only.
+  chmod(target_pathname.c_str(), 0444);
+#endif
+
+  // Now we can safely remove the compressed archive.
   unlink(source_pathname.c_str());
   unlink(source_pathname.c_str());
 
 
   // All done uncompressing.
   // All done uncompressing.
@@ -828,9 +870,9 @@ start_download(P3DPackage::DownloadType dtype, const string &url,
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool P3DPackage::
 bool P3DPackage::
 is_extractable(const string &filename) const {
 is_extractable(const string &filename) const {
-  Extracts::const_iterator ci;
-  for (ci = _extracts.begin(); ci != _extracts.end(); ++ci) {
-    if ((*ci).get_filename() == filename) {
+  Extracts::const_iterator ei;
+  for (ei = _extracts.begin(); ei != _extracts.end(); ++ei) {
+    if ((*ei).get_filename() == filename) {
       return true;
       return true;
     }
     }
   }
   }

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

@@ -112,7 +112,15 @@ begin() {
     if (!url.empty() && url[url.length() - 1] != '/') {
     if (!url.empty() && url[url.length() - 1] != '/') {
       url += '/';
       url += '/';
     }
     }
-    url += "contents.xml";
+    ostringstream strm;
+    strm << url << "contents.xml";
+
+    // Append a uniquifying query string to the URL to force the
+    // download to go all the way through any caches.  We use the time
+    // in seconds; that's unique enough.
+    strm << "?" << time(NULL);
+    url = strm.str();
+
     PPDownloadRequest *req = new PPDownloadRequest(PPDownloadRequest::RT_contents_file);
     PPDownloadRequest *req = new PPDownloadRequest(PPDownloadRequest::RT_contents_file);
     start_download(url, req);
     start_download(url, req);
   }
   }

+ 14 - 3
direct/src/plugin_standalone/panda3d.cxx

@@ -382,11 +382,22 @@ get_plugin(const string &download_url, const string &this_platform,
   }
   }
 
 
   // Couldn't read it, so go get it.
   // Couldn't read it, so go get it.
-  string url = download_url;
-  url += "contents.xml";
+  ostringstream strm;
+  strm << download_url << "contents.xml";
+  // Append a uniquifying query string to the URL to force the
+  // download to go all the way through any caches.  We use the time
+  // in seconds; that's unique enough.
+  strm << "?" << time(NULL);
+  string url = strm.str();
+
+  // We might as well explicitly request the cache to be disabled too,
+  // since we have an interface for that via HTTPChannel.
+  DocumentSpec request(url);
+  request.set_cache_control(DocumentSpec::CC_no_cache);
   
   
   HTTPClient *http = HTTPClient::get_global_ptr();
   HTTPClient *http = HTTPClient::get_global_ptr();
-  PT(HTTPChannel) channel = http->get_document(url);
+  PT(HTTPChannel) channel = http->make_channel(false);
+  channel->get_document(request);
 
 
   // First, download it to a temporary file.
   // First, download it to a temporary file.
   Filename tempfile = Filename::temporary("", "p3d_");
   Filename tempfile = Filename::temporary("", "p3d_");