/* === S Y N F I G ========================================================= */ /*! \file synfigapp/pluginmanager.cpp ** \brief Plugin Manager responsible for loading plugins ** ** $Id$ ** ** \legal ** Copyright (c) 2012-2013 Konstantin Dmitriev ** ** This package is free software; you can redistribute it and/or ** modify it under the terms of the GNU General Public License as ** published by the Free Software Foundation; either version 2 of ** the License, or (at your option) any later version. ** ** This package is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** General Public License for more details. ** \endlegal */ /* ========================================================================= */ /* === H E A D E R S ======================================================= */ #ifdef USING_PCH # include "pch.h" #else #ifdef HAVE_CONFIG_H # include #endif #include "pluginmanager.h" #include #include #include #include #include #include #include #endif /* === U S I N G =========================================================== */ using namespace std; using namespace etl; using namespace synfig; using namespace synfigapp; /* === M A C R O S ========================================================= */ /* === G L O B A L S ======================================================= */ /* === M E T H O D S ======================================================= */ PluginLauncher::PluginLauncher(synfig::Canvas::Handle canvas) { // Save the original filename filename_original = canvas->get_file_name(); String filename_base; if (is_absolute_path(filename_original)) { filename_base = filename_original; } else { filename_base = synfigapp::Main::get_user_app_directory()+ETL_DIRECTORY_SEPARATOR+"tmp"+ETL_DIRECTORY_SEPARATOR+filename_original; } // Make random filename and ensure there's no file with such name exist struct stat buf; // Filename to save the file for processing do { synfig::GUID guid; filename_processed = filename_base+"."+guid.get_string().substr(0,8); } while (stat(filename_processed.c_str(), &buf) != -1); // We need a copy (filename_backup) in case of plugin execution will fail. // The tmp_filename_orig will store original unmodified version of file // If plugin will fail, then tmp_filename can be damaged and we should reopen // tmp_filename_orig instead. do { synfig::GUID guid; filename_backup = filename_base+"."+guid.get_string().substr(0,8); } while (stat(filename_backup.c_str(), &buf) != -1); save_canvas(canvas->get_identifier().file_system->get_identifier(filename_processed),canvas); save_canvas(canvas->get_identifier().file_system->get_identifier(filename_backup),canvas); //canvas=0; exitcode=-1; output=""; } bool PluginLauncher::check_python_version(String path) { String command; String result; command = path + " --version 2>&1"; FILE* pipe = popen(command.c_str(), "r"); if (!pipe) { return false; } char buffer[128]; while(!feof(pipe)) { if(fgets(buffer, 128, pipe) != NULL) result += buffer; } pclose(pipe); // Output is like: "Python 3.3.0" if (result.substr(7,1) != "3"){ return false; } return true; } bool #ifdef WIN32 PluginLauncher::execute( std::string script_path, const std::string& synfig_root ) #else PluginLauncher::execute( std::string script_path, const std::string& /* synfig_root */ ) #endif { String command = ""; // Path to python binary can be overriden // with SYNFIG_PYTHON_BINARY env variable: char* custom_python_binary=getenv("SYNFIG_PYTHON_BINARY"); if(custom_python_binary) { command=custom_python_binary; if (!check_python_version(command)) { output="Error: You need to have Python 3 installed."; return false; } } else { // Set path to python binary depending on the os type. // For Windows case Python binary is expected // at INSTALL_PREFIX/python/python.exe std::list< String > binary_choices; binary_choices.push_back("python"); binary_choices.push_back("python3"); std::list< String >::iterator iter; for(iter=binary_choices.begin();iter!=binary_choices.end();iter++) { String python_path; #ifdef WIN32 python_path = "\"" + synfig_root+ETL_DIRECTORY_SEPARATOR+"python"+ETL_DIRECTORY_SEPARATOR+*iter+".exe" + "\""; #else python_path = *iter; #endif if (check_python_version(python_path)) { command = python_path; break; } } if (command == "") { output=_("Error: No Python 3 binary found.\n\nHint: You can set SYNFIG_PYTHON_BINARY environment variable pointing at your custom python installation."); return false; } } synfig::info("Python 3 binary found: "+command); // Construct the full command: command = command+" \""+script_path+"\" \""+filename_processed+"\" 2>&1"; #ifdef WIN32 // This covers the dumb cmd.exe behavior. // See: http://eli.thegreenplace.net/2011/01/28/on-spaces-in-the-paths-of-programs-and-files-on-windows/ command = "\"" + command + "\""; #endif FILE* pipe = popen(command.c_str(), "r"); if (!pipe) { output = "ERROR: pipe failed!"; return false; } char buffer[128]; while(!feof(pipe)) { if(fgets(buffer, 128, pipe) != NULL) output += buffer; } if (output != "" ){ synfig::info(output); } exitcode=pclose(pipe); if (0==exitcode){ return true; } else { return false; } } std::string PluginLauncher::get_result_path() { if (0==exitcode){ return filename_processed; } else { return filename_backup; } } PluginLauncher::~PluginLauncher() { remove( filename_processed.c_str() ); remove( filename_backup.c_str() ); } PluginManager::PluginManager(): list_() { } // END of synfigapp::PluginManager::PluginManager() void PluginManager::load_dir( const std::string &pluginsprefix ) { synfig::info("Loading plugins from %s", pluginsprefix.c_str()); DIR *dir; struct dirent *entry; dir = opendir(pluginsprefix.c_str()); if(dir) { while ( (entry = readdir(dir)) != NULL) { if ( std::string(entry->d_name) != std::string(".") && std::string(entry->d_name) != std::string("..") ) { std::string pluginpath; pluginpath = pluginsprefix+ETL_DIRECTORY_SEPARATOR+entry->d_name; struct stat sb; stat(pluginpath.c_str(), &sb); // error handling if stat failed if (S_ISDIR(sb.st_mode)) { // checking if directory contains a plugin... DIR *plugindir; struct dirent *plugindirentry; plugindir = opendir(pluginpath.c_str()); if(!plugindir) { synfig::warning("Can't read plugin directory!"); return; } while ( (plugindirentry = readdir(plugindir)) != NULL) { if ( std::string(plugindirentry->d_name) == std::string("plugin.xml") ){ std::string pluginfilepath; pluginfilepath = pluginpath+ETL_DIRECTORY_SEPARATOR+plugindirentry->d_name; load_plugin(pluginfilepath); } } } } }; closedir(dir); } } // END of synfigapp::PluginManager::load_dir() void PluginManager::load_plugin( const std::string &path ) { // Get locale std::string current_locale = setlocale(LC_ALL, NULL); synfig::info(" Loading plugin: %s", basename(dirname(path)).c_str()); PluginManager::plugin p; std::string plugindir = dirname(path); p.id=plugindir; // parse xml file try { xmlpp::DomParser parser; //parser.set_validate(); parser.set_substitute_entities(); //We just want the text to be resolved/unescaped automatically. parser.parse_file(path); if(parser) { //Walk the tree: const xmlpp::Node* pNode = parser.get_document()->get_root_node(); //deleted by DomParser. if ( std::string(pNode->get_name()) == std::string("plugin") ){ //Recurse through child nodes: xmlpp::Node::NodeList list = pNode->get_children(); unsigned int name_relevance = 0; for(xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter) { const xmlpp::Node* node = *iter; if ( std::string(node->get_name()) == std::string("name") ) { const xmlpp::Element* nodeElement = dynamic_cast(node); xmlpp::Node::NodeList l = nodeElement->get_children(); xmlpp::Node::NodeList::iterator i = l.begin(); xmlpp::Node* n = *i; const xmlpp::TextNode* nodeText = dynamic_cast(n); if(nodeText) { // Get the language attribute const xmlpp::Attribute* langAttribute = nodeElement->get_attribute("lang", "xml"); if (langAttribute) { // Element have language attribute, std::string lang = langAttribute->get_value(); // let's compare it with current locale if (!current_locale.compare(0, lang.size(), lang)) { if (lang.size() > name_relevance){ p.name=nodeText->get_content(); } } } else { // Element have no language attribute - use as fallback if (name_relevance == 0){ p.name=nodeText->get_content(); } } } } else if ( std::string(node->get_name()) == std::string("exec") ) { xmlpp::Node::NodeList l = node->get_children(); xmlpp::Node::NodeList::iterator i = l.begin(); xmlpp::Node* n = *i; const xmlpp::TextNode* nodeText = dynamic_cast(n); if(nodeText) { p.path=plugindir+ETL_DIRECTORY_SEPARATOR+nodeText->get_content(); } } } } else { synfig::info("Invalid plugin.xml file."); } } } catch(const std::exception& ex) { std::cout << "Exception caught: " << ex.what() << std::endl; } if ( p.id != "" && p.name != "" && p.path != ""){ list_.push_back(p); } else { synfig::warning("Invalid plugin.xml file!"); } } PluginManager::~PluginManager() { }