Browse Source

some self-downloading work

David Rose 16 years ago
parent
commit
1b6814e7ac

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

@@ -14,12 +14,16 @@
     handleStreamBuf.cxx handleStreamBuf.h \
     handleStreamBuf.cxx handleStreamBuf.h \
     p3d_lock.h p3d_plugin.h \
     p3d_lock.h p3d_plugin.h \
     p3d_plugin_common.h \
     p3d_plugin_common.h \
+    p3dDownload.h p3dDownload.I \
+    p3dFileDownload.h p3dFileDownload.I \
     p3dInstance.h p3dInstance.I \
     p3dInstance.h p3dInstance.I \
     p3dInstanceManager.h p3dInstanceManager.I \
     p3dInstanceManager.h p3dInstanceManager.I \
     p3dSession.h p3dSession.I
     p3dSession.h p3dSession.I
 
 
   #define INCLUDED_SOURCES \
   #define INCLUDED_SOURCES \
     p3d_plugin.cxx \
     p3d_plugin.cxx \
+    p3dDownload.cxx \
+    p3dFileDownload.cxx \
     p3dInstance.cxx \
     p3dInstance.cxx \
     p3dInstanceManager.cxx \
     p3dInstanceManager.cxx \
     p3dSession.cxx
     p3dSession.cxx

+ 56 - 2
direct/src/plugin/p3dInstance.cxx

@@ -14,6 +14,7 @@
 
 
 #include "p3dInstance.h"
 #include "p3dInstance.h"
 #include "p3dInstanceManager.h"
 #include "p3dInstanceManager.h"
+#include "p3dDownload.h"
 
 
 #include <sstream>
 #include <sstream>
 
 
@@ -68,7 +69,7 @@ P3DInstance::
 
 
   DESTROY_LOCK(_request_lock);
   DESTROY_LOCK(_request_lock);
 
 
-  // TODO: empty _pending_requests queue.
+  // TODO: empty _pending_requests queue and _downloads map.
 }
 }
 
 
 
 
@@ -189,13 +190,33 @@ finish_request(P3D_request *request, bool handled) {
 //               post_url request, this sends the data retrieved from
 //               post_url request, this sends the data retrieved from
 //               the requested URL, a piece at a time.
 //               the requested URL, a piece at a time.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-void P3DInstance::
+bool P3DInstance::
 feed_url_stream(int unique_id,
 feed_url_stream(int unique_id,
                 P3D_result_code result_code,
                 P3D_result_code result_code,
                 int http_status_code, 
                 int http_status_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) {
+  Downloads::iterator di = _downloads.find(unique_id);
+  if (di == _downloads.end()) {
+    cerr << "Unexpected feed_url_stream for " << unique_id << "\n";
+    // Don't know this request.
+    return false;
+  }
+
+  P3DDownload *download = (*di).second;
+  bool download_ok = download->feed_url_stream
+    (result_code, http_status_code, total_expected_data,
+     this_data, this_data_size);
+
+  if (!download_ok || download->get_download_finished()) {
+    // All done.
+    cerr << "completed download " << unique_id << "\n";
+    _downloads.erase(di);
+    delete download;
+  }
+
+  return download_ok;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -217,6 +238,39 @@ lookup_token(const string &keyword) const {
   return string();
   return string();
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DInstance::start_download
+//       Access: Public
+//  Description: Adds a newly-allocated P3DDownload object to the
+//               download queue, and issues the request to start it
+//               downloading.  As the download data comes in, it will
+//               be fed to the download object.  After
+//               download_finished() has been called, the P3DDownload
+//               object will be deleted.
+////////////////////////////////////////////////////////////////////
+void P3DInstance::
+start_download(P3DDownload *download) {
+  assert(download->get_download_id() == 0);
+
+  int download_id = _next_instance_id;
+  ++_next_instance_id;
+  download->set_download_id(download_id);
+
+  bool inserted = _downloads.insert(Downloads::value_type(download_id, download)).second;
+  assert(inserted);
+
+  cerr << "beginning download " << download_id << ": " << download->get_url()
+       << "\n";
+
+  P3D_request *request = new P3D_request;
+  request->_instance = this;
+  request->_request_type = P3D_RT_get_url;
+  request->_request._get_url._url = strdup(download->get_url().c_str());
+  request->_request._get_url._unique_id = download_id;
+
+  add_request(request);
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DInstance::make_xml
 //     Function: P3DInstance::make_xml
 //       Access: Public
 //       Access: Public

+ 8 - 1
direct/src/plugin/p3dInstance.h

@@ -19,9 +19,11 @@
 
 
 #include <vector>
 #include <vector>
 #include <deque>
 #include <deque>
+#include <map>
 #include <tinyxml.h>
 #include <tinyxml.h>
 
 
 class P3DSession;
 class P3DSession;
+class P3DDownload;
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //       Class : P3DInstance
 //       Class : P3DInstance
@@ -48,7 +50,7 @@ public:
   void add_request(P3D_request *request);
   void add_request(P3D_request *request);
   void finish_request(P3D_request *request, bool handled);
   void finish_request(P3D_request *request, bool handled);
 
 
-  void feed_url_stream(int unique_id,
+  bool feed_url_stream(int unique_id,
                        P3D_result_code result_code,
                        P3D_result_code result_code,
                        int http_status_code, 
                        int http_status_code, 
                        size_t total_expected_data,
                        size_t total_expected_data,
@@ -61,6 +63,8 @@ public:
   inline const string &get_python_version() const;
   inline const string &get_python_version() const;
   string lookup_token(const string &keyword) const;
   string lookup_token(const string &keyword) const;
 
 
+  void start_download(P3DDownload *download);
+
   TiXmlElement *make_xml();
   TiXmlElement *make_xml();
 
 
 private:
 private:
@@ -86,6 +90,9 @@ private:
   string _python_version;
   string _python_version;
   P3DSession *_session;
   P3DSession *_session;
 
 
+  typedef map<int, P3DDownload *> Downloads;
+  Downloads _downloads;
+
   LOCK _request_lock;
   LOCK _request_lock;
   typedef deque<P3D_request *> Requests;
   typedef deque<P3D_request *> Requests;
   Requests _pending_requests;
   Requests _pending_requests;

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

@@ -172,13 +172,17 @@ wait_request() {
     }
     }
     
     
     // No pending requests; go to sleep.
     // No pending requests; go to sleep.
-    if (seq == _request_seq) {
 #ifdef _WIN32
 #ifdef _WIN32
+    if (seq == _request_seq) {
       WaitForSingleObject(_request_ready, INFINITE);
       WaitForSingleObject(_request_ready, INFINITE);
+    }
 #else
 #else
+    ACQUIRE_LOCK(_request_ready_lock);
+    if (seq == _request_seq) {
       pthread_cond_wait(&_request_ready_cvar, &_request_ready_lock);
       pthread_cond_wait(&_request_ready_cvar, &_request_ready_lock);
-#endif
     }
     }
+    RELEASE_LOCK(_request_ready_lock);
+#endif
     seq = _request_seq;
     seq = _request_seq;
   }
   }
 }
 }
@@ -205,11 +209,12 @@ get_unique_session_index() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void P3DInstanceManager::
 void P3DInstanceManager::
 signal_request_ready() {
 signal_request_ready() {
-  ++_request_seq;
 #ifdef _WIN32
 #ifdef _WIN32
+  ++_request_seq;
   SetEvent(_request_ready);
   SetEvent(_request_ready);
 #else
 #else
   ACQUIRE_LOCK(_request_ready_lock);
   ACQUIRE_LOCK(_request_ready_lock);
+  ++_request_seq;
   pthread_cond_signal(&_request_ready_cvar);
   pthread_cond_signal(&_request_ready_cvar);
   RELEASE_LOCK(_request_ready_lock);
   RELEASE_LOCK(_request_ready_lock);
 #endif
 #endif

+ 204 - 115
direct/src/plugin/p3dSession.cxx

@@ -15,7 +15,6 @@
 #include "p3dSession.h"
 #include "p3dSession.h"
 #include "p3dInstance.h"
 #include "p3dInstance.h"
 #include "p3dInstanceManager.h"
 #include "p3dInstanceManager.h"
-#include <tinyxml.h>
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DSession::Constructor
 //     Function: P3DSession::Constructor
@@ -32,10 +31,184 @@ P3DSession(P3DInstance *inst) {
   _python_version = inst->get_python_version();
   _python_version = inst->get_python_version();
   _python_root_dir = "C:/p3drun";
   _python_root_dir = "C:/p3drun";
 
 
+  _python_state = PS_init;
+  _started_read_thread = false;
+  _read_thread_continue = false;
+
+  _output_filename = inst->lookup_token("output_filename");
+
   INIT_LOCK(_instances_lock);
   INIT_LOCK(_instances_lock);
+}
 
 
-  _read_thread_continue = false;
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: P3DSession::Destructor
+//       Access: Public
+//  Description: Terminates the session by shutting down Python and
+//               stopping the subprocess.
+////////////////////////////////////////////////////////////////////
+P3DSession::
+~P3DSession() {
+  if (_python_state == PS_running) {
+    // Tell the process we're going away.
+    TiXmlDocument doc;
+    TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "", "");
+    TiXmlElement *xcommand = new TiXmlElement("command");
+    xcommand->SetAttribute("cmd", "exit");
+    doc.LinkEndChild(decl);
+    doc.LinkEndChild(xcommand);
+    _pipe_write << doc << flush;
+
+    // Also close the pipe, to help underscore the point.
+    _pipe_write.close();
+    _pipe_read.close();
+
+#ifdef _WIN32
+    // Now give the process a chance to terminate itself cleanly.
+    if (WaitForSingleObject(_p3dpython.hProcess, 2000) == WAIT_TIMEOUT) {
+      // It didn't shut down cleanly, so kill it the hard way.
+      cerr << "Terminating process.\n";
+      TerminateProcess(_p3dpython.hProcess, 2);
+    }
+
+    CloseHandle(_p3dpython.hProcess);
+    CloseHandle(_p3dpython.hThread);
+#endif
+  }
+
+  // If there are any leftover commands in the queue (presumably
+  // implying we have never started the python process), then delete
+  // them now, unsent.
+  Commands::iterator ci;
+  for (ci = _commands.begin(); ci != _commands.end(); ++ci) {
+    delete (*ci);
+  }
+  _commands.clear();
+
+  join_read_thread();
+  DESTROY_LOCK(_instances_lock);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DSession::start_instance
+//       Access: Public
+//  Description: Adds the indicated instance to the session, and
+//               starts it running.  It is an error if the instance
+//               has been started anywhere else.
+//
+//               The instance must have the same session_key as the
+//               one that was passed to the P3DSession constructor.
+////////////////////////////////////////////////////////////////////
+void P3DSession::
+start_instance(P3DInstance *inst) {
+  assert(inst->_session == NULL);
+  assert(inst->get_session_key() == _session_key);
+  assert(inst->get_python_version() == _python_version);
+
+  ACQUIRE_LOCK(_instances_lock);
+  inst->_session = this;
+  bool inserted = _instances.insert(Instances::value_type(inst->get_instance_id(), inst)).second;
+  RELEASE_LOCK(_instances_lock);
+  assert(inserted);
+
+  TiXmlDocument *doc = new TiXmlDocument;
+  TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "", "");
+  TiXmlElement *xcommand = new TiXmlElement("command");
+  xcommand->SetAttribute("cmd", "start_instance");
+  TiXmlElement *xinstance = inst->make_xml();
+  
+  doc->LinkEndChild(decl);
+  doc->LinkEndChild(xcommand);
+  xcommand->LinkEndChild(xinstance);
+
+  send_command(doc);
+
+  if (_python_state == PS_init) {
+    download_p3dpython(inst);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DSession::terminate_instance
+//       Access: Public
+//  Description: Removes the indicated instance from the session, and
+//               stops it.  It is an error if the instance is not
+//               already running on this session.
+////////////////////////////////////////////////////////////////////
+void P3DSession::
+terminate_instance(P3DInstance *inst) {
+  TiXmlDocument *doc = new TiXmlDocument;
+  TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "", "");
+  TiXmlElement *xcommand = new TiXmlElement("command");
+  xcommand->SetAttribute("cmd", "terminate_instance");
+  xcommand->SetAttribute("id", inst->get_instance_id());
+  
+  doc->LinkEndChild(decl);
+  doc->LinkEndChild(xcommand);
+
+  send_command(doc);
+
+  ACQUIRE_LOCK(_instances_lock);
+  if (inst->_session == this) {
+    inst->_session = NULL;
+    _instances.erase(inst->get_instance_id());
+  }
+  RELEASE_LOCK(_instances_lock);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DSession::send_command
+//       Access: Private
+//  Description: Sends the indicated command to the running Python
+//               process.  If the process has not yet been started,
+//               queues it up until it is ready.
+//
+//               The command must be a newly-allocated TiXmlDocument;
+//               it will be deleted after it has been delivered to the
+//               process.
+////////////////////////////////////////////////////////////////////
+void P3DSession::
+send_command(TiXmlDocument *command) {
+  if (_python_state == PS_running) {
+    // Python is running.  Send the command.
+    _pipe_write << *command << flush;
+    delete command;
+  } else {
+    // Python not yet running.  Queue up the command instead.
+    _commands.push_back(command);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DSession::download_p3dpython
+//       Access: Private
+//  Description: Starts the Python package downloading.  Once it is
+//               fully downloaded and unpacked, automatically calls
+//               start_p3dpython.
+////////////////////////////////////////////////////////////////////
+void P3DSession::
+download_p3dpython(P3DInstance *inst) {
+  P3DFileDownload *download = new P3DFileDownload();
+  download->set_url("http://fewmet/~drose/p3drun.tgz");
+
+  string local_filename = "temp.tgz";
+  if (!download->set_filename(local_filename)) {
+    cerr << "Could not open " << local_filename << "\n";
+    return;
+  }
+
+  inst->start_download(download);
+
+  //  start_p3dpython();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DSession::start_p3dpython
+//       Access: Private
+//  Description: Starts Python running in a child process.
+////////////////////////////////////////////////////////////////////
+void P3DSession::
+start_p3dpython() {
   string p3dpython = "c:/cygwin/home/drose/player/direct/built/bin/p3dpython.exe";
   string p3dpython = "c:/cygwin/home/drose/player/direct/built/bin/p3dpython.exe";
   //  string p3dpython = _python_root_dir + "/p3dpython.exe";
   //  string p3dpython = _python_root_dir + "/p3dpython.exe";
 
 
@@ -93,20 +266,19 @@ P3DSession(P3DInstance *inst) {
   }
   }
 
 
   HANDLE error_handle = GetStdHandle(STD_ERROR_HANDLE);
   HANDLE error_handle = GetStdHandle(STD_ERROR_HANDLE);
-  string output_filename = inst->lookup_token("output_filename");
-  bool got_output_filename = !output_filename.empty();
+  bool got_output_filename = !_output_filename.empty();
   if (got_output_filename) {
   if (got_output_filename) {
     // Open the named file for output and redirect the child's stderr
     // Open the named file for output and redirect the child's stderr
     // into it.
     // into it.
     HANDLE handle = CreateFile
     HANDLE handle = CreateFile
-      (output_filename.c_str(), GENERIC_WRITE, 
+      (_output_filename.c_str(), GENERIC_WRITE, 
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        NULL, CREATE_ALWAYS, 0, NULL);
        NULL, CREATE_ALWAYS, 0, NULL);
     if (handle != INVALID_HANDLE_VALUE) {
     if (handle != INVALID_HANDLE_VALUE) {
       error_handle = handle;
       error_handle = handle;
-      SetHandleInformation(error_hanlesdle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
+      SetHandleInformation(error_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
     } else {
     } else {
-      cerr << "Unable to open " << output_filename << "\n";
+      cerr << "Unable to open " << _output_filename << "\n";
     }
     }
   }
   }
 
 
@@ -131,13 +303,7 @@ P3DSession(P3DInstance *inst) {
     (p3dpython.c_str(), NULL, NULL, NULL, TRUE, 0,
     (p3dpython.c_str(), NULL, NULL, NULL, TRUE, 0,
      (void *)env.c_str(), _python_root_dir.c_str(),
      (void *)env.c_str(), _python_root_dir.c_str(),
      &startup_info, &_p3dpython);
      &startup_info, &_p3dpython);
-  _started_p3dpython = (result != 0);
-
-  if (!_started_p3dpython) {
-    cerr << "Failed to create process.\n";
-  } else {
-    cerr << "Created process: " << _p3dpython.dwProcessId << "\n";
-  }
+  bool started_p3dpython = (result != 0);
 
 
   // Close the pipe handles that are now owned by the child.
   // Close the pipe handles that are now owned by the child.
   CloseHandle(w_from);
   CloseHandle(w_from);
@@ -150,116 +316,32 @@ P3DSession(P3DInstance *inst) {
   _pipe_write.open_write(w_to);
   _pipe_write.open_write(w_to);
 #endif  // _WIN32
 #endif  // _WIN32
 
 
+  if (!started_p3dpython) {
+    cerr << "Failed to create process.\n";
+    return;
+  }
+  _python_state = PS_running;
+
+  cerr << "Created process: " << _p3dpython.dwProcessId << "\n";
+
   if (!_pipe_read) {
   if (!_pipe_read) {
     cerr << "unable to open read pipe\n";
     cerr << "unable to open read pipe\n";
   }
   }
   if (!_pipe_write) {
   if (!_pipe_write) {
     cerr << "unable to open write pipe\n";
     cerr << "unable to open write pipe\n";
   }
   }
-
-  spawn_read_thread();
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: P3DSession::Destructor
-//       Access: Public
-//  Description: Terminates the session by shutting down Python and
-//               stopping the subprocess.
-////////////////////////////////////////////////////////////////////
-P3DSession::
-~P3DSession() {
-  if (_started_p3dpython) {
-    // Tell the process we're going away.
-    TiXmlDocument doc;
-    TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "", "");
-    TiXmlElement *xcommand = new TiXmlElement("command");
-    xcommand->SetAttribute("cmd", "exit");
-    doc.LinkEndChild(decl);
-    doc.LinkEndChild(xcommand);
-    _pipe_write << doc << flush;
-
-    // Also close the pipe, to help underscore the point.
-    _pipe_write.close();
-    _pipe_read.close();
-
-#ifdef _WIN32
-    // Now give the process a chance to terminate itself cleanly.
-    if (WaitForSingleObject(_p3dpython.hProcess, 2000) == WAIT_TIMEOUT) {
-      // It didn't shut down cleanly, so kill it the hard way.
-      cerr << "Terminating process.\n";
-      TerminateProcess(_p3dpython.hProcess, 2);
-    }
-
-    CloseHandle(_p3dpython.hProcess);
-    CloseHandle(_p3dpython.hThread);
-#endif
-  }
-
-  join_read_thread();
-  DESTROY_LOCK(_instances_lock);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: P3DSession::start_instance
-//       Access: Public
-//  Description: Adds the indicated instance to the session, and
-//               starts it running.  It is an error if the instance
-//               has been started anywhere else.
-//
-//               The instance must have the same session_key as the
-//               one that was passed to the P3DSession constructor.
-////////////////////////////////////////////////////////////////////
-void P3DSession::
-start_instance(P3DInstance *inst) {
-  assert(inst->_session == NULL);
-  assert(inst->get_session_key() == _session_key);
-  assert(inst->get_python_version() == _python_version);
-
-  ACQUIRE_LOCK(_instances_lock);
-  inst->_session = this;
-  bool inserted = _instances.insert(Instances::value_type(inst->get_instance_id(), inst)).second;
-  RELEASE_LOCK(_instances_lock);
-  assert(inserted);
-
-  TiXmlDocument doc;
-  TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "", "");
-  TiXmlElement *xcommand = new TiXmlElement("command");
-  xcommand->SetAttribute("cmd", "start_instance");
-  TiXmlElement *xinstance = inst->make_xml();
   
   
-  doc.LinkEndChild(decl);
-  doc.LinkEndChild(xcommand);
-  xcommand->LinkEndChild(xinstance);
-
-  _pipe_write << doc << flush;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: P3DSession::terminate_instance
-//       Access: Public
-//  Description: Removes the indicated instance from the session, and
-//               stops it.  It is an error if the instance is not
-//               already running on this session.
-////////////////////////////////////////////////////////////////////
-void P3DSession::
-terminate_instance(P3DInstance *inst) {
-  TiXmlDocument doc;
-  TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "", "");
-  TiXmlElement *xcommand = new TiXmlElement("command");
-  xcommand->SetAttribute("cmd", "terminate_instance");
-  xcommand->SetAttribute("id", inst->get_instance_id());
-  
-  doc.LinkEndChild(decl);
-  doc.LinkEndChild(xcommand);
-
-  _pipe_write << doc << flush;
+  spawn_read_thread();
 
 
-  ACQUIRE_LOCK(_instances_lock);
-  if (inst->_session == this) {
-    inst->_session = NULL;
-    _instances.erase(inst->get_instance_id());
+  // Now that the process has been started, feed it any commands we
+  // may have queued up.
+  Commands::iterator ci;
+  for (ci = _commands.begin(); ci != _commands.end(); ++ci) {
+    _pipe_write << *(*ci);
+    delete (*ci);
   }
   }
-  RELEASE_LOCK(_instances_lock);
+  _pipe_write << flush;
+  _commands.clear();
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -283,6 +365,7 @@ spawn_read_thread() {
 #ifdef _WIN32
 #ifdef _WIN32
   _read_thread = CreateThread(NULL, 0, &win_rt_thread_run, this, 0, NULL);
   _read_thread = CreateThread(NULL, 0, &win_rt_thread_run, this, 0, NULL);
 #endif
 #endif
+  _started_read_thread = true;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -292,6 +375,10 @@ spawn_read_thread() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void P3DSession::
 void P3DSession::
 join_read_thread() {
 join_read_thread() {
+  if (!_started_read_thread) {
+    return;
+  }
+
   cerr << "session waiting for thread\n";
   cerr << "session waiting for thread\n";
   _read_thread_continue = false;
   _read_thread_continue = false;
   _pipe_read.close();
   _pipe_read.close();
@@ -303,6 +390,8 @@ join_read_thread() {
   _read_thread = NULL;
   _read_thread = NULL;
 #endif
 #endif
   cerr << "session done waiting for thread\n";
   cerr << "session done waiting for thread\n";
+
+  _started_read_thread = false;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 24 - 3
direct/src/plugin/p3dSession.h

@@ -18,7 +18,9 @@
 #include "p3d_plugin_common.h"
 #include "p3d_plugin_common.h"
 #include "handleStream.h"
 #include "handleStream.h"
 
 
-#include <set>
+#include <map>
+#include <vector>
+#include <tinyxml.h>
 
 
 class P3DInstance;
 class P3DInstance;
 
 
@@ -43,6 +45,10 @@ public:
   INLINE int get_num_instances() const;
   INLINE int get_num_instances() const;
 
 
 private:
 private:
+  void send_command(TiXmlDocument *command);
+  void download_p3dpython(P3DInstance *inst);
+  void start_p3dpython();
+
   void spawn_read_thread();
   void spawn_read_thread();
   void join_read_thread();
   void join_read_thread();
 
 
@@ -56,8 +62,17 @@ private:
 #endif
 #endif
 
 
 private:
 private:
+  enum PythonState {
+    PS_init,
+    PS_downloading,
+    PS_running,
+    PS_done,
+  };
+  PythonState _python_state;
+
   string _session_key;
   string _session_key;
   string _python_version;
   string _python_version;
+  string _output_filename;
 
 
   string _python_root_dir;
   string _python_root_dir;
 
 
@@ -65,13 +80,19 @@ private:
   Instances _instances;
   Instances _instances;
   LOCK _instances_lock;
   LOCK _instances_lock;
 
 
+  // Commands that are queued up to send down the pipe.  Normally
+  // these only accumulate before the python process has been started;
+  // after that, commands are written to the pipe directly.
+  typedef vector<TiXmlDocument *> Commands;
+  Commands _commands;
+
   // Members for communicating with the p3dpython child process.
   // Members for communicating with the p3dpython child process.
-  bool _started_p3dpython;
 #ifdef _WIN32
 #ifdef _WIN32
   PROCESS_INFORMATION _p3dpython;
   PROCESS_INFORMATION _p3dpython;
 #endif
 #endif
 
 
-  // The remaining members are manipulated by the read thread.
+  // The remaining members are manipulated by or for the read thread.
+  bool _started_read_thread;
   HandleStream _pipe_read;
   HandleStream _pipe_read;
   HandleStream _pipe_write;
   HandleStream _pipe_write;
 
 

+ 9 - 3
direct/src/plugin/p3d_plugin.cxx

@@ -23,7 +23,12 @@ bool initialized_lock = false;
 LOCK _lock;
 LOCK _lock;
 
 
 bool 
 bool 
-P3D_initialize() {
+P3D_initialize(int api_version) {
+  if (api_version != P3D_API_VERSION) {
+    // Can't accept an incompatible version.
+    return false;
+  }
+
   if (!initialized_lock) {
   if (!initialized_lock) {
     INIT_LOCK(_lock);
     INIT_LOCK(_lock);
     initialized_lock = true;
     initialized_lock = true;
@@ -141,7 +146,7 @@ P3D_request_finish(P3D_request *request, bool handled) {
   RELEASE_LOCK(_lock);
   RELEASE_LOCK(_lock);
 }
 }
 
 
-void
+bool
 P3D_instance_feed_url_stream(P3D_instance *instance, int unique_id,
 P3D_instance_feed_url_stream(P3D_instance *instance, int unique_id,
                              P3D_result_code result_code,
                              P3D_result_code result_code,
                              int http_status_code, 
                              int http_status_code, 
@@ -149,9 +154,10 @@ P3D_instance_feed_url_stream(P3D_instance *instance, int unique_id,
                              const unsigned char *this_data, 
                              const unsigned char *this_data, 
                              size_t this_data_size) {
                              size_t this_data_size) {
   ACQUIRE_LOCK(_lock);
   ACQUIRE_LOCK(_lock);
-  ((P3DInstance *)instance)->
+  bool result = ((P3DInstance *)instance)->
     feed_url_stream(unique_id, result_code, http_status_code,
     feed_url_stream(unique_id, result_code, http_status_code,
                     total_expected_data, this_data, this_data_size);
                     total_expected_data, this_data, this_data_size);
   RELEASE_LOCK(_lock);
   RELEASE_LOCK(_lock);
+  return result;
 }
 }
 
 

+ 23 - 8
direct/src/plugin/p3d_plugin.h

@@ -68,20 +68,29 @@ extern "C" {
    functions themselves, allowing the plugin library to be loaded via
    functions themselves, allowing the plugin library to be loaded via
    an explicit LoadLibrary() or equivalent call. */
    an explicit LoadLibrary() or equivalent call. */
 
 
+
+/* This symbol serves to validate that runtime and compile-time
+libraries match.  It should be passed to P3D_initialize() (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 1
+
 /************************ GLOBAL FUNCTIONS **************************/
 /************************ GLOBAL FUNCTIONS **************************/
 
 
 /* The following interfaces are global to the plugin space, as opposed
 /* The following interfaces are global to the plugin space, as opposed
    to being specific to a particular instance. */
    to being specific to a particular instance. */
 
 
 /* This function should be called immediately after the plugin is
 /* This function should be called immediately after the plugin is
-   loaded.
-
-   This function returns true if the plugin is valid, false otherwise.
-   If it returns false, the host should not call any more functions in
-   this API, and should immediately unload the DLL and (if possible)
-   download a new one. */
+   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
+   the API as the host.
+
+   This function returns true if the plugin is valid and uses a
+   compatible API, false otherwise.  If it returns false, the host
+   should not call any more functions in this API, and should
+   immediately unload the DLL and (if possible) download a new one. */
 typedef bool 
 typedef bool 
-P3D_initialize_func();
+P3D_initialize_func(int api_version);
 
 
 /* This function frees a pointer returned by
 /* This function frees a pointer returned by
    P3D_instance_get_property(), or another similar function that
    P3D_instance_get_property(), or another similar function that
@@ -418,8 +427,14 @@ typedef enum {
    is appended together by the plugin to define the total set of data
    is appended together by the plugin to define the total set of data
    retrieved from the URL.  For a particular call to feed_url_stream,
    retrieved from the URL.  For a particular call to feed_url_stream,
    this may contain no data at all (e.g. this_data_size may be 0).
    this may contain no data at all (e.g. this_data_size may be 0).
+
+   The return value of this function is true if there are no problems
+   and the download should continue, false if there was an error
+   accepting the data and the host should abort.  If this function
+   returns false on a P3D_RC_in_progress, there is no need to call
+   the function again with any future updates.
  */
  */
-typedef void
+typedef bool
 P3D_instance_feed_url_stream_func(P3D_instance *instance, int unique_id,
 P3D_instance_feed_url_stream_func(P3D_instance *instance, int unique_id,
                                   P3D_result_code result_code,
                                   P3D_result_code result_code,
                                   int http_status_code, 
                                   int http_status_code, 

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

@@ -1,4 +1,6 @@
 #include "p3d_plugin.cxx"
 #include "p3d_plugin.cxx"
+#include "p3dDownload.cxx"
+#include "p3dFileDownload.cxx"
 #include "p3dInstance.cxx"
 #include "p3dInstance.cxx"
 #include "p3dInstanceManager.cxx"
 #include "p3dInstanceManager.cxx"
 #include "p3dSession.cxx"
 #include "p3dSession.cxx"

+ 30 - 16
direct/src/plugin/panda3d.cxx

@@ -104,16 +104,24 @@ thread_main() {
   channel->download_to_ram(&rf);
   channel->download_to_ram(&rf);
 
 
   size_t bytes_sent = 0;
   size_t bytes_sent = 0;
-  while (channel->run()) {
+  while (channel->run() || rf.get_data_size() != 0) {
     if (rf.get_data_size() != 0) {
     if (rf.get_data_size() != 0) {
       // Got some new data.
       // Got some new data.
-      P3D_instance_feed_url_stream
+      bool download_ok = P3D_instance_feed_url_stream
         (_instance, _unique_id, P3D_RC_in_progress,
         (_instance, _unique_id, P3D_RC_in_progress,
          channel->get_status_code(),
          channel->get_status_code(),
          channel->get_file_size(),
          channel->get_file_size(),
          (const unsigned char *)rf.get_data().data(), rf.get_data_size());
          (const unsigned char *)rf.get_data().data(), rf.get_data_size());
       bytes_sent += rf.get_data_size();
       bytes_sent += rf.get_data_size();
       rf.clear();
       rf.clear();
+
+      if (!download_ok) {
+        // The plugin doesn't care any more.  Interrupt the download.
+        cerr << "Download interrupted: " << _url 
+             << ", after " << bytes_sent << " of " << channel->get_file_size()
+             << " bytes.\n";
+        return;
+      }
     }
     }
   }
   }
 
 
@@ -195,7 +203,7 @@ load_plugin(const string &p3d_plugin_filename) {
   }
   }
 
 
   // Successfully loaded.
   // Successfully loaded.
-  if (!P3D_initialize()) {
+  if (!P3D_initialize(P3D_API_VERSION)) {
     // Oops, failure to initialize.
     // Oops, failure to initialize.
     return false;
     return false;
   }
   }
@@ -525,7 +533,7 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
       }
       }
       TranslateMessage(&msg);
       TranslateMessage(&msg);
       DispatchMessage(&msg);
       DispatchMessage(&msg);
-      
+
       // Check for new requests from the Panda3D plugin.
       // Check for new requests from the Panda3D plugin.
       P3D_instance *inst = P3D_check_request(false);
       P3D_instance *inst = P3D_check_request(false);
       while (inst != (P3D_instance *)NULL) {
       while (inst != (P3D_instance *)NULL) {
@@ -535,6 +543,8 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
         }
         }
         inst = P3D_check_request(false);
         inst = P3D_check_request(false);
       }
       }
+
+      Thread::force_yield();
       retval = GetMessage(&msg, NULL, 0, 0);
       retval = GetMessage(&msg, NULL, 0, 0);
     }
     }
     
     
@@ -550,26 +560,30 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
   } else {
   } else {
     // Not an embedded window, so we don't have our own window to
     // Not an embedded window, so we don't have our own window to
     // generate Windows events.  Instead, just wait for requests.
     // generate Windows events.  Instead, just wait for requests.
-    P3D_instance *inst = P3D_check_request(true);
-    while (inst != (P3D_instance *)NULL) {
-      P3D_request *request = P3D_instance_get_request(inst);
-      if (request != (P3D_request *)NULL) {
-        handle_request(request);
+    while (!_instances.empty()) {
+      P3D_instance *inst = P3D_check_request(false);
+      if (inst != (P3D_instance *)NULL) {
+        P3D_request *request = P3D_instance_get_request(inst);
+        if (request != (P3D_request *)NULL) {
+          handle_request(request);
+        }
       }
       }
-      inst = P3D_check_request(true);
+      Thread::force_yield();
     }
     }
   }
   }
     
     
 #endif
 #endif
 
 
   // Now wait while we process pending requests.
   // Now wait while we process pending requests.
-  P3D_instance *inst = P3D_check_request(true);
-  while (inst != (P3D_instance *)NULL) {
-    P3D_request *request = P3D_instance_get_request(inst);
-    if (request != (P3D_request *)NULL) {
-      handle_request(request);
+  while (!_instances.empty()) {
+    P3D_instance *inst = P3D_check_request(false);
+    if (inst != (P3D_instance *)NULL) {
+      P3D_request *request = P3D_instance_get_request(inst);
+      if (request != (P3D_request *)NULL) {
+        handle_request(request);
+      }
     }
     }
-    inst = P3D_check_request(true);
+    Thread::force_yield();
   }
   }
 
 
   // All instances have finished; we can exit.
   // All instances have finished; we can exit.

+ 1 - 1
direct/src/showbase/MakeAppMF.py

@@ -181,7 +181,7 @@ class AppPacker:
     def addEggFile(self, filename, outFilename):
     def addEggFile(self, filename, outFilename):
         # Precompile egg files to bam's.
         # Precompile egg files to bam's.
         np = self.loader.loadModel(filename, okMissing = True)
         np = self.loader.loadModel(filename, okMissing = True)
-        if np.isEmpty():
+        if not np:
             raise StandardError, 'Could not read egg file %s' % (filename)
             raise StandardError, 'Could not read egg file %s' % (filename)
 
 
         self.addNode(np.node(), outFilename)
         self.addNode(np.node(), outFilename)