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