1 /* === S Y N F I G ========================================================= */
2 /*! \file synfigapp/pluginmanager.cpp
3 ** \brief Plugin Manager responsible for loading plugins
4 **
5 ** $Id$
6 **
7 ** \legal
8 ** Copyright (c) 2012-2013 Konstantin Dmitriev
9 **
10 ** This package is free software; you can redistribute it and/or
11 ** modify it under the terms of the GNU General Public License as
12 ** published by the Free Software Foundation; either version 2 of
13 ** the License, or (at your option) any later version.
14 **
15 ** This package is distributed in the hope that it will be useful,
16 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 ** General Public License for more details.
19 ** \endlegal
20 */
21 /* ========================================================================= */
22
23 /* === H E A D E R S ======================================================= */
24
25 #ifdef USING_PCH
26 # include "pch.h"
27 #else
28 #ifdef HAVE_CONFIG_H
29 # include <config.h>
30 #endif
31
32 #include "pluginmanager.h"
33
34 #include <iostream>
35
36 #include <libxml++/libxml++.h>
37
38 #include <dirent.h>
39 #include <sys/stat.h>
40
41 #include <synfig/general.h>
42 #include <synfig/savecanvas.h>
43 #include <synfig/filesystemnative.h>
44 #include <synfigapp/main.h>
45
46 #include <synfigapp/localization.h>
47
48 #endif
49
50 /* === U S I N G =========================================================== */
51
52 using namespace std;
53 using namespace etl;
54 using namespace synfig;
55 using namespace synfigapp;
56
57 /* === M A C R O S ========================================================= */
58
59
60 /* === G L O B A L S ======================================================= */
61
62 /* === M E T H O D S ======================================================= */
63
PluginLauncher(synfig::Canvas::Handle canvas)64 PluginLauncher::PluginLauncher(synfig::Canvas::Handle canvas)
65 {
66 // Save the original filename
67 filename_original = canvas->get_file_name();
68
69 String filename_base;
70 if (is_absolute_path(filename_original))
71 {
72 filename_base = filename_original;
73 } else {
74 filename_base = synfigapp::Main::get_user_app_directory()+ETL_DIRECTORY_SEPARATOR+"tmp"+ETL_DIRECTORY_SEPARATOR+filename_original;
75 }
76
77 // Make random filename and ensure there's no file with such name exist
78 struct stat buf;
79
80 // Filename to save the file for processing
81 do {
82 synfig::GUID guid;
83 filename_processed = filename_base+"."+guid.get_string().substr(0,8)+".sif"; // without .sif suffix it won't be read back
84 } while (stat(filename_processed.c_str(), &buf) != -1);
85
86 /* The plugin could die with nonzero exit code
87 * synfig could crash loading the modified file (should not happen)
88 * having a backup file should protect against both cases
89 */
90 do {
91 synfig::GUID guid;
92 filename_backup = filename_base+"."+guid.get_string().substr(0,8)+".sif";
93 } while (stat(filename_backup.c_str(), &buf) != -1);
94
95 save_canvas(FileSystemNative::instance()->get_identifier(filename_processed),canvas);
96 // copy file would be faster ..
97 save_canvas(FileSystemNative::instance()->get_identifier(filename_backup),canvas);
98
99 //canvas=0;
100 exitcode=-1;
101 output="";
102 }
103
104 bool
check_python_version(String path)105 PluginLauncher::check_python_version(String path)
106 {
107 String command;
108 String result;
109 command = path + " --version 2>&1";
110 FILE* pipe = popen(command.c_str(), "r");
111 if (!pipe) {
112 return false;
113 }
114 char buffer[128];
115 while(!feof(pipe)) {
116 if(fgets(buffer, 128, pipe) != NULL)
117 result += buffer;
118 }
119 pclose(pipe);
120 // Output is like: "Python 3.3.0"
121 if (result.substr(7,1) != "3"){
122 return false;
123 }
124 return true;
125 }
126
127 bool
128 #ifdef _WIN32
execute(std::string script_path,const std::string & synfig_root)129 PluginLauncher::execute( std::string script_path, const std::string& synfig_root )
130 #else
131 PluginLauncher::execute( std::string script_path, const std::string& /* synfig_root */ )
132 #endif
133 {
134 String command = "";
135
136 // Path to python binary can be overriden
137 // with SYNFIG_PYTHON_BINARY env variable:
138 char* custom_python_binary=getenv("SYNFIG_PYTHON_BINARY");
139 if(custom_python_binary) {
140 command=custom_python_binary;
141 if (!check_python_version(command)) {
142 output="Error: You need to have Python 3 installed.";
143 return false;
144 }
145 } else {
146 // Set path to python binary depending on the os type.
147 // For Windows case Python binary is expected
148 // at INSTALL_PREFIX/python/python.exe
149 std::list< String > binary_choices;
150 binary_choices.push_back("python");
151 binary_choices.push_back("python3");
152 std::list< String >::iterator iter;
153 for(iter=binary_choices.begin();iter!=binary_choices.end();iter++)
154 {
155 String python_path;
156 #ifdef _WIN32
157 python_path = "\"" + synfig_root+ETL_DIRECTORY_SEPARATOR+"python"+ETL_DIRECTORY_SEPARATOR+*iter+".exe" + "\"";
158 #else
159 python_path = *iter;
160 #endif
161 if (check_python_version(python_path))
162 {
163 command = python_path;
164 break;
165 }
166
167 }
168 if (command == "")
169 {
170 output=_("Error: No Python 3 binary found.\n\nHint: You can set SYNFIG_PYTHON_BINARY environment variable pointing at your custom python installation.");
171 return false;
172 }
173 }
174 synfig::info("Python 3 binary found: "+command);
175
176
177 // Construct the full command:
178 command = command+" \""+script_path+"\" \""+filename_processed+"\" 2>&1";
179 #ifdef _WIN32
180 // This covers the dumb cmd.exe behavior.
181 // See: http://eli.thegreenplace.net/2011/01/28/on-spaces-in-the-paths-of-programs-and-files-on-windows/
182 command = "\"" + command + "\"";
183 #endif
184
185 FILE* pipe = popen(command.c_str(), "r");
186 if (!pipe) {
187 output = "ERROR: pipe failed!";
188 return false;
189 }
190 char buffer[128];
191 while(!feof(pipe)) {
192 if(fgets(buffer, 128, pipe) != NULL)
193 output += buffer;
194 }
195
196 if (output != "" ){
197 synfig::info(output);
198 }
199
200 exitcode=pclose(pipe);
201
202 if (0==exitcode){
203 return true;
204 } else {
205 return false;
206 }
207 }
208
209 std::string
get_result_path()210 PluginLauncher::get_result_path()
211 {
212 if (0==exitcode){
213 return filename_processed;
214 } else {
215 return filename_backup;
216 }
217 }
218
~PluginLauncher()219 PluginLauncher::~PluginLauncher()
220 {
221 remove( filename_processed.c_str() );
222 remove( filename_backup.c_str() );
223 }
224
PluginManager()225 PluginManager::PluginManager():
226 list_()
227 {
228 } // END of synfigapp::PluginManager::PluginManager()
229
230 void
load_dir(const std::string & pluginsprefix)231 PluginManager::load_dir( const std::string &pluginsprefix )
232 {
233
234 synfig::info("Loading plugins from %s", pluginsprefix.c_str());
235
236 DIR *dir;
237 struct dirent *entry;
238
239 dir = opendir(pluginsprefix.c_str());
240 if(dir) {
241 while ( (entry = readdir(dir)) != NULL) {
242 if ( std::string(entry->d_name) != std::string(".") && std::string(entry->d_name) != std::string("..") ) {
243 std::string pluginpath;
244 pluginpath = pluginsprefix+ETL_DIRECTORY_SEPARATOR+entry->d_name;
245 struct stat sb;
246 stat(pluginpath.c_str(), &sb);
247 // error handling if stat failed
248 if (S_ISDIR(sb.st_mode)) {
249 // checking if directory contains a plugin...
250 DIR *plugindir;
251 struct dirent *plugindirentry;
252
253 plugindir = opendir(pluginpath.c_str());
254 if(!plugindir) {
255 synfig::warning("Can't read plugin directory!");
256 return;
257 }
258
259 while ( (plugindirentry = readdir(plugindir)) != NULL) {
260 if ( std::string(plugindirentry->d_name) == std::string("plugin.xml") ){
261 std::string pluginfilepath;
262 pluginfilepath = pluginpath+ETL_DIRECTORY_SEPARATOR+plugindirentry->d_name;
263
264 load_plugin(pluginfilepath);
265 }
266 }
267
268 }
269 }
270
271 };
272
273 closedir(dir);
274 }
275 } // END of synfigapp::PluginManager::load_dir()
276
277 void
load_plugin(const std::string & path)278 PluginManager::load_plugin( const std::string &path )
279 {
280 // Get locale
281 std::string current_locale = setlocale(LC_ALL, NULL);
282
283 synfig::info(" Loading plugin: %s", basename(dirname(path)).c_str());
284
285 PluginManager::plugin p;
286 std::string plugindir = dirname(path);
287 p.id=plugindir;
288
289 // parse xml file
290 try
291 {
292 xmlpp::DomParser parser;
293 //parser.set_validate();
294 parser.set_substitute_entities(); //We just want the text to be resolved/unescaped automatically.
295 parser.parse_file(path);
296 if(parser)
297 {
298 //Walk the tree:
299 const xmlpp::Node* pNode = parser.get_document()->get_root_node(); //deleted by DomParser.
300 if ( std::string(pNode->get_name()) == std::string("plugin") ){
301 //Recurse through child nodes:
302 xmlpp::Node::NodeList list = pNode->get_children();
303
304 unsigned int name_relevance = 0;
305
306 for(xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
307 {
308 const xmlpp::Node* node = *iter;
309 if ( std::string(node->get_name()) == std::string("name") ) {
310
311 const xmlpp::Element* nodeElement = dynamic_cast<const xmlpp::Element*>(node);
312
313 xmlpp::Node::NodeList l = nodeElement->get_children();
314 xmlpp::Node::NodeList::iterator i = l.begin();
315 xmlpp::Node* n = *i;
316
317 const xmlpp::TextNode* nodeText = dynamic_cast<const xmlpp::TextNode*>(n);
318
319 if(nodeText)
320 {
321 // Get the language attribute
322 const xmlpp::Attribute* langAttribute = nodeElement->get_attribute("lang", "xml");
323
324 if (langAttribute) {
325 // Element have language attribute,
326 std::string lang = langAttribute->get_value();
327 // let's compare it with current locale
328 if (!current_locale.compare(0, lang.size(), lang)) {
329 if (lang.size() > name_relevance){
330 p.name=nodeText->get_content();
331 }
332 }
333 } else {
334 // Element have no language attribute - use as fallback
335 if (name_relevance == 0){
336 p.name=nodeText->get_content();
337 }
338 }
339 }
340
341 } else if ( std::string(node->get_name()) == std::string("exec") ) {
342
343 xmlpp::Node::NodeList l = node->get_children();
344 xmlpp::Node::NodeList::iterator i = l.begin();
345 xmlpp::Node* n = *i;
346
347 const xmlpp::TextNode* nodeText = dynamic_cast<const xmlpp::TextNode*>(n);
348
349 if(nodeText)
350 {
351 p.path=plugindir+ETL_DIRECTORY_SEPARATOR+nodeText->get_content();
352 }
353 }
354 }
355 } else {
356 synfig::info("Invalid plugin.xml file.");
357 }
358 }
359 }
360 catch(const std::exception& ex)
361 {
362 std::cout << "Exception caught: " << ex.what() << std::endl;
363 }
364
365 if ( p.id != "" && p.name != "" && p.path != ""){
366 list_.push_back(p);
367 } else {
368 synfig::warning("Invalid plugin.xml file!");
369 }
370 }
371
~PluginManager()372 PluginManager::~PluginManager()
373 {
374 }
375