dmppythonutils.cpp 7.8 KB
/**************************************************************************
* file:              dmppythonutils.cpp

* Author:            wanzhongping
* Date:              2021-04-20 13:38:27
* Email:             zhongpingw@chinadci.com
* copyright:         广州城市信息研究所有限公司
***************************************************************************/


#include "dmppythonutils.h"
#include "dmplogger.h"
#include "dmpapplication.h"
#include "../dmpserverinterface.h"
#include <boost/format.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>

namespace python = boost::python;

#define INIT_MODULE PyInit_dmap_server
extern "C" PyObject *INIT_MODULE();

DmpPythonUtils::DmpPythonUtils()
{
}

DmpPythonUtils::~DmpPythonUtils()
{
    ExitPython();
}

void DmpPythonUtils::Init()
{
    if (PyImport_AppendInittab("dmap_server", INIT_MODULE) == -1)
        throw std::runtime_error("Failed to add dmap_server to the interpreter's "
                                 "builtin modules");
    Py_Initialize();
    python_enabled_ = true;
    main_module_ = python::import("__main__");
    main_namespace_ = main_module_.attr("__dict__");
}

bool DmpPythonUtils::CheckSystemImports()
{
    RunString("import sys");
    RunString("import os");

    std::string pluginpaths;
    std::vector<std::string> vec_paths = ExtraPluginsPaths();
    std::vector<std::string>::iterator iter;
    std::string comma = ",";
    for (iter = vec_paths.begin(); iter != vec_paths.end(); iter++)
    {
        if (!boost::filesystem::exists(*iter))
        {
            boost::format fmt = boost::format("The extra plugin path '%1' does not exist!") % *iter;
            LOGGER_WARN(fmt.str());
        }

        if (!pluginpaths.empty())
        {
            pluginpaths += comma;
        }
        pluginpaths += '"' + *iter + '"';
    }
    if (!pluginpaths.empty())
    {
        pluginpaths += comma;
    }
    pluginpaths += '"' + HomePluginsPath() + '"';
    pluginpaths += comma + '"' + PluginsPath() + '"';

    // expect that bindings are installed locally, so add the path to modules
    // also add path to plugins
    std::string newpaths;
    newpaths += '"' + PythonPath() + '"' + comma;
    newpaths += '"' + HomePythonPath() + '"' + comma;
    newpaths += pluginpaths;

    RunString("sys.path = [" + newpaths + "] + sys.path");

    if (!RunString("import dmap.utils"))
    {
        return false;
    }

    boost::format fmt_plugin_paths = boost::format("dmap.utils.plugin_paths = [%1%]") % pluginpaths;
    RunString(fmt_plugin_paths.str());
    boost::format fmt_sys_plugin_paths = boost::format("dmap.utils.sys_plugin_path = \"%1%\"") % PluginsPath();
    RunString(fmt_sys_plugin_paths.str());
    boost::format fmt_home_plugin_paths = boost::format("dmap.utils.home_plugin_path = \"%1%\"") % HomePluginsPath();
    RunString(fmt_home_plugin_paths.str());

    return true;
}

bool DmpPythonUtils::RunString(const std::string &command, bool single)
{
    try
    {
        std::cout << command << std::endl;
        if(single){
            python::exec(command.c_str(), main_namespace_, main_namespace_);
        }else
        {
            python::exec_file(command.c_str(), main_namespace_, main_namespace_);
        }
        
    }
    catch (const python::error_already_set &)
    {
        std::cerr << ">>> Error! Uncaught exception:\n";
        PyErr_Print();
        return false;
    }
    return true;
}

bool DmpPythonUtils::EvalString(const std::string &command, std::string &result )
{
    try
    {
        std::cout << command << std::endl;
        python::object  res = python::eval(command.c_str(), main_namespace_, main_namespace_);
        result = python::extract<std::string>(res);
    }
    catch (const python::error_already_set &)
    {
        std::cerr << ">>> Error! Uncaught exception:\n";
        PyErr_Print();
        return false;
    }
    return true;
}


void DmpPythonUtils::InitServerPython(DmpServerInterface *interface)
{
    Init();
    if (!CheckSystemImports())
    {
        ExitPython();
        return;
    }
    
    try
    {
        python::import("dmap_server");
        main_namespace_["serverIface_"] = boost::python::ptr(interface);
        RunString("dmap.utils.initServerInterface(serverIface_)");
    }
    catch (python::error_already_set &e)
    {
        PyErr_PrintEx(0);
    }
    //Finish();
}

bool DmpPythonUtils::StartServerPlugin(std::string packageName)
{
    boost::format fmt = boost::format("dmap.utils.startServerPlugin('%1%')") % packageName;
    try
    {    
        std::cout << fmt.str() << std::endl; 
        python::object res = python::eval(fmt.str().c_str(), main_namespace_, main_namespace_);
        return python::extract<bool>(res);
    }
    catch (const python::error_already_set &)
    {
        std::cerr << ">>> Error! Uncaught exception:\n";
        PyErr_Print();
    }
    return false;
}

void DmpPythonUtils::Finish()
{
    // release GIL!
    // Later on, we acquire GIL just before doing some Python calls and
    // release GIL again when the work with Python API is done.
    // (i.e. there must be PyGILState_Ensure + PyGILState_Release pair
    // around any calls to Python API, otherwise we may segfault!)
    //main_state_ = PyEval_SaveThread();
}

std::vector<std::string> DmpPythonUtils::ExtraPluginsPaths() const
{
    std::vector<std::string> vec_paths;
    const char *paths = getenv("DMAP_PLUGINPATH");
    if (paths)
    {
        if (boost::icontains(paths, ";"))
        {
            boost::split(vec_paths, paths, boost::is_any_of(";"), boost::token_compress_on);
        }
        else
        {
            vec_paths.push_back(paths);
        }
    }
    return vec_paths;
}

std::string DmpPythonUtils::PythonPath() const
{
    if (DmpApplication::IsRunningFromBuildDir())
        return DmpApplication::build_output_path() + "/python";
    else
        return DmpApplication::pkg_data_path() + "/python";
}

std::string DmpPythonUtils::PluginsPath() const
{
    return PythonPath() + "/plugins";
}

std::string DmpPythonUtils::HomePythonPath() const
{
    std::string settingsDir = DmpApplication::DMapSettingsDirPath();
    boost::format fmt = boost::format("%1%/python") % settingsDir;
    return fmt.str();
}

std::string DmpPythonUtils::HomePluginsPath() const
{
    return HomePythonPath() + "/plugins";
}

bool DmpPythonUtils::IsEnabled()
{
    return python_enabled_;
}

bool DmpPythonUtils::LoadPlugin(const std::string &packageName)
{
    boost::format fmt = boost::format("dmap.utils.loadPlugin('%1%')") % packageName;
    try
    {   std::cout << fmt.str() << std::endl; 
        python::object res = python::eval(fmt.str().c_str(), main_namespace_, main_namespace_);
        return python::extract<bool>(res);
    }
    catch (const python::error_already_set &)
    {
        std::cerr << ">>> Error! Uncaught exception:\n";
        PyErr_Print();
    }
    return false;
}

void DmpPythonUtils::UninstallErrorHook()
{
    RunString("qgis.utils.uninstallErrorHook()");
}
std::string DmpPythonUtils::GetPluginMetadata(const std::string &pluginName, const std::string &function)
{
    std::string res;
    boost::format fmt = boost::format("dmap.utils.pluginMetadata('%1%', '%2%')") % pluginName % function;
    EvalString(fmt.str(), res);
    LOGGER_DEBUG("metadata " + pluginName + " - '" + function + "' = " + res);
    return res;
}
std::vector<std::string> DmpPythonUtils::PluginList()
{
    RunString("dmap.utils.updateAvailablePlugins()");

    std::string output;
    EvalString("'\\n'.join(dmap.utils.available_plugins)", output);

    std::vector<std::string> vec_plugins;
    boost::split(vec_plugins, output, boost::is_any_of("\n"), boost::token_compress_on);
    return vec_plugins;
}
void DmpPythonUtils::ExitPython()
{
    if (error_hook_installed_)
        UninstallErrorHook();
    // causes segfault!
    //Py_Finalize();
    // main_module_ = nullptr;
    // main_dict_ = nullptr;
    python_enabled_ = false;
}