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