open_toontown_panda3d/direct/src/plugin/p3dMainObject.cxx

708 lines
21 KiB
C++

/**
* 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."
*
* @file p3dMainObject.cxx
* @author drose
* @date 2009-07-10
*/
#include "p3dMainObject.h"
#include "p3dPythonObject.h"
#include "p3dInstance.h"
#include "p3dSession.h"
#include "p3dStringObject.h"
#include "p3dInstanceManager.h"
using std::ios;
using std::max;
using std::streamsize;
using std::string;
/**
*
*/
P3DMainObject::
P3DMainObject() :
_pyobj(nullptr),
_inst(nullptr),
_unauth_play(false)
{
}
/**
*
*/
P3DMainObject::
~P3DMainObject() {
set_pyobj(nullptr);
// Clear the local properties.
Properties::const_iterator pi;
for (pi = _properties.begin(); pi != _properties.end(); ++pi) {
P3D_object *value = (*pi).second;
P3D_OBJECT_DECREF(value);
}
_properties.clear();
}
/**
* Returns the fundamental type of this kind of object.
*/
P3D_object_type P3DMainObject::
get_type() {
return P3D_OT_object;
}
/**
* Returns the object value coerced to a boolean, if possible.
*/
bool P3DMainObject::
get_bool() {
return true;
}
/**
* Returns the object value coerced to an integer, if possible.
*/
int P3DMainObject::
get_int() {
return 0;
}
/**
* Returns the object value coerced to a floating-point value, if possible.
*/
double P3DMainObject::
get_float() {
return 0.0;
}
/**
* Fills the indicated C++ string object with the value of this object coerced
* to a string.
*/
void P3DMainObject::
make_string(string &value) {
if (_pyobj == nullptr) {
value = "P3DMainObject";
} else {
int size = P3D_OBJECT_GET_STRING(_pyobj, nullptr, 0);
char *buffer = new char[size];
P3D_OBJECT_GET_STRING(_pyobj, buffer, size);
value = string(buffer, size);
delete[] buffer;
}
}
/**
* Returns the named property element in the object. The return value is a
* new-reference P3D_object, or NULL on error.
*/
P3D_object *P3DMainObject::
get_property(const string &property) {
if (_pyobj == nullptr) {
// Without a pyobj, we just report whatever's been stored locally.
Properties::const_iterator pi;
pi = _properties.find(property);
if (pi != _properties.end()) {
P3D_object *result = (*pi).second;
P3D_OBJECT_INCREF(result);
return result;
}
return nullptr;
}
// With a pyobj, we pass the query down to it.
return P3D_OBJECT_GET_PROPERTY(_pyobj, property.c_str());
}
/**
* Modifies (or deletes, if value is NULL) the named property element in the
* object. Returns true on success, false on failure.
*/
bool P3DMainObject::
set_property(const string &property, bool needs_response, P3D_object *value) {
// First, we set the property locally.
if (value != nullptr) {
Properties::iterator pi;
pi = _properties.insert(Properties::value_type(property, nullptr)).first;
assert(pi != _properties.end());
P3D_object *orig_value = (*pi).second;
if (orig_value != value) {
P3D_OBJECT_XDECREF(orig_value);
(*pi).second = value;
P3D_OBJECT_INCREF(value);
}
} else {
// (Or delete the property locally.)
Properties::iterator pi;
pi = _properties.find(property);
if (pi != _properties.end()) {
P3D_object *orig_value = (*pi).second;
P3D_OBJECT_DECREF(orig_value);
_properties.erase(pi);
}
}
if (_pyobj == nullptr) {
// Without a pyobj, that's all we do.
return true;
}
// With a pyobj, we also pass this request down.
return P3D_OBJECT_SET_PROPERTY(_pyobj, property.c_str(), needs_response, value);
}
/**
* Returns true if the named method exists on this object, false otherwise.
*/
bool P3DMainObject::
has_method(const string &method_name) {
// Some special-case methods implemented in-place.
if (method_name == "play") {
return true;
} else if (method_name == "read_game_log") {
return true;
} else if (method_name == "read_system_log") {
return true;
} else if (method_name == "read_log") {
return true;
} else if (method_name == "uninstall") {
return true;
}
if (_pyobj == nullptr) {
// No methods until we get our pyobj.
return false;
}
return P3D_OBJECT_HAS_METHOD(_pyobj, method_name.c_str());
}
/**
* Invokes the named method on the object, passing the indicated parameters.
* If the method name is empty, invokes the object itself.
*
* If needs_response is true, the return value is a new-reference P3D_object
* on success, or NULL on failure. If needs_response is false, the return
* value is always NULL, and there is no way to determine success or failure.
*/
P3D_object *P3DMainObject::
call(const string &method_name, bool needs_response,
P3D_object *params[], int num_params) {
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
nout << "main." << method_name << "(";
for (int i = 0; i < num_params; ++i) {
if (i != 0) {
nout << ", ";
}
int buffer_size = P3D_OBJECT_GET_REPR(params[i], nullptr, 0);
char *buffer = new char[buffer_size];
P3D_OBJECT_GET_REPR(params[i], buffer, buffer_size);
nout.write(buffer, buffer_size);
delete[] buffer;
}
nout << ")\n";
if (method_name == "play") {
return call_play(params, num_params);
} else if (method_name == "read_game_log") {
return call_read_game_log(params, num_params);
} else if (method_name == "read_system_log") {
return call_read_system_log(params, num_params);
} else if (method_name == "read_log") {
return call_read_log(params, num_params);
} else if (method_name == "uninstall") {
return call_uninstall(params, num_params);
}
if (_pyobj == nullptr) {
// No methods until we get our pyobj.
return nullptr;
}
return P3D_OBJECT_CALL(_pyobj, method_name.c_str(), needs_response,
params, num_params);
}
/**
* Writes a formatted representation of the value to the indicated string.
* This is intended for developer assistance.
*/
void P3DMainObject::
output(std::ostream &out) {
out << "P3DMainObject";
}
/**
* Changes the internal pyobj pointer. This is the P3D_object that references
* the actual PyObject held within the child process, corresponding to the
* true main object there. The new object's reference count is incremented,
* and the previous object's is decremented.
*/
void P3DMainObject::
set_pyobj(P3D_object *pyobj) {
if (pyobj == this) {
// We are setting a reference directly to ourselves. This happens when
// the application has accepted the main object we gave it in
// set_instance_info(). This means the application is directly
// manipulating this object as its appRunner.main. In this case, we don't
// actually need to set the reference; instead, we clear anything we had
// set.
nout << "application shares main object\n";
pyobj = nullptr;
} else if (pyobj != nullptr) {
// In the alternate case, the application has its own, separate
// appRunner.main object. Thus, we do need to set the pointer.
nout << "application has its own main object\n";
}
if (_pyobj != pyobj) {
P3D_OBJECT_XDECREF(_pyobj);
_pyobj = pyobj;
if (_pyobj != nullptr) {
P3D_OBJECT_INCREF(_pyobj);
// Now that we have a pyobj, we have to transfer down all of the
// properties we'd set locally.
apply_properties(_pyobj);
}
}
}
/**
* Returns the internal pyobj pointer, or NULL if it has not yet been set.
*/
P3D_object *P3DMainObject::
get_pyobj() const {
return _pyobj;
}
/**
* Applies the locally-set properties onto the indicated Python object, but
* does not store the object. This is a one-time copy of the locally-set
* properties (like "coreapiHostUrl" and the like) onto the indicated Python
* object.
*/
void P3DMainObject::
apply_properties(P3D_object *pyobj) {
P3DPythonObject *p3dpyobj = nullptr;
if (pyobj->_class == &P3DObject::_object_class) {
p3dpyobj = ((P3DObject *)pyobj)->as_python_object();
}
Properties::const_iterator pi;
for (pi = _properties.begin(); pi != _properties.end(); ++pi) {
const string &property_name = (*pi).first;
P3D_object *value = (*pi).second;
if (p3dpyobj != nullptr && P3D_OBJECT_GET_TYPE(value) != P3D_OT_object) {
// If we know we have an actual P3DPythonObject (we really expect this),
// then we can call set_property_insecure() directly, because we want to
// allow setting the initial properties even if Javascript has no
// permissions to write into Python. But we don't allow setting objects
// this way in any event.
p3dpyobj->set_property_insecure(property_name, false, value);
} else {
// Otherwise, we go through the generic interface.
P3D_OBJECT_SET_PROPERTY(pyobj, property_name.c_str(), false, value);
}
}
}
/**
* Sets a callback pointer to the instance that owns this object. When this
* instance destructs, it clears this pointer to NULL.
*/
void P3DMainObject::
set_instance(P3DInstance *inst) {
if (_inst != nullptr) {
// Save the game log filename of the instance just before it goes away, in
// case JavaScript asks for it later.
_game_log_pathname = _inst->get_log_pathname();
}
_inst = inst;
}
/**
* Starts the process remotely, as if the play button had been clicked. If
* the application has not yet been validated, this pops up the validation
* dialog.
*
* Only applicable if the application was in the ready state, or the unauth
* state. Returns true if the application is now started, false otherwise.
*
* This may be invoked from the unauth state only once. If the user chooses
* not to authorize the plugin at that time, it may not be invoked
* automatically again.
*/
P3D_object *P3DMainObject::
call_play(P3D_object *params[], int num_params) {
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
if (_inst == nullptr) {
return inst_mgr->new_bool_object(false);
}
// I guess there's no harm in allowing JavaScript to call play(), with or
// without explicit scripting authorization.
nout << "play() called from JavaScript\n";
if (!_inst->is_trusted()) {
// Requires authorization. We allow this only once; beyond that, and
// you're only annoying the user.
if (!_unauth_play) {
_unauth_play = true;
_inst->splash_button_clicked_main_thread();
}
} else if (!_inst->is_started()) {
// We allow calling play() from a ready state without limit, but probably
// only once will be necessary.
_inst->splash_button_clicked_main_thread();
}
return inst_mgr->new_bool_object(_inst->is_started());
}
/**
* Reads the entire logfile as a string, and returns it to the calling
* JavaScript process.
*/
P3D_object *P3DMainObject::
call_read_game_log(P3D_object *params[], int num_params) {
if (_inst != nullptr) {
string log_pathname = _inst->get_log_pathname();
return read_log(log_pathname, params, num_params);
}
if (!_game_log_pathname.empty()) {
// The instance has already finished, but we saved its log filename.
return read_log(_game_log_pathname, params, num_params);
}
// No log available for us.
nout << "read_game_log: error: game log name unknown" << "\n";
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
return inst_mgr->new_undefined_object();
}
/**
* As above, but reads the system log, the logfile for the installation
* process.
*/
P3D_object *P3DMainObject::
call_read_system_log(P3D_object *params[], int num_params) {
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
string log_pathname = inst_mgr->get_log_pathname();
return read_log(log_pathname, params, num_params);
}
/**
* Reads a named logfile. The filename must end in ".log" and must not
* contain any slashes or colons (it must be found within the log directory).
*/
P3D_object *P3DMainObject::
call_read_log(P3D_object *params[], int num_params) {
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
if (num_params < 1) {
nout << "read_log: error: not enough parameters" << "\n";
return inst_mgr->new_undefined_object();
}
int size = P3D_OBJECT_GET_STRING(params[0], nullptr, 0);
char *buffer = new char[size];
P3D_OBJECT_GET_STRING(params[0], buffer, size);
string log_filename = string(buffer, size);
delete[] buffer;
if (log_filename.size() < 4 || log_filename.substr(log_filename.size() - 4) != string(".log")) {
// Wrong filename extension.
nout << "read_log: error: invalid filename" << "\n";
return inst_mgr->new_undefined_object();
}
size_t slash = log_filename.find('/');
if (slash != string::npos) {
// No slashes allowed.
nout << "read_log: error: invalid filename" << "\n";
return inst_mgr->new_undefined_object();
}
slash = log_filename.find('\\');
if (slash != string::npos) {
// Nor backslashes.
nout << "read_log: error: invalid filename" << "\n";
return inst_mgr->new_undefined_object();
}
size_t colon = log_filename.find(':');
if (colon != string::npos) {
// Nor colons, for that matter.
nout << "read_log: error: invalid filename" << "\n";
return inst_mgr->new_undefined_object();
}
string log_pathname = inst_mgr->get_log_directory() + log_filename;
P3D_object *result = read_log(log_pathname, params + 1, num_params - 1);
return result;
}
/**
* log-reader meta function that handles reading previous log files in
* addition to the present one
*/
P3D_object *P3DMainObject::
read_log(const string &log_pathname, P3D_object *params[], int num_params) {
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
string log_directory = inst_mgr->get_log_directory();
std::ostringstream log_data;
// Check the first parameter, if any--if given, it specifies the last n
// bytes to retrieve.
size_t tail_bytes = 0;
if (num_params > 0) {
tail_bytes = (size_t)max(P3D_OBJECT_GET_INT(params[0]), 0);
}
// Check the second parameter, if any--if given, it specifies the first n
// bytes to retrieve.
size_t head_bytes = 0;
if (num_params > 1) {
head_bytes = (size_t)max(P3D_OBJECT_GET_INT(params[1]), 0);
}
// Check the third parameter, if any--if given, it specifies the last n
// bytes to retrieve from previous copies of this file.
size_t tail_bytes_prev = 0;
if (num_params > 2) {
tail_bytes_prev = (size_t)max(P3D_OBJECT_GET_INT(params[2]), 0);
}
// Check the fourth parameter, if any--if given, it specifies the first n
// bytes to retrieve from previous copies of this file.
size_t head_bytes_prev = 0;
if (num_params > 3) {
head_bytes_prev = (size_t)max(P3D_OBJECT_GET_INT(params[3]), 0);
}
// Determine the base of the log file names
nout << "log_pathname: " << log_pathname << "\n";
string log_basename = log_pathname;
size_t slash = log_basename.rfind('/');
if (slash != string::npos) {
log_basename = log_basename.substr(slash + 1);
}
#ifdef _WIN32
slash = log_basename.rfind('\\');
if (slash != string::npos) {
log_basename = log_basename.substr(slash + 1);
}
#endif // _WIN32
string log_leafname_primary = log_basename;
int dash = log_basename.rfind("-");
if (dash != string::npos) {
log_basename = log_basename.substr(0, dash+1);
} else {
int dotLog = log_basename.rfind(".log");
if (dotLog != string::npos) {
log_basename = log_basename.substr(0, dotLog);
log_basename += "-";
}
}
// Read matching files
std::vector<string> all_logs;
int log_matches_found = 0;
string log_matching_pathname;
inst_mgr->scan_directory(log_directory, all_logs);
for (int i = (int)all_logs.size() - 1; i >= 0; --i) {
if (all_logs[i] == log_leafname_primary ||
(all_logs[i].find(log_basename) == 0 &&
all_logs[i].size() > 4 &&
all_logs[i].substr(all_logs[i].size() - 4) == string(".log"))) {
log_matches_found++;
log_matching_pathname = (log_directory + all_logs[i]);
read_log_file(log_matching_pathname, tail_bytes, head_bytes, log_data);
tail_bytes = tail_bytes_prev;
head_bytes = head_bytes_prev;
}
}
if (log_matches_found == 0) {
nout << "read_log: warning: no matching file(s) on disk." << "\n";
}
string log_data_str = log_data.str();
P3D_object *result = new P3DStringObject(log_data_str);
return result;
}
/**
* The generic log-reader function.
*/
void P3DMainObject::
read_log_file(const string &log_pathname,
size_t tail_bytes, size_t head_bytes,
std::ostringstream &log_data) {
// Get leaf name
string log_leafname = log_pathname;
size_t slash = log_leafname.rfind('/');
if (slash != string::npos) {
log_leafname = log_leafname.substr(slash + 1);
}
#ifdef _WIN32
slash = log_leafname.rfind('\\');
if (slash != string::npos) {
log_leafname = log_leafname.substr(slash + 1);
}
#endif // _WIN32
// Render log file header to log_data
log_data << "=======================================";
log_data << "=======================================" << "\n";
log_data << "== PandaLog-" << log_pathname << "\n";
// load file
ifstream log(log_pathname.c_str(), ios::in);
if (!log) {
log_data << "== PandaLog-" << "Error opening file";
log_data << " " << "(" << log_leafname << ")" << "\n";
return;
}
// Get the size of the file.
log.seekg(0, ios::end);
size_t file_size = (size_t)log.tellg();
nout << "read_log: " << log_pathname << " is " << file_size
<< " bytes, tail_bytes = " << tail_bytes << ", head_bytes = "
<< head_bytes << "\n";
// Early out if the file is empty
if (file_size == (size_t)0) {
log_data << "== PandaLog-" << "Empty File";
log_data << " " << "(" << log_leafname << ")" << "\n";
return;
}
// Check if we are getting the full file
size_t full_bytes = 0;
if (file_size <= head_bytes + tail_bytes) {
// We will return the entire log.
full_bytes = file_size;
head_bytes = 0;
tail_bytes = 0;
}
// Allocate a temp buffer to hold file data
size_t buffer_bytes = max(max(full_bytes, head_bytes), tail_bytes) + 1;
nout << "allocating " << buffer_bytes << " bytes to read at a time from file of size " << file_size << ".\n";
char *buffer = new char[buffer_bytes];
if (buffer == nullptr) {
log_data << "== PandaLog-" << "Error allocating buffer";
log_data << " " << "(" << log_leafname << ")" << "\n";
return;
}
// Render log data if full file is to be fetched
if (full_bytes > 0) {
log.seekg(0, ios::beg);
log.read(buffer, full_bytes);
streamsize read_bytes = log.gcount();
assert(read_bytes < (streamsize)buffer_bytes);
buffer[read_bytes] = '\0';
log_data << "== PandaLog-" << "Full Start";
log_data << " " << "(" << log_leafname << ")" << "\n";
log_data << buffer;
log_data << "== PandaLog-" << "Full End";
log_data << " " << "(" << log_leafname << ")" << "\n";
}
// Render log data if head bytes are to be fetched
if (head_bytes > 0) {
log.seekg(0, ios::beg);
log.read(buffer, head_bytes);
streamsize read_bytes = log.gcount();
assert(read_bytes < (streamsize)buffer_bytes);
buffer[read_bytes] = '\0';
log_data << "== PandaLog-" << "Head Start";
log_data << " " << "(" << log_leafname << ")" << "\n";
log_data << buffer << "\n";
log_data << "== PandaLog-" << "Head End";
log_data << " " << "(" << log_leafname << ")" << "\n";
}
// Render separator if head & tail bytes are to be fetched
if ((head_bytes > 0) && (tail_bytes > 0)) {
log_data << "== PandaLog-" << "truncated";
log_data << " " << "(" << log_leafname << ")" << "\n";
}
// Render log data if tail bytes are to be fetched
if (tail_bytes > 0) {
log.seekg(file_size - tail_bytes, ios::beg);
log.read(buffer, tail_bytes);
streamsize read_bytes = log.gcount();
assert(read_bytes < (streamsize)buffer_bytes);
buffer[read_bytes] = '\0';
log_data << "== PandaLog-" << "Tail Start";
log_data << " " << "(" << log_leafname << ")" << "\n";
log_data << buffer;
log_data << "== PandaLog-" << "Tail End";
log_data << " " << "(" << log_leafname << ")" << "\n";
}
// Render log file footer to log_data log_data <<
// "======================================="; log_data <<
// "=======================================" << "\n";
// cleanup
delete[] buffer;
buffer = nullptr;
}
/**
* Implements the uninstall() plugin method, which removes all Panda installed
* files for a particular host, or referenced by a particular p3d file.
*/
P3D_object *P3DMainObject::
call_uninstall(P3D_object *params[], int num_params) {
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
// Get the first parameter, the uninstall mode.
string mode;
if (num_params > 0) {
int size = P3D_OBJECT_GET_STRING(params[0], nullptr, 0);
char *buffer = new char[size];
P3D_OBJECT_GET_STRING(params[0], buffer, size);
mode = string(buffer, size);
delete[] buffer;
}
if (mode == "all") {
nout << "uninstall all\n";
inst_mgr->uninstall_all();
return inst_mgr->new_bool_object(true);
}
if (_inst != nullptr) {
nout << "uninstall " << mode << " for " << _inst << "\n";
bool success = false;
if (mode == "host") {
success = _inst->uninstall_host();
} else {
success = _inst->uninstall_packages();
}
return inst_mgr->new_bool_object(success);
}
nout << "couldn't uninstall; no instance.\n";
return inst_mgr->new_bool_object(false);
}