2
0
Эх сурвалжийг харах

more towards self-downloading

David Rose 16 жил өмнө
parent
commit
652ca28703

+ 4 - 2
direct/src/plugin/Sources.pp

@@ -1,9 +1,9 @@
 // This directory is still experimental.  Define HAVE_P3D_PLUGIN in
 // This directory is still experimental.  Define HAVE_P3D_PLUGIN in
 // your Config.pp to build it.
 // your Config.pp to build it.
-#define BUILD_DIRECTORY $[and $[HAVE_P3D_PLUGIN],$[HAVE_PYTHON],$[HAVE_TINYXML]]
+#define BUILD_DIRECTORY $[and $[HAVE_P3D_PLUGIN],$[HAVE_PYTHON],$[HAVE_TINYXML],$[HAVE_OPENSSL],$[HAVE_ZLIB]]
 
 
 #begin lib_target
 #begin lib_target
-  #define USE_PACKAGES tinyxml
+  #define USE_PACKAGES tinyxml openssl zlib
   #define TARGET p3d_plugin
   #define TARGET p3d_plugin
 
 
   #define COMBINED_SOURCES \
   #define COMBINED_SOURCES \
@@ -18,6 +18,7 @@
     p3dFileDownload.h p3dFileDownload.I \
     p3dFileDownload.h p3dFileDownload.I \
     p3dInstance.h p3dInstance.I \
     p3dInstance.h p3dInstance.I \
     p3dInstanceManager.h p3dInstanceManager.I \
     p3dInstanceManager.h p3dInstanceManager.I \
+    p3dPackage.h p3dPackage.I \
     p3dSession.h p3dSession.I
     p3dSession.h p3dSession.I
 
 
   #define INCLUDED_SOURCES \
   #define INCLUDED_SOURCES \
@@ -26,6 +27,7 @@
     p3dFileDownload.cxx \
     p3dFileDownload.cxx \
     p3dInstance.cxx \
     p3dInstance.cxx \
     p3dInstanceManager.cxx \
     p3dInstanceManager.cxx \
+    p3dPackage.cxx \
     p3dSession.cxx
     p3dSession.cxx
 
 
   #define INSTALL_HEADERS \
   #define INSTALL_HEADERS \

+ 190 - 0
direct/src/plugin/make_package.py

@@ -0,0 +1,190 @@
+#! /bin/env python
+
+"""
+This command is used to build a downloadable package for the p3d
+plugin to retrieve and install.  It examines the files in the current
+directory, assumes they are all intended to be part of the package,
+and constructs the necessary package xml file and archive file for
+hosting on the web serevr.
+
+make_package.py [opts]
+
+Options:
+
+  -d stage_dir
+
+     Specify the staging directory.  This is a temporary directory on
+     the local machine that will be filled with the contents of the
+     package directory for the web server.
+
+  -p package_name
+  -v package_version
+
+     Specify the name and version of the package to build.
+
+"""
+
+import sys
+import getopt
+import os
+import tarfile
+import gzip
+import stat
+import md5
+
+import direct
+from pandac.PandaModules import *
+
+class ArgumentError(AttributeError):
+    pass
+
+class FileSpec:
+    def __init__(self, filename, pathname):
+        self.filename = filename
+        self.pathname = pathname
+        self.size = 0
+        self.timestamp = 0
+        self.hash = None
+
+        s = os.stat(self.pathname)
+        self.size = s[stat.ST_SIZE]
+        self.timestamp = s[stat.ST_MTIME]
+
+        m = md5.new()
+        f = open(self.pathname, 'rb')
+        data = f.read(4096)
+        while data:
+            m.update(data)
+            data = f.read(4096)
+        f.close()
+
+        self.hash = m.hexdigest()
+
+    def get_params(self):
+        return 'filename="%s" size=%s timestamp=%s hash="%s"' % (
+            self.filename, self.size, self.timestamp, self.hash)
+
+class PackageMaker:
+    def __init__(self):
+        self.startDir = None
+        self.stageDir = None
+        self.packageName = None
+        self.packageVersion = None
+
+
+    def build(self):
+        if not self.startDir:
+            raise ArgumentError, "Start directory not specified."
+        if not self.stageDir:
+            raise ArgumentError, "Stage directory not specified."
+        if not self.packageName or not self.packageVersion:
+            raise ArgumentError, "Package name and version not specified."
+
+        self.packageFullname = '%s_%s' % (
+            self.packageName, self.packageVersion)
+
+        self.cleanDir(self.stageDir)
+
+        uncompressedArchiveBasename = '%s.tar' % (self.packageFullname)
+        uncompressedArchivePathname = os.path.join(self.stageDir, uncompressedArchiveBasename)
+        self.archive = tarfile.open(uncompressedArchivePathname, 'w')
+
+        self.components = []
+
+        self.addComponents()
+        self.archive.close()
+
+        uncompressedArchive = FileSpec(uncompressedArchiveBasename, uncompressedArchivePathname)
+
+        compressedArchiveBasename = '%s.tgz' % (self.packageFullname)
+        compressedArchivePathname = os.path.join(self.stageDir, compressedArchiveBasename)
+
+        print "\ncompressing"
+        f = open(uncompressedArchivePathname, 'rb')
+        gz = gzip.open(compressedArchivePathname, 'w', 9)
+        data = f.read(4096)
+        while data:
+            gz.write(data)
+            data = f.read(4096)
+        gz.close()
+        f.close()
+
+        compressedArchive = FileSpec(compressedArchiveBasename, compressedArchivePathname)
+
+        os.unlink(uncompressedArchivePathname)
+
+        descFileBasename = '%s.xml' % (self.packageFullname)
+        descFilePathname = os.path.join(self.stageDir, descFileBasename)
+
+        f = open(descFilePathname, 'w')
+        print >> f, '<?xml version="1.0" ?>'
+        print >> f, ''
+        print >> f, '<package name="%s" version="%s">' % (self.packageName, self.packageVersion)
+        print >> f, '  <uncompressed_archive %s />' % (uncompressedArchive.get_params())
+        print >> f, '  <compressed_archive %s />' % (compressedArchive.get_params())
+        for file in self.components:
+            print >> f, '  <component %s />' % (file.get_params())
+        print >> f, '</package>'
+        f.close()
+        
+
+    def cleanDir(self, dirname):
+        """ Remove all the files in the named directory.  Does not
+        operate recursively. """
+
+        for filename in os.listdir(dirname):
+            pathname = os.path.join(dirname, filename)
+            try:
+                os.unlink(pathname)
+            except OSError:
+                pass
+
+    def addComponents(self):
+        """ Walks through all the files in the start directory and
+        adds them to the archive.  Recursively visits
+        sub-directories. """
+
+        startDir = self.startDir
+        if startDir.endswith(os.altsep) or startDir.endswith(os.sep):
+            startDir = startDir[:-1]
+        prefix = startDir + os.sep
+        for dirpath, dirnames, filenames in os.walk(startDir):
+            if dirpath == startDir:
+                localpath = ''
+            else:
+                assert dirpath.startswith(prefix)
+                localpath = dirpath[len(prefix):]
+
+            for basename in filenames:
+                file = FileSpec(os.path.join(localpath, basename),
+                                os.path.join(startDir, basename))
+                print file.filename
+                self.components.append(file)
+                self.archive.add(file.pathname, file.filename, recursive = False)
+                
+def makePackage(args):
+    opts, args = getopt.getopt(args, 'd:p:v:h')
+
+    pm = PackageMaker()
+    pm.startDir = '.'
+    for option, value in opts:
+        if option == '-d':
+            pm.stageDir = Filename.fromOsSpecific(value).toOsSpecific()
+        elif option == '-p':
+            pm.packageName = value
+        elif option == '-v':
+            pm.packageVersion = value
+            
+        elif option == '-h':
+            print __doc__
+            sys.exit(1)
+
+    pm.build()
+        
+
+if __name__ == '__main__':
+    try:
+        makePackage(sys.argv[1:])
+    except ArgumentError, e:
+        print e.args[0]
+        sys.exit(1)

+ 22 - 2
direct/src/plugin/p3dDownload.cxx

@@ -25,7 +25,8 @@ P3DDownload() {
   _http_status_code = 0;
   _http_status_code = 0;
   _total_data = 0;
   _total_data = 0;
   _total_expected_data = 0;
   _total_expected_data = 0;
-
+  
+  _canceled = false;
   _download_id = 0;
   _download_id = 0;
 }
 }
 
 
@@ -48,6 +49,21 @@ set_url(const string &url) {
   _url = url;
   _url = url;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DDownload::cancel
+//       Access: Public
+//  Description: Cancels a running download.  download_finished() will
+//               not be called, but the P3DDownload object itself will
+//               eventually be deleted by its owning P3DInstance.
+////////////////////////////////////////////////////////////////////
+void P3DDownload::
+cancel() {
+  _canceled = true;
+  if (_status == P3D_RC_in_progress) {
+    _status = P3D_RC_generic_error;
+  }    
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DDownload::feed_url_stream
 //     Function: P3DDownload::feed_url_stream
 //       Access: Public
 //       Access: Public
@@ -60,12 +76,16 @@ feed_url_stream(P3D_result_code result_code,
                 size_t total_expected_data,
                 size_t total_expected_data,
                 const unsigned char *this_data, 
                 const unsigned char *this_data, 
                 size_t this_data_size) {
                 size_t this_data_size) {
+  if (_canceled) {
+    return false;
+  }
+
   _status = result_code;
   _status = result_code;
   _http_status_code = http_status_code;
   _http_status_code = http_status_code;
 
 
   if (this_data_size != 0) {
   if (this_data_size != 0) {
     if (!receive_data(this_data, this_data_size)) {
     if (!receive_data(this_data, this_data_size)) {
-      // Failure writing to disk.
+      // Failure writing to disk or some such.
       _status = P3D_RC_generic_error;
       _status = P3D_RC_generic_error;
       download_finished(false);
       download_finished(false);
       return false;
       return false;

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

@@ -37,6 +37,8 @@ public:
   inline bool get_download_finished() const;
   inline bool get_download_finished() const;
   inline bool get_download_success() const;
   inline bool get_download_success() const;
 
 
+  void cancel();
+
 public:
 public:
   // These are intended to be called only by P3DInstance.
   // These are intended to be called only by P3DInstance.
   inline void set_download_id(int download_id);
   inline void set_download_id(int download_id);
@@ -62,6 +64,7 @@ protected:
   size_t _total_expected_data;
   size_t _total_expected_data;
 
 
 private:
 private:
+  bool _canceled;
   int _download_id;
   int _download_id;
   string _url;
   string _url;
 };
 };

+ 39 - 1
direct/src/plugin/p3dInstanceManager.I

@@ -13,13 +13,51 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 
 
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DInstanceManager::get_root_dir
+//       Access: Public
+//  Description: Returns the root directory into which all the P3D
+//               runtime files are downloaded and installed.  This
+//               must be a writable directory or nothing will work.
+////////////////////////////////////////////////////////////////////
+inline const string &P3DInstanceManager::
+get_root_dir() const {
+  return _root_dir;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DInstanceManager::get_download_url
+//       Access: Public
+//  Description: Returns the URL of the download server.  All
+//               downloadable files will be retrieved from various
+//               subdirectories of this URL root.
+////////////////////////////////////////////////////////////////////
+inline const string &P3DInstanceManager::
+get_download_url() const {
+  return _download_url;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DInstanceManager::get_command_instance
+//       Access: Public
+//  Description: Returns a global P3DInstance object that exists for
+//               the lifetime of the instance manager.  This instance
+//               object has no reality onscreen, but can be used as a
+//               download host for downloading files that aren't
+//               specifically needed by any user-created instance.
+////////////////////////////////////////////////////////////////////
+inline P3DInstance *P3DInstanceManager::
+get_command_instance() const {
+  return _command_instance;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DInstanceManager::get_num_instances
 //     Function: P3DInstanceManager::get_num_instances
 //       Access: Public
 //       Access: Public
 //  Description: Returns the number of instances currently running
 //  Description: Returns the number of instances currently running
 //               within the world.
 //               within the world.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-INLINE int P3DInstanceManager::
+inline int P3DInstanceManager::
 get_num_instances() const {
 get_num_instances() const {
   return _instances.size();
   return _instances.size();
 }
 }

+ 58 - 3
direct/src/plugin/p3dInstanceManager.cxx

@@ -15,6 +15,7 @@
 #include "p3dInstanceManager.h"
 #include "p3dInstanceManager.h"
 #include "p3dInstance.h"
 #include "p3dInstance.h"
 #include "p3dSession.h"
 #include "p3dSession.h"
+#include "p3dPackage.h"
 
 
 P3DInstanceManager *P3DInstanceManager::_global_ptr;
 P3DInstanceManager *P3DInstanceManager::_global_ptr;
 
 
@@ -35,6 +36,8 @@ P3DInstanceManager() {
   INIT_LOCK(_request_ready_lock);
   INIT_LOCK(_request_ready_lock);
   pthread_cond_init(&_request_ready_cvar, NULL);
   pthread_cond_init(&_request_ready_cvar, NULL);
 #endif
 #endif
+
+  _command_instance = NULL;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -44,7 +47,14 @@ P3DInstanceManager() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 P3DInstanceManager::
 P3DInstanceManager::
 ~P3DInstanceManager() {
 ~P3DInstanceManager() {
-  // Actually, the destructor is never called.
+  // Actually, this destructor is never called, since this is a global
+  // object that never gets deleted.
+
+  assert(_instances.empty());
+  assert(_sessions.empty());
+
+  delete _command_instance;
+
 #ifdef _WIN32
 #ifdef _WIN32
   CloseHandle(_request_ready);
   CloseHandle(_request_ready);
 #else
 #else
@@ -63,6 +73,8 @@ P3DInstanceManager::
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool P3DInstanceManager::
 bool P3DInstanceManager::
 initialize() {
 initialize() {
+  _root_dir = "c:/cygwin/home/drose/p3ddir";
+  _download_url = "http://10.196.143.118/~drose/";
   return true;
   return true;
 }
 }
 
 
@@ -109,6 +121,8 @@ create_instance(P3D_request_ready_func *func,
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void P3DInstanceManager::
 void P3DInstanceManager::
 finish_instance(P3DInstance *inst) {
 finish_instance(P3DInstance *inst) {
+  assert(inst != _command_instance);
+
   Instances::iterator ii;
   Instances::iterator ii;
   ii = _instances.find(inst);
   ii = _instances.find(inst);
   assert(ii != _instances.end());
   assert(ii != _instances.end());
@@ -145,6 +159,10 @@ check_request() {
     }
     }
   }
   }
 
 
+  if (_command_instance->has_request()) {
+    return _command_instance;
+  }
+
   return NULL;
   return NULL;
 }
 }
 
 
@@ -164,10 +182,10 @@ wait_request() {
   int seq = _request_seq;
   int seq = _request_seq;
 
 
   while (true) {
   while (true) {
-    if (_instances.empty()) {
+    if (check_request() != (P3DInstance *)NULL) {
       return;
       return;
     }
     }
-    if (check_request() != (P3DInstance *)NULL) {
+    if (_instances.empty()) {
       return;
       return;
     }
     }
     
     
@@ -187,6 +205,27 @@ wait_request() {
   }
   }
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DInstanceManager::get_package
+//       Access: Public
+//  Description: Returns a (possibly shared) pointer to the indicated
+//               package.
+////////////////////////////////////////////////////////////////////
+P3DPackage *P3DInstanceManager::
+get_package(const string &package_name, const string &package_version) {
+  string key = package_name + "_" + package_version;
+  Packages::iterator pi = _packages.find(key);
+  if (pi != _packages.end()) {
+    return (*pi).second;
+  }
+
+  P3DPackage *package = new P3DPackage(package_name, package_version);
+  bool inserted = _packages.insert(Packages::value_type(key, package)).second;
+  assert(inserted);
+
+  return package;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DInstanceManager::get_unique_session_index
 //     Function: P3DInstanceManager::get_unique_session_index
 //       Access: Public
 //       Access: Public
@@ -229,6 +268,22 @@ P3DInstanceManager *P3DInstanceManager::
 get_global_ptr() {
 get_global_ptr() {
   if (_global_ptr == NULL) {
   if (_global_ptr == NULL) {
     _global_ptr = new P3DInstanceManager;
     _global_ptr = new P3DInstanceManager;
+    _global_ptr->create_command_instance();
   }
   }
   return _global_ptr;
   return _global_ptr;
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DInstanceManager::create_command_instance;
+//       Access: Private
+//  Description: Create a command instance.  This is used to handle
+//               requests that have nothing to do with any particular
+//               host-created instance.
+////////////////////////////////////////////////////////////////////
+void P3DInstanceManager::
+create_command_instance() {
+  P3D_window_handle dummy_handle;
+  _command_instance = 
+    new P3DInstance(NULL, "", P3D_WT_hidden, 0, 0, 0, 0, 
+                    dummy_handle, NULL, 0);
+}

+ 19 - 2
direct/src/plugin/p3dInstanceManager.h

@@ -22,6 +22,7 @@
 
 
 class P3DInstance;
 class P3DInstance;
 class P3DSession;
 class P3DSession;
+class P3DPackage;
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //       Class : P3DInstanceManager
 //       Class : P3DInstanceManager
@@ -36,6 +37,9 @@ private:
 public:
 public:
   bool initialize();
   bool initialize();
 
 
+  inline const string &get_root_dir() const;
+  inline const string &get_download_url() const;
+
   P3DInstance *
   P3DInstance *
   create_instance(P3D_request_ready_func *func,
   create_instance(P3D_request_ready_func *func,
                   const string &p3d_filename, 
                   const string &p3d_filename, 
@@ -51,7 +55,11 @@ public:
   P3DInstance *check_request();
   P3DInstance *check_request();
   void wait_request();
   void wait_request();
 
 
-  INLINE int get_num_instances() const;
+  P3DPackage *get_package(const string &package_name, 
+                          const string &package_version);
+
+  inline P3DInstance *get_command_instance() const;
+  inline int get_num_instances() const;
 
 
   int get_unique_session_index();
   int get_unique_session_index();
   void signal_request_ready();
   void signal_request_ready();
@@ -59,7 +67,13 @@ public:
   static P3DInstanceManager *get_global_ptr();
   static P3DInstanceManager *get_global_ptr();
 
 
 private:
 private:
-  string _p3d_root_directory;
+  void create_command_instance();
+
+private:
+  string _root_dir;
+  string _download_url;
+
+  P3DInstance *_command_instance;
 
 
   typedef set<P3DInstance *> Instances;
   typedef set<P3DInstance *> Instances;
   Instances _instances;
   Instances _instances;
@@ -67,6 +81,9 @@ private:
   typedef map<string, P3DSession *> Sessions;
   typedef map<string, P3DSession *> Sessions;
   Sessions _sessions;
   Sessions _sessions;
 
 
+  typedef map<string, P3DPackage *> Packages;
+  Packages _packages;
+
   int _unique_session_index;
   int _unique_session_index;
 
 
   // Implements a condition-var like behavior.
   // Implements a condition-var like behavior.

+ 71 - 0
direct/src/plugin/p3dPackage.I

@@ -0,0 +1,71 @@
+// Filename: p3dPackage.I
+// Created by:  drose (12Jun09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::get_ready
+//       Access: Public
+//  Description: Returns true if the package has been downloaded and
+//               verified and is ready to be used, false if it has
+//               not.
+////////////////////////////////////////////////////////////////////
+inline bool P3DPackage::
+get_ready() const {
+  return _ready;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::get_failed
+//       Access: Public
+//  Description: Returns true if the package cannot be made ready, for
+//               instance because the download server is down.
+////////////////////////////////////////////////////////////////////
+inline bool P3DPackage::
+get_failed() const {
+  return _failed;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::decode_hexdigit
+//       Access: Private
+//  Description: Returns the integer value corresponding to the
+//               indicated hex digit.  Returns -1 if it is not a hex
+//               digit.
+////////////////////////////////////////////////////////////////////
+inline int P3DPackage::
+decode_hexdigit(char c) {
+  if (isdigit(c)) {
+    return c - '0';
+  }
+  c = tolower(c);
+  if (c >= 'a' && c <= 'f') {
+    return c - 'a' + 10;
+  }
+
+  return -1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::encode_hexdigit
+//       Access: Private
+//  Description: Returns the hex digit corresponding to the
+//               indicated integer value.
+////////////////////////////////////////////////////////////////////
+inline char P3DPackage::
+encode_hexdigit(int c) {
+  if (c >= 10) {
+    return c - 10 + 'a';
+  }
+  return c + '0';
+}

+ 695 - 0
direct/src/plugin/p3dPackage.cxx

@@ -0,0 +1,695 @@
+// Filename: p3dPackage.cxx
+// Created by:  drose (12Jun09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "p3dPackage.h"
+#include "p3dInstanceManager.h"
+
+#include "openssl/md5.h"
+#include "zlib.h"
+
+#include <algorithm>
+#include <fstream>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/utime.h>
+
+#ifdef _WIN32
+#include <direct.h>
+#define stat _stat
+#define utime _utime
+#define utimbuf _utimbuf
+#endif
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+P3DPackage::
+P3DPackage(const string &package_name, const string &package_version) :
+  _package_name(package_name),
+  _package_version(package_version)
+{
+  _package_fullname = _package_name + "_" + _package_version;
+  _ready = false;
+  _failed = false;
+  _active_download = NULL;
+  _partial_download = false;
+
+  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
+
+  // Ensure the package directory exists; create it if it does not.
+  _package_dir = inst_mgr->get_root_dir() + string("/") + _package_name;
+#ifdef _WIN32
+  _mkdir(_package_dir.c_str());
+#else
+  mkdir(_package_dir.c_str(), 0777);
+#endif
+
+  _package_dir += string("/") + _package_version;
+#ifdef _WIN32
+  _mkdir(_package_dir.c_str());
+#else
+  mkdir(_package_dir.c_str(), 0777);
+#endif
+
+  _desc_file_basename = _package_fullname + ".xml";
+  _desc_file_pathname = _package_dir + "/" + _desc_file_basename;
+  
+  // TODO: we should check the desc file for updates with the server.
+  // Perhaps this should be done in a parent class.
+
+  // Load the desc file, if it exists.
+  TiXmlDocument doc(_desc_file_pathname.c_str());
+  if (!doc.LoadFile()) {
+    download_desc_file();
+  } else {
+    got_desc_file(&doc, false);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+P3DPackage::
+~P3DPackage() {
+  // Tell any pending callbacks that we're no good any more.
+  report_done(false);
+
+  // Cancel any pending download.
+  if (_active_download != NULL) {
+    _active_download->cancel();
+    delete _active_download;
+    _active_download = NULL;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::set_callback
+//       Access: Public
+//  Description: Registers a callback on the package.  The callback
+//               object will be notified when the package is ready for
+//               use (or when it has failed to download properly).
+////////////////////////////////////////////////////////////////////
+void P3DPackage::
+set_callback(Callback *callback) {
+  if (_ready || _failed) {
+    // Actually, we're already done.  Signal the callback immediately.
+    callback->package_ready(this, _ready);
+  } else {
+    // Bootstrap still in progress.  Save the callback.
+    _callbacks.push_back(callback);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::cancel_callback
+//       Access: Public
+//  Description: Unregisters a particular callback object.  This
+//               object will no longer be notified when the package is
+//               ready.
+////////////////////////////////////////////////////////////////////
+void P3DPackage::
+cancel_callback(Callback *callback) {
+  Callbacks::iterator ci;
+  ci = find(_callbacks.begin(), _callbacks.end(), callback);
+  if (ci != _callbacks.end()) {
+    _callbacks.erase(ci);
+  } else {
+    cerr << "Canceling unknown callback on " << _package_fullname << "\n";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::download_desc_file
+//       Access: Private
+//  Description: Starts downloading the desc file for the package.
+////////////////////////////////////////////////////////////////////
+void P3DPackage::
+download_desc_file() {
+  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
+  string url = inst_mgr->get_download_url();
+  url += _package_name + "/" + _package_version + "/" + _desc_file_basename;
+
+  start_download(DT_desc_file, url, _desc_file_pathname, false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::desc_file_download_finished
+//       Access: Private
+//  Description: Called when the desc file has been fully downloaded.
+////////////////////////////////////////////////////////////////////
+void P3DPackage::
+desc_file_download_finished(bool success) {
+  if (!success) {
+    report_done(false);
+    return;
+  }
+
+  TiXmlDocument doc(_desc_file_pathname.c_str());
+  if (!doc.LoadFile()) {
+    cerr << "Couldn't read " << _desc_file_pathname << "\n";
+    report_done(false);
+    return;
+  }
+
+  got_desc_file(&doc, true);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::got_desc_file
+//       Access: Private
+//  Description: Reads the desc file and begins verifying the files.
+////////////////////////////////////////////////////////////////////
+void P3DPackage::
+got_desc_file(TiXmlDocument *doc, bool freshly_downloaded) {
+  cerr << "got desc file\n";
+
+  TiXmlElement *xpackage = doc->FirstChildElement("package");
+  TiXmlElement *uncompressed_archive = NULL;
+  TiXmlElement *compressed_archive = NULL;
+  
+  if (xpackage != NULL) {
+    uncompressed_archive = xpackage->FirstChildElement("uncompressed_archive");
+    compressed_archive = xpackage->FirstChildElement("compressed_archive");
+  }
+
+  if (uncompressed_archive == NULL || compressed_archive == NULL) {
+    // The desc file didn't include the archive file itself, weird.
+    if (!freshly_downloaded) {
+      download_desc_file();
+      return;
+    }
+    report_done(false);
+    return;
+  }
+
+  _uncompressed_archive.load_xml(uncompressed_archive);
+  _compressed_archive.load_xml(compressed_archive);
+
+  // Now get all the components.
+  _components.clear();
+  TiXmlElement *component = xpackage->FirstChildElement("component");
+  while (component != NULL) {
+    FileSpec file;
+    file.load_xml(component);
+    _components.push_back(file);
+    component = component->NextSiblingElement("component");
+  }
+
+  cerr << "got " << _components.size() << " components\n";
+
+  // Verify all of the components.
+  bool all_components_ok = true;
+  Components::iterator ci;
+  for (ci = _components.begin(); ci != _components.end(); ++ci) {
+    if (!(*ci).quick_verify(_package_dir)) {
+      all_components_ok = false;
+      break;
+    }
+  }
+
+  if (all_components_ok) {
+    // Great, we're ready to begin.
+    report_done(true);
+
+  } else if (_uncompressed_archive.quick_verify(_package_dir)) {
+    // We need to re-extract the archive.
+    extract_archive();
+
+  } else if (_compressed_archive.quick_verify(_package_dir)) {
+    // We need to uncompress the archive.
+    uncompress_archive();
+
+  } else {
+    // Shoot, we need to download the archive.
+    download_compressed_archive(true);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::download_compressed_archive
+//       Access: Private
+//  Description: Starts downloading the archive file for the package.
+////////////////////////////////////////////////////////////////////
+void P3DPackage::
+download_compressed_archive(bool allow_partial) {
+  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
+  string url = inst_mgr->get_download_url();
+  url += _package_name + "/" + _package_version + "/" + _compressed_archive._filename;
+
+  string target_pathname = _package_dir + "/" + _compressed_archive._filename;
+
+  start_download(DT_compressed_archive, url, target_pathname, allow_partial);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::compressed_archive_download_finished
+//       Access: Private
+//  Description: Called when the desc file has been fully downloaded.
+////////////////////////////////////////////////////////////////////
+void P3DPackage::
+compressed_archive_download_finished(bool success) {
+  if (!success) {
+    report_done(false);
+    return;
+  }
+
+  if (_compressed_archive.full_verify(_package_dir)) {
+    // Go on to uncompress the archive.
+    uncompress_archive();
+    return;
+  }
+
+  // Oof, didn't download it correctly.
+  if (_partial_download) {
+    // Go back and get the whole file this time.
+    download_compressed_archive(false);
+  }
+
+  cerr << _compressed_archive._filename
+       << " failed hash check after download\n";
+  report_done(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::uncompress_archive
+//       Access: Private
+//  Description: Uncompresses the archive file.
+////////////////////////////////////////////////////////////////////
+void P3DPackage::
+uncompress_archive() {
+  cerr << "uncompressing " << _compressed_archive._filename << "\n";
+
+  string source_pathname = _package_dir + "/" + _compressed_archive._filename;
+  string target_pathname = _package_dir + "/" + _uncompressed_archive._filename;
+
+  gzFile source = gzopen(source_pathname.c_str(), "rb");
+  if (source == NULL) {
+    cerr << "Couldn't open " << source_pathname << "\n";
+    report_done(false);
+    return;
+  }
+
+  ofstream target(target_pathname.c_str(), ios::out | ios::binary);
+  if (!target) {
+    cerr << "Couldn't write to " << target_pathname << "\n";
+    report_done(false);
+  }
+
+  static const int buffer_size = 1024;
+  char buffer[buffer_size];
+
+  int count = gzread(source, buffer, buffer_size);
+  while (count > 0) {
+    target.write(buffer, count);
+    count = gzread(source, buffer, buffer_size);
+  }
+
+  if (count < 0) {
+    cerr << "gzip error decompressing " << source_pathname << "\n";
+    int errnum;
+    cerr << gzerror(source, &errnum) << "\n";
+    gzclose(source);
+    report_done(false);
+    return;
+  }
+
+  gzclose(source);
+    
+  if (!target) {
+    cerr << "Couldn't write entire file to " << target_pathname << "\n";
+    report_done(false);
+    return;
+  }
+
+  target.close();
+
+  if (!_uncompressed_archive.full_verify(_package_dir)) {
+    cerr << "after uncompressing " << target_pathname << ", failed hash check\n";
+    report_done(false);
+    return;
+  }
+
+  unlink(source_pathname.c_str());
+
+  // All done uncompressing.
+  extract_archive();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::extract_archive
+//       Access: Private
+//  Description: Extracts the components from the archive file.
+////////////////////////////////////////////////////////////////////
+void P3DPackage::
+extract_archive() {
+  cerr << "extracting " << _uncompressed_archive._filename << "\n";
+
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::report_done
+//       Access: Private
+//  Description: Transitions the package to "ready" or "failure"
+//               state, and reports this change to all the listening
+//               callbacks.
+////////////////////////////////////////////////////////////////////
+void P3DPackage::
+report_done(bool success) {
+  if (success) {
+    _ready = true;
+    _failed = false;
+  } else {
+    _ready = false;
+    _failed = true;
+  }
+
+  Callbacks orig_callbacks;
+  orig_callbacks.swap(_callbacks);
+  Callbacks::iterator ci;
+  for (ci = orig_callbacks.begin(); ci != orig_callbacks.end(); ++ci) {
+    (*ci)->package_ready(this, _ready);
+  }
+
+  // We shouldn't have added any more callbacks during the above loop.
+  assert(_callbacks.empty());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::start_download
+//       Access: Private
+//  Description: Initiates a download of the indicated file.
+////////////////////////////////////////////////////////////////////
+void P3DPackage::
+start_download(P3DPackage::DownloadType dtype, const string &url, 
+               const string &pathname, bool allow_partial) {
+  // Only one download should be active at a time
+  assert(_active_download == NULL);
+
+  Download *download = new Download(this, dtype);
+  download->set_url(url);
+  download->set_filename(pathname);
+
+  // TODO: implement partial file re-download.
+  allow_partial = false;
+  
+  if (!allow_partial) {
+    unlink(pathname.c_str());
+  }
+
+  _active_download = download;
+  _partial_download = false;
+  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
+  inst_mgr->get_command_instance()->start_download(download);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::decode_hex
+//       Access: Private, Static
+//  Description: Decodes the hex string in source into the character
+//               array in dest.  dest must have has least size bytes;
+//               source must have size * 2 bytes.
+//
+//               Returns true on success, false if there was a non-hex
+//               digit in the string.
+////////////////////////////////////////////////////////////////////
+bool P3DPackage::
+decode_hex(unsigned char *dest, const char *source, size_t size) {
+  for (size_t i = 0; i < size; ++i) {
+    int high = decode_hexdigit(source[i * 2]);
+    int low = decode_hexdigit(source[i * 2 + 1]);
+    if (high < 0 || low < 0) {
+      return false;
+    }
+    dest[i] = (high << 4) | low;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::encode_hex
+//       Access: Private, Static
+//  Description: Encodes a character array into a hex string for
+//               output.  dest must have at least size * 2 bytes;
+//               source must have size bytes.  The result is not
+//               null-terminated.
+////////////////////////////////////////////////////////////////////
+void P3DPackage::
+encode_hex(char *dest, const unsigned char *source, size_t size) {
+  for (size_t i = 0; i < size; ++i) {
+    int high = (source[i] >> 4) & 0xf;
+    int low = source[i] & 0xf;
+    dest[2 * i] = encode_hexdigit(high);
+    dest[2 * i + 1] = encode_hexdigit(low);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::stream_hex
+//       Access: Private, Static
+//  Description: Writes the indicated buffer as a string of hex
+//               characters to the given ostream.
+////////////////////////////////////////////////////////////////////
+void P3DPackage::
+stream_hex(ostream &out, const unsigned char *source, size_t size) {
+  for (size_t i = 0; i < size; ++i) {
+    int high = (source[i] >> 4) & 0xf;
+    int low = source[i] & 0xf;
+    out.put(encode_hexdigit(high));
+    out.put(encode_hexdigit(low));
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::Download::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+P3DPackage::Download::
+Download(P3DPackage *package, DownloadType dtype) :
+  _package(package),
+  _dtype(dtype)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::Download::download_finished
+//       Access: Protected, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void P3DPackage::Download::
+download_finished(bool success) {
+  P3DFileDownload::download_finished(success);
+  assert(_package->_active_download == this);
+  _package->_active_download = NULL;
+
+  switch (_dtype) {
+  case DT_desc_file:
+    _package->desc_file_download_finished(success);
+    break;
+
+  case DT_compressed_archive:
+    _package->compressed_archive_download_finished(success);
+    break;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::FileSpec::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+P3DPackage::FileSpec::
+FileSpec() {
+  _size = 0;
+  _timestamp = 0;
+  memset(_hash, 0, sizeof(_hash));
+  _got_hash = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::FileSpec::load_xml
+//       Access: Public
+//  Description: Reads the data from the indicated XML file.
+////////////////////////////////////////////////////////////////////
+void P3DPackage::FileSpec::
+load_xml(TiXmlElement *element) {
+  const char *filename = element->Attribute("filename");
+  if (filename != NULL) {
+    _filename = filename;
+  }
+
+  const char *size = element->Attribute("size");
+  if (size != NULL) {
+    char *endptr;
+    _size = strtoul(size, &endptr, 10);
+  }
+
+  const char *timestamp = element->Attribute("timestamp");
+  if (timestamp != NULL) {
+    char *endptr;
+    _timestamp = strtoul(timestamp, &endptr, 10);
+  }
+
+  _got_hash = false;
+  const char *hash = element->Attribute("hash");
+  if (hash != NULL && strlen(hash) == (hash_size * 2)) {
+    // Decode the hex hash string.
+    _got_hash = decode_hex(_hash, hash, hash_size);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::FileSpec::quick_verify
+//       Access: Public
+//  Description: Performs a quick test to ensure the file has not been
+//               modified.  This test is vulnerable to people
+//               maliciously attempting to fool the program (by
+//               setting datestamps etc.).
+//
+//               Returns true if it is intact, false if it needs to be
+//               redownloaded.
+////////////////////////////////////////////////////////////////////
+bool P3DPackage::FileSpec::
+quick_verify(const string &package_dir) const {
+  string pathname = package_dir + "/" + _filename;
+  struct stat st;
+  if (stat(pathname.c_str(), &st) != 0) {
+    cerr << "file not found: " << _filename << "\n";
+    return false;
+  }
+
+  if (st.st_size != _size) {
+    // If the size is wrong, the file fails.
+    cerr << "size wrong: " << _filename << "\n";
+    return false;
+  }
+
+  if (st.st_mtime == _timestamp) {
+    // If the size is right and the timestamp is right, the file passes.
+    return true;
+  }
+
+  cerr << "modification time wrong: " << _filename << "\n";
+
+  // If the size is right but the timestamp is wrong, the file
+  // soft-fails.  We follow this up with a hash check.
+  if (!check_hash(pathname)) {
+    // Hard fail, the hash is wrong.
+    cerr << "hash check wrong: " << _filename << "\n";
+    return false;
+  }
+
+  cerr << "hash check ok: " << _filename << "\n";
+
+  // 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 successfully
+  // next time.
+  utimbuf utb;
+  utb.actime = st.st_atime;
+  utb.modtime = _timestamp;
+  utime(pathname.c_str(), &utb);
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::FileSpec::quick_verify
+//       Access: Public
+//  Description: Performs a more thorough test to ensure the file has
+//               not been modified.  This test is less vulnerable to
+//               malicious attacks, since it reads and verifies the
+//               entire file.
+//
+//               Returns true if it is intact, false if it needs to be
+//               redownloaded.
+////////////////////////////////////////////////////////////////////
+bool P3DPackage::FileSpec::
+full_verify(const string &package_dir) const {
+  string pathname = package_dir + "/" + _filename;
+  struct stat st;
+  if (stat(pathname.c_str(), &st) != 0) {
+    cerr << "file not found: " << _filename << "\n";
+    return false;
+  }
+
+  if (st.st_size != _size) {
+    // If the size is wrong, the file fails.
+    cerr << "size wrong: " << _filename << "\n";
+    return false;
+  }
+
+  // If the size is right but the timestamp is wrong, the file
+  // soft-fails.  We follow this up with a hash check.
+  if (!check_hash(pathname)) {
+    // Hard fail, the hash is wrong.
+    cerr << "hash check wrong: " << _filename << "\n";
+    return false;
+  }
+
+  cerr << "hash check ok: " << _filename << "\n";
+
+  // The hash is OK.  If the timestamp is wrong, change it back to
+  // what we expect it to be, so we can quick-verify it successfully
+  // next time.
+
+  if (st.st_mtime != _timestamp) {
+    utimbuf utb;
+    utb.actime = st.st_atime;
+    utb.modtime = _timestamp;
+    utime(pathname.c_str(), &utb);
+  }
+    
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DPackage::FileSpec::check_hash
+//       Access: Public
+//  Description: Returns true if the file has the expected md5 hash,
+//               false otherwise.
+////////////////////////////////////////////////////////////////////
+bool P3DPackage::FileSpec::
+check_hash(const string &pathname) const {
+  ifstream stream(pathname.c_str(), ios::in | ios::binary);
+  if (!stream) {
+    cerr << "unable to read " << pathname << "\n";
+    return false;
+  }
+
+  unsigned char md[hash_size];
+
+  MD5_CTX ctx;
+  MD5_Init(&ctx);
+
+  static const int buffer_size = 1024;
+  char buffer[buffer_size];
+
+  stream.read(buffer, buffer_size);
+  size_t count = stream.gcount();
+  while (count != 0) {
+    MD5_Update(&ctx, buffer, count);
+    stream.read(buffer, buffer_size);
+    count = stream.gcount();
+  }
+
+  MD5_Final(md, &ctx);
+
+  return (memcmp(md, _hash, hash_size) == 0);
+}

+ 135 - 0
direct/src/plugin/p3dPackage.h

@@ -0,0 +1,135 @@
+// Filename: p3dPackage.h
+// Created by:  drose (12Jun09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef P3DPACKAGE_H
+#define P3DPACKAGE_H
+
+#include "p3d_plugin_common.h"
+#include "p3dFileDownload.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : P3DPackage
+// Description : This corresponds to a downloadable, patchable
+//               package, and all its constituent files.  For
+//               instance, a particular version of the Panda3D
+//               runtime, which consists of a bunch of dll's
+//               downloaded in a single tar file, is a package.
+//
+//               The plugin is responsible for managing these packages
+//               on disk, downloading new versions when needed, and
+//               removing stale versions to limit disk space waste.
+////////////////////////////////////////////////////////////////////
+class P3DPackage {
+public:
+  P3DPackage(const string &package_name, const string &package_version);
+  ~P3DPackage();
+
+  class Callback {
+  public:
+    virtual void package_ready(P3DPackage *package, bool success);
+  };
+
+  inline bool get_ready() const;
+  inline bool get_failed() const;
+
+  void set_callback(Callback *callback);
+  void cancel_callback(Callback *callback);
+
+private:
+  enum DownloadType {
+    DT_desc_file,
+    DT_compressed_archive
+  };
+
+  class Download : public P3DFileDownload {
+  public:
+    Download(P3DPackage *package, DownloadType dtype);
+
+  protected:
+    virtual void download_finished(bool success);
+
+  private:
+    P3DPackage *_package;
+    DownloadType _dtype;
+  };
+
+  void download_desc_file();
+  void desc_file_download_finished(bool success);
+  void got_desc_file(TiXmlDocument *doc, bool freshly_downloaded);
+
+  void download_compressed_archive(bool allow_partial);
+  void compressed_archive_download_finished(bool success);
+
+  void uncompress_archive();
+  void extract_archive();
+
+  void report_done(bool success);
+  void start_download(DownloadType dtype, const string &url, 
+                      const string &pathname, bool allow_partial);
+
+  static inline int decode_hexdigit(char c);
+  static inline char encode_hexdigit(int c);
+
+  static bool decode_hex(unsigned char *dest, const char *source, size_t size);
+  static void encode_hex(char *dest, const unsigned char *source, size_t size);
+  static void stream_hex(ostream &out, const unsigned char *source, size_t size);
+
+private:
+  string _package_name;
+  string _package_version;
+  string _package_fullname;
+  string _package_dir;
+
+  string _desc_file_basename;
+  string _desc_file_pathname;
+
+  bool _ready;
+  bool _failed;
+  Download *_active_download;
+  bool _partial_download;
+
+  typedef vector<Callback *> Callbacks;
+  Callbacks _callbacks;
+
+  enum { hash_size = 16 };
+
+  class FileSpec {
+  public:
+    FileSpec();
+    void load_xml(TiXmlElement *element);
+
+    bool quick_verify(const string &package_dir) const;
+    bool full_verify(const string &package_dir) const;
+
+    bool check_hash(const string &pathname) const;
+
+    string _filename;
+    size_t _size;
+    time_t _timestamp;
+    unsigned char _hash[hash_size];
+    bool _got_hash;
+  };
+
+  FileSpec _compressed_archive;
+  FileSpec _uncompressed_archive;
+
+  typedef vector<FileSpec> Components;
+  Components _components;
+
+  friend class Download;
+};
+
+#include "p3dPackage.I"
+
+#endif

+ 4 - 4
direct/src/plugin/p3dSession.cxx

@@ -131,9 +131,9 @@ start_instance(P3DInstance *inst) {
 
 
   send_command(doc);
   send_command(doc);
 
 
-  if (_python_state == PS_init) {
-    download_p3dpython(inst);
-  }
+  P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
+  P3DPackage *panda = inst_mgr->get_package("panda3d", "dev");
+  //  start_p3dpython();
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -209,7 +209,7 @@ download_p3dpython(P3DInstance *inst) {
   inst->start_download(download);
   inst->start_download(download);
   */
   */
 
 
-  start_p3dpython();
+  //  start_p3dpython();
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

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

@@ -3,4 +3,5 @@
 #include "p3dFileDownload.cxx"
 #include "p3dFileDownload.cxx"
 #include "p3dInstance.cxx"
 #include "p3dInstance.cxx"
 #include "p3dInstanceManager.cxx"
 #include "p3dInstanceManager.cxx"
+#include "p3dPackage.cxx"
 #include "p3dSession.cxx"
 #include "p3dSession.cxx"

+ 3 - 2
direct/src/plugin/panda3d.cxx

@@ -143,14 +143,15 @@ thread_main() {
     } else {
     } else {
       status = P3D_RC_generic_error;
       status = P3D_RC_generic_error;
     }
     }
+    cerr << "Error getting URL " << _url << "\n";
+  } else {
+    cerr << "Done getting URL " << _url << ", got " << bytes_sent << " bytes\n";
   }
   }
 
 
   P3D_instance_feed_url_stream
   P3D_instance_feed_url_stream
     (_instance, _unique_id, status,
     (_instance, _unique_id, status,
      channel->get_status_code(),
      channel->get_status_code(),
      bytes_sent, NULL, 0);
      bytes_sent, NULL, 0);
-
-  cerr << "Done getting URL " << _url << ", got " << bytes_sent << " bytes\n";
 }
 }
 
 
 bool
 bool