X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fextension%2Fimplementation%2Fscript.cpp;h=428ee626fd361f79342dc7624d67f192e62da165;hb=9374eea4b2a4b72eeb5e7cc0ebc6db99931a48df;hp=e28015f6f57ed73c08af85cc2775a7b7a6c7c980;hpb=b6ae2ff55ce50b104521cce0a0c2e96f40881006;p=inkscape.git diff --git a/src/extension/implementation/script.cpp b/src/extension/implementation/script.cpp index e28015f6f..428ee626f 100644 --- a/src/extension/implementation/script.cpp +++ b/src/extension/implementation/script.cpp @@ -5,8 +5,10 @@ * Authors: * Bryce Harrington * Ted Gould + * Jon A. Cruz + * Abhishek Sharma * - * Copyright (C) 2002-2005 Authors + * Copyright (C) 2002-2005,2007 Authors * * Released under GNU GPL, read the file 'COPYING' for more information */ @@ -20,51 +22,146 @@ #include #include -#include -#include +#include +#include +#include #include "ui/view/view.h" #include "desktop-handles.h" +#include "desktop.h" #include "selection.h" #include "sp-namedview.h" #include "io/sys.h" -#include "prefs-utils.h" +#include "preferences.h" #include "../system.h" #include "extension/effect.h" +#include "extension/output.h" +#include "extension/input.h" #include "extension/db.h" #include "script.h" +#include "dialogs/dialog-events.h" +#include "inkscape.h" +#include "xml/node.h" +#include "xml/attribute-record.h" #include "util/glib-list-iterators.h" + + #ifdef WIN32 #include +#include +#include "registrytool.h" #endif + + /** This is the command buffer that gets allocated from the stack */ #define BUFSIZE (255) + + /* Namespaces */ namespace Inkscape { namespace Extension { namespace Implementation { -/* Real functions */ -/** - \return A script object - \brief This function creates a script object and sets up the +/** \brief Make GTK+ events continue to come through a little bit + + This just keeps coming the events through so that we'll make the GUI + update and look pretty. +*/ +void Script::pump_events (void) { + while ( Gtk::Main::events_pending() ) { + Gtk::Main::iteration(); + } + return; +} + + +/** \brief A table of what interpreters to call for a given language + + This table is used to keep track of all the programs to execute a + given script. It also tracks the preference to use to overwrite + the given interpreter to a custom one per user. +*/ +Script::interpreter_t const Script::interpreterTab[] = { + {"perl", "perl-interpreter", "perl" }, +#ifdef WIN32 + {"python", "python-interpreter", "pythonw" }, +#else + {"python", "python-interpreter", "python" }, +#endif + {"ruby", "ruby-interpreter", "ruby" }, + {"shell", "shell-interpreter", "sh" }, + { NULL, NULL, NULL } +}; + + + +/** \brief Look up an interpreter name, and translate to something that + is executable + \param interpNameArg The name of the interpreter that we're looking + for, should be an entry in interpreterTab +*/ +std::string Script::resolveInterpreterExecutable(const Glib::ustring &interpNameArg) +{ + interpreter_t const *interp = 0; + bool foundInterp = false; + for (interp = interpreterTab ; interp->identity ; interp++ ){ + if (interpNameArg == interp->identity) { + foundInterp = true; + break; + } + } + + // Do we have a supported interpreter type? + if (!foundInterp) { + return ""; + } + std::string interpreter_path = Glib::filename_from_utf8(interp->defaultval); + + // 1. Check preferences for an override. + // Note: this must be an absolute path. + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Glib::ustring prefInterp = prefs->getString("/extensions/" + Glib::ustring(interp->prefstring)); + + if (!prefInterp.empty()) { + interpreter_path = Glib::filename_from_utf8(prefInterp); + } + + // 2. Search the path. + // Do this on all systems, for consistency. + // PATH is set up to contain the Python and Perl binary directories + // on Windows, so no extra code is necessary. + if (!Glib::path_is_absolute(interpreter_path)) { + interpreter_path = Glib::find_program_in_path(interpreter_path); + } + return interpreter_path; +} + +/** \brief This function creates a script object and sets up the variables. + \return A script object This function just sets the command to NULL. It should get built officially in the load function. This allows for less allocation of memory in the unloaded state. */ Script::Script() : - Implementation(), - command(NULL), - helper_extension(NULL) + Implementation() +{ +} + +/** + * brief Destructor + */ +Script::~Script() { } + + /** \return A string with the complete string with the relative directory expanded \brief This function takes in a Repr that contains a reldir entry @@ -80,29 +177,40 @@ Script::Script() : string. This means that the caller of this function can always free what they are given (and should do it too!). */ -gchar * +std::string Script::solve_reldir(Inkscape::XML::Node *reprin) { - gchar const *reldir = reprin->attribute("reldir"); - if (reldir == NULL) { - return g_strdup(sp_repr_children(reprin)->content()); + gchar const *s = reprin->attribute("reldir"); + + // right now the only recognized relative directory is "extensions" + if (!s || Glib::ustring(s) != "extensions") { + Glib::ustring str = sp_repr_children(reprin)->content(); + return str; } - if (!strcmp(reldir, "extensions")) { - for(unsigned int i=0; icontent(), NULL); - if ( Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS) ) { - return filename; - } - g_free(filename); + Glib::ustring reldir = s; + + for (unsigned int i=0; + i < Inkscape::Extension::Extension::search_path.size(); + i++) { + + gchar * fname = g_build_filename( + Inkscape::Extension::Extension::search_path[i], + sp_repr_children(reprin)->content(), + NULL); + Glib::ustring filename = fname; + g_free(fname); + + if ( Inkscape::IO::file_test(filename.c_str(), G_FILE_TEST_EXISTS) ) { + return Glib::filename_from_utf8(filename); } - } else { - return g_strdup(sp_repr_children(reprin)->content()); } - return NULL; + return ""; } + + /** \return Whether the command given exists, including in the path \brief This function is used to find out if something exists for @@ -119,66 +227,63 @@ Script::solve_reldir(Inkscape::XML::Node *reprin) { then a TRUE is returned. If we get all the way through the path then a FALSE is returned, the command could not be found. */ -bool -Script::check_existance(gchar const *command) +bool Script::check_existence(const std::string &command) { - if (*command == '\0') { - /* We check the simple case first. */ - return FALSE; - } - if (g_utf8_strchr(command, -1, G_DIR_SEPARATOR) != NULL) { - /* Don't search when it contains a slash. */ - if (Inkscape::IO::file_test(command, G_FILE_TEST_EXISTS)) - return TRUE; - else - return FALSE; + // Check the simple case first + if (command.empty()) { + return false; } + //Don't search when it is an absolute path. */ + if (Glib::path_is_absolute(command)) { + if (Glib::file_test(command, Glib::FILE_TEST_EXISTS)) { + return true; + } else { + return false; + } + } - gchar *path = g_strdup(g_getenv("PATH")); - if (path == NULL) { - /* There is no `PATH' in the environment. + std::string path = Glib::getenv("PATH"); + if (path.empty()) { + /* There is no `PATH' in the environment. The default search path is the current directory */ - path = g_strdup(G_SEARCHPATH_SEPARATOR_S); + path = G_SEARCHPATH_SEPARATOR_S; } - gchar *orig_path = path; - for (; path != NULL;) { - gchar *const local_path = path; - path = g_utf8_strchr(path, -1, G_SEARCHPATH_SEPARATOR); - if (path == NULL) { - break; - } - /* Not sure whether this is UTF8 happy, but it would seem - like it considering that I'm searching (and finding) - the ':' character */ - if (path != local_path && path != NULL) { - path[0] = '\0'; - path++; - } else { - path = NULL; - } + std::string::size_type pos = 0; + std::string::size_type pos2 = 0; + while ( pos < path.size() ) { + + std::string localPath; - gchar *final_name; - if (local_path == '\0') { - final_name = g_strdup(command); + pos2 = path.find(G_SEARCHPATH_SEPARATOR, pos); + if (pos2 == path.npos) { + localPath = path.substr(pos); + pos = path.size(); } else { - final_name = g_build_filename(local_path, command, NULL); + localPath = path.substr(pos, pos2-pos); + pos = pos2+1; } - if (Inkscape::IO::file_test(final_name, G_FILE_TEST_EXISTS)) { - g_free(final_name); - g_free(orig_path); - return TRUE; + //printf("### %s\n", localPath.c_str()); + std::string candidatePath = + Glib::build_filename(localPath, command); + + if (Glib::file_test(candidatePath, + Glib::FILE_TEST_EXISTS)) { + return true; } - g_free(final_name); } - return FALSE; + return false; } + + + + /** \return none \brief This function 'loads' an extention, basically it determines @@ -197,64 +302,30 @@ Script::check_existance(gchar const *command) and should error out at a higher level. */ -bool -Script::load(Inkscape::Extension::Extension *module) +bool Script::load(Inkscape::Extension::Extension *module) { if (module->loaded()) { - return TRUE; + return true; } - helper_extension = NULL; + helper_extension = ""; /* This should probably check to find the executable... */ Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr()); - gchar *command_text = NULL; while (child_repr != NULL) { - if (!strcmp(child_repr->name(), "script")) { + if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) { child_repr = sp_repr_children(child_repr); while (child_repr != NULL) { - if (!strcmp(child_repr->name(), "command")) { - command_text = solve_reldir(child_repr); - - const gchar * interpretstr = child_repr->attribute("interpreter"); + if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) { + const gchar *interpretstr = child_repr->attribute("interpreter"); if (interpretstr != NULL) { - struct interpreter_t { - gchar * identity; - gchar * prefstring; - gchar * defaultval; - }; - const interpreter_t interpreterlst[] = { - {"perl", "perl-interpreter", "perl"}, - {"python", "python-interpreter", "python"}, - {"ruby", "ruby-interpreter", "ruby"}, - {"shell", "shell-interpreter", "sh"} - }; /* Change count below if you change structure */ - for (unsigned int i = 0; i < 4; i++) { - if (!strcmp(interpretstr, interpreterlst[i].identity)) { - const gchar * insertText = interpreterlst[i].defaultval; - if (prefs_get_string_attribute("extensions", interpreterlst[i].prefstring) != NULL) - insertText = prefs_get_string_attribute("extensions", interpreterlst[i].prefstring); -#ifdef _WIN32 - else { - char szExePath[MAX_PATH]; - char szCurrentDir[MAX_PATH]; - GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir); - if (reinterpret_cast(FindExecutable(command_text, szCurrentDir, szExePath)) > 32) - insertText = szExePath; - } -#endif - - gchar * temp = command_text; - command_text = g_strconcat(insertText, " ", temp, NULL); - g_free(temp); - - break; - } - } + std::string interpString = resolveInterpreterExecutable(interpretstr); + command.insert(command.end(), interpString); } + command.insert(command.end(), solve_reldir(child_repr)); } - if (!strcmp(child_repr->name(), "helper_extension")) { - helper_extension = g_strdup(sp_repr_children(child_repr)->content()); + if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) { + helper_extension = sp_repr_children(child_repr)->content(); } child_repr = sp_repr_next(child_repr); } @@ -264,15 +335,12 @@ Script::load(Inkscape::Extension::Extension *module) child_repr = sp_repr_next(child_repr); } - g_return_val_if_fail(command_text != NULL, FALSE); - - if (command != NULL) - g_free(command); - command = command_text; + //g_return_val_if_fail(command.length() > 0, false); - return TRUE; + return true; } + /** \return None. \brief Unload this puppy! @@ -281,21 +349,15 @@ Script::load(Inkscape::Extension::Extension *module) This function just sets the module to unloaded. It free's the command if it has been allocated. */ -void -Script::unload(Inkscape::Extension::Extension *module) +void Script::unload(Inkscape::Extension::Extension */*module*/) { - if (command != NULL) { - g_free(command); - command = NULL; - } - if (helper_extension != NULL) { - g_free(helper_extension); - helper_extension = NULL; - } - - return; + command.clear(); + helper_extension = ""; } + + + /** \return Whether the check passed or not \brief Check every dependency that was given to make sure we should keep this extension @@ -305,28 +367,27 @@ Script::unload(Inkscape::Extension::Extension *module) bool Script::check(Inkscape::Extension::Extension *module) { + int script_count = 0; Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr()); while (child_repr != NULL) { - if (!strcmp(child_repr->name(), "script")) { + if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) { + script_count++; child_repr = sp_repr_children(child_repr); while (child_repr != NULL) { - if (!strcmp(child_repr->name(), "check")) { - gchar *command_text = solve_reldir(child_repr); - if (command_text != NULL) { + if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "check")) { + std::string command_text = solve_reldir(child_repr); + if (!command_text.empty()) { /* I've got the command */ - bool existance; - - existance = check_existance(command_text); - g_free(command_text); + bool existance = check_existence(command_text); if (!existance) - return FALSE; + return false; } } - if (!strcmp(child_repr->name(), "helper_extension")) { + if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) { gchar const *helper = sp_repr_children(child_repr)->content(); if (Inkscape::Extension::db.get(helper) == NULL) { - return FALSE; + return false; } } @@ -338,47 +399,80 @@ Script::check(Inkscape::Extension::Extension *module) child_repr = sp_repr_next(child_repr); } - return TRUE; + if (script_count == 0) { + return false; + } + + return true; } -/** - \return A dialog for preferences - \brief A stub funtion right now - \param module Module who's preferences need getting - \param filename Hey, the file you're getting might be important +class ScriptDocCache : public ImplementationDocumentCache { + friend class Script; +protected: + std::string _filename; + int _tempfd; +public: + ScriptDocCache (Inkscape::UI::View::View * view); + ~ScriptDocCache ( ); +}; - This function should really do something, right now it doesn't. -*/ -Gtk::Widget * -Script::prefs_input(Inkscape::Extension::Input *module, gchar const *filename) +ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) : + ImplementationDocumentCache(view), + _filename(""), + _tempfd(0) +{ + try { + _tempfd = Inkscape::IO::file_open_tmp(_filename, "ink_ext_XXXXXX.svg"); + } catch (...) { + /// \todo Popup dialog here + return; + } + + SPDesktop *desktop = (SPDesktop *) view; + sp_namedview_document_from_window(desktop); + + Inkscape::Extension::save( + Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE), + view->doc(), _filename.c_str(), false, false, false, Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY); + + return; +} + +ScriptDocCache::~ScriptDocCache ( ) { - /*return module->autogui(); */ - return NULL; + close(_tempfd); + unlink(_filename.c_str()); } +ImplementationDocumentCache *Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) { + return new ScriptDocCache(view); +} + + /** \return A dialog for preferences \brief A stub funtion right now - \param module Module whose preferences need getting + \param module Module who's preferences need getting + \param filename Hey, the file you're getting might be important This function should really do something, right now it doesn't. */ -Gtk::Widget * -Script::prefs_output(Inkscape::Extension::Output *module) +Gtk::Widget *Script::prefs_input(Inkscape::Extension::Input *module, + const gchar */*filename*/) { - /*return module->autogui();*/ - return NULL; + return module->autogui(NULL, NULL); } + + /** \return A dialog for preferences \brief A stub funtion right now - \param module Module who's preferences need getting + \param module Module whose preferences need getting This function should really do something, right now it doesn't. */ -Gtk::Widget * -Script::prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *view) +Gtk::Widget *Script::prefs_output(Inkscape::Extension::Output *module) { return module->autogui(NULL, NULL); } @@ -404,59 +498,54 @@ Script::prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::Vi the incoming filename (so that it's not the temporary filename). That document is then returned from this function. */ -SPDocument * -Script::open(Inkscape::Extension::Input *module, gchar const *filename) +SPDocument *Script::open(Inkscape::Extension::Input *module, + const gchar *filenameArg) { - int data_read = 0; - gint tempfd; - gchar *tempfilename_out; - - // FIXME: process the GError instead of passing NULL - if ((tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_out, NULL)) == -1) { - /* Error, couldn't create temporary filename */ - if (errno == EINVAL) { - /* The last six characters of template were not XXXXXX. Now template is unchanged. */ - perror("Extension::Script: template for filenames is misconfigured.\n"); - exit(-1); - } else if (errno == EEXIST) { - /* Now the contents of template are undefined. */ - perror("Extension::Script: Could not create a unique temporary filename\n"); - return NULL; - } else { - perror("Extension::Script: Unknown error creating temporary filename\n"); - exit(-1); - } + std::list params; + module->paramListString(params); + + std::string tempfilename_out; + int tempfd_out = 0; + try { + tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg"); + } catch (...) { + /// \todo Popup dialog here + return NULL; } - gsize bytesRead = 0; - gsize bytesWritten = 0; - GError *error = NULL; - gchar *local_filename = g_filename_from_utf8( filename, - -1, &bytesRead, &bytesWritten, &error); + std::string lfilename = Glib::filename_from_utf8(filenameArg); - data_read = execute(command, local_filename, tempfilename_out); - g_free(local_filename); + file_listener fileout; + int data_read = execute(command, params, lfilename, fileout); + fileout.toFile(tempfilename_out); - SPDocument *mydoc = NULL; + SPDocument * mydoc = NULL; if (data_read > 10) { - if (helper_extension == NULL) { - mydoc = Inkscape::Extension::open(Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), tempfilename_out); + if (helper_extension.size()==0) { + mydoc = Inkscape::Extension::open( + Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), + tempfilename_out.c_str()); } else { - mydoc = Inkscape::Extension::open(Inkscape::Extension::db.get(helper_extension), tempfilename_out); + mydoc = Inkscape::Extension::open( + Inkscape::Extension::db.get(helper_extension.c_str()), + tempfilename_out.c_str()); } - } + } // data_read - if (mydoc != NULL) - sp_document_set_uri(mydoc, (const gchar *)filename); + if (mydoc != NULL) { + mydoc->setBase(0); + mydoc->changeUriAndHrefs(filenameArg); + } // make sure we don't leak file descriptors from g_file_open_tmp - close(tempfd); - // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name - unlink(tempfilename_out); - g_free(tempfilename_out); + close(tempfd_out); + + unlink(tempfilename_out.c_str()); return mydoc; -} +} // open + + /** \return none @@ -466,6 +555,7 @@ Script::open(Inkscape::Extension::Input *module, gchar const *filename) \param module Extention to be used \param doc Document to be saved \param filename The name to save the final file as + \return false in case of any failure writing the file, otherwise true Well, at some point people need to save - it is really what makes the entire application useful. And, it is possible that someone @@ -482,51 +572,59 @@ Script::open(Inkscape::Extension::Input *module, gchar const *filename) put the output of the script into the final output file. We then delete the temporary file. */ -void -Script::save(Inkscape::Extension::Output *module, SPDocument *doc, gchar const *filename) +void Script::save(Inkscape::Extension::Output *module, + SPDocument *doc, + const gchar *filenameArg) { - gint tempfd; - gchar *tempfilename_in; - // FIXME: process the GError instead of passing NULL - if ((tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_in, NULL)) == -1) { - /* Error, couldn't create temporary filename */ - if (errno == EINVAL) { - /* The last six characters of template were not XXXXXX. Now template is unchanged. */ - perror("Extension::Script: template for filenames is misconfigured.\n"); - exit(-1); - } else if (errno == EEXIST) { - /* Now the contents of template are undefined. */ - perror("Extension::Script: Could not create a unique temporary filename\n"); - return; - } else { - perror("Extension::Script: Unknown error creating temporary filename\n"); - exit(-1); - } - } - - if (helper_extension == NULL) { - Inkscape::Extension::save(Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE), doc, tempfilename_in, FALSE, FALSE, FALSE); + std::list params; + module->paramListString(params); + + std::string tempfilename_in; + int tempfd_in = 0; + try { + tempfd_in = Inkscape::IO::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX.svg"); + } catch (...) { + /// \todo Popup dialog here + throw Inkscape::Extension::Output::save_failed(); + } + + if (helper_extension.size() == 0) { + Inkscape::Extension::save( + Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE), + doc, tempfilename_in.c_str(), false, false, false, + Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY); } else { - Inkscape::Extension::save(Inkscape::Extension::db.get(helper_extension), doc, tempfilename_in, FALSE, FALSE, FALSE); + Inkscape::Extension::save( + Inkscape::Extension::db.get(helper_extension.c_str()), + doc, tempfilename_in.c_str(), false, false, false, + Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY); } - gsize bytesRead = 0; - gsize bytesWritten = 0; - GError *error = NULL; - gchar *local_filename = g_filename_from_utf8( filename, - -1, &bytesRead, &bytesWritten, &error); - execute(command, tempfilename_in, local_filename); + file_listener fileout; + int data_read = execute(command, params, tempfilename_in, fileout); + + bool success = false; - g_free(local_filename); + if (data_read > 0) { + std::string lfilename = Glib::filename_from_utf8(filenameArg); + success = fileout.toFile(lfilename); + } // make sure we don't leak file descriptors from g_file_open_tmp - close(tempfd); + close(tempfd_in); // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name - unlink(tempfilename_in); - g_free(tempfilename_in); + unlink(tempfilename_in.c_str()); + + if (success == false) { + throw Inkscape::Extension::Output::save_failed(); + } + + return; } + + /** \return none \brief This function uses an extention as a effect on a document. @@ -536,7 +634,7 @@ Script::save(Inkscape::Extension::Output *module, SPDocument *doc, gchar const * This function is a little bit trickier than the previous two. It needs two temporary files to get it's work done. Both of these files have random names created for them using the g_file_open_temp function - with the sp_ext_ prefix in the temporary directory. Like the other + with the ink_ext_ prefix in the temporary directory. Like the other functions, the temporary files are deleted at the end. To save/load the two temporary documents (both are SVG) the internal @@ -555,98 +653,91 @@ Script::save(Inkscape::Extension::Output *module, SPDocument *doc, gchar const * exists at the time, the other is created by that script). At that point both should be full, and the second one is loaded. */ -void -Script::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *doc) +void Script::effect(Inkscape::Extension::Effect *module, + Inkscape::UI::View::View *doc, + ImplementationDocumentCache * docCache) { - int data_read = 0; - SPDocument * mydoc = NULL; - gint tempfd_in; - gchar *tempfilename_in; - - // FIXME: process the GError instead of passing NULL - if ((tempfd_in = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_in, NULL)) == -1) { - /* Error, couldn't create temporary filename */ - if (errno == EINVAL) { - /* The last six characters of template were not XXXXXX. Now template is unchanged. */ - perror("Extension::Script: template for filenames is misconfigured.\n"); - exit(-1); - } else if (errno == EEXIST) { - /* Now the contents of template are undefined. */ - perror("Extension::Script: Could not create a unique temporary filename\n"); - return; - } else { - perror("Extension::Script: Unknown error creating temporary filename\n"); - exit(-1); - } + if (docCache == NULL) { + docCache = newDocCache(module, doc); } - - gint tempfd_out; - gchar *tempfilename_out; - // FIXME: process the GError instead of passing NULL - if ((tempfd_out = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_out, NULL)) == -1) { - /* Error, couldn't create temporary filename */ - if (errno == EINVAL) { - /* The last six characters of template were not XXXXXX. Now template is unchanged. */ - perror("Extension::Script: template for filenames is misconfigured.\n"); - exit(-1); - } else if (errno == EEXIST) { - /* Now the contents of template are undefined. */ - perror("Extension::Script: Could not create a unique temporary filename\n"); - return; - } else { - perror("Extension::Script: Unknown error creating temporary filename\n"); - exit(-1); - } + ScriptDocCache * dc = dynamic_cast(docCache); + if (dc == NULL) { + printf("TOO BAD TO LIVE!!!"); + exit(1); } - Inkscape::Extension::save(Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE), - doc->doc(), tempfilename_in, FALSE, FALSE, FALSE); + SPDesktop *desktop = (SPDesktop *)doc; + sp_namedview_document_from_window(desktop); + + std::list params; + module->paramListString(params); + + if (module->no_doc) { + // this is a no-doc extension, e.g. a Help menu command; + // just run the command without any files, ignoring errors + + Glib::ustring empty; + file_listener outfile; + execute(command, params, empty, outfile); + + return; + } - Glib::ustring local_command(command); + std::string tempfilename_out; + int tempfd_out = 0; + try { + tempfd_out = Inkscape::IO::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg"); + } catch (...) { + /// \todo Popup dialog here + return; + } - /* fixme: Should be some sort of checking here. Don't know how to do this with structs instead - * of classes. */ - SPDesktop *desktop = (SPDesktop *) doc; if (desktop != NULL) { - using Inkscape::Util::GSListConstIterator; - GSListConstIterator selected = sp_desktop_selection(desktop)->itemList(); + Inkscape::Util::GSListConstIterator selected = + sp_desktop_selection(desktop)->itemList(); while ( selected != NULL ) { - local_command += " --id="; - local_command += SP_OBJECT_ID(*selected); + Glib::ustring selected_id; + selected_id += "--id="; + selected_id += (*selected)->getId(); + params.insert(params.begin(), selected_id); ++selected; } } - Glib::ustring * paramString = module->paramString(); - local_command += *paramString; - delete paramString; + file_listener fileout; + int data_read = execute(command, params, dc->_filename, fileout); + fileout.toFile(tempfilename_out); - // std::cout << local_command << std::endl; + pump_events(); - data_read = execute(local_command.c_str(), tempfilename_in, tempfilename_out); + SPDocument * mydoc = NULL; + if (data_read > 10) { + mydoc = Inkscape::Extension::open( + Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), + tempfilename_out.c_str()); + } // data_read - if (data_read > 10) - mydoc = Inkscape::Extension::open(Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), tempfilename_out); + pump_events(); // make sure we don't leak file descriptors from g_file_open_tmp - close(tempfd_in); close(tempfd_out); - // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name - unlink(tempfilename_in); - g_free(tempfilename_in); - unlink(tempfilename_out); - g_free(tempfilename_out); + + g_unlink(tempfilename_out.c_str()); /* Do something with mydoc.... */ - if (mydoc != NULL) { + if (mydoc) { doc->doc()->emitReconstructionStart(); copy_doc(doc->doc()->rroot, mydoc->rroot); doc->doc()->emitReconstructionFinish(); mydoc->release(); + sp_namedview_update_layers_from_document(desktop); } + + return; } + /** \brief A function to take all the svg elements from one document and put them in another. @@ -662,218 +753,83 @@ Script::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *do elements and putting them into the old document. The copy is then complete. */ -void -Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot) +void Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot) { std::vector delete_list; + Inkscape::XML::Node * oldroot_namedview = NULL; + for (Inkscape::XML::Node * child = oldroot->firstChild(); child != NULL; child = child->next()) { - if (!strcmp("sodipodi:namedview", child->name())) - continue; - if (!strcmp("svg:defs", child->name())) - continue; - delete_list.push_back(child); + if (!strcmp("sodipodi:namedview", child->name())) { + oldroot_namedview = child; + for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild(); + oldroot_namedview_child != NULL; + oldroot_namedview_child = oldroot_namedview_child->next()) { + delete_list.push_back(oldroot_namedview_child); + } + } else { + delete_list.push_back(child); + } } - for (unsigned int i = 0; i < delete_list.size(); i++) + for (unsigned int i = 0; i < delete_list.size(); i++) { sp_repr_unparent(delete_list[i]); + } for (Inkscape::XML::Node * child = newroot->firstChild(); child != NULL; child = child->next()) { - if (!strcmp("sodipodi:namedview", child->name())) - continue; - if (!strcmp("svg:defs", child->name())) - continue; - oldroot->appendChild(child->duplicate()); - } - - /** \todo Restore correct layer */ - /** \todo Restore correct selection */ -} - -/* Helper class used by Script::execute */ -class pipe_t { -public: - /* These functions set errno if they return false. - I'm not sure whether that's a good idea or not, but it should be reasonably - straightforward to change it if needed. */ - bool open(char *command, char const *errorFile, int mode); - bool close(); - - /* These return the number of bytes read/written. */ - size_t read(void *buffer, size_t size); - size_t write(void const *buffer, size_t size); - - enum { - mode_read = 1 << 0, - mode_write = 1 << 1, - }; - -private: -#ifdef WIN32 - /* This is used to translate win32 errors into errno errors. - It only recognizes a few win32 errors for the moment though. */ - static int translate_error(DWORD err); - - HANDLE hpipe; -#else - FILE *ppipe; -#endif -}; - -/** - \return none - \brief This is the core of the extension file as it actually does - the execution of the extension. - \param in_command The command to be executed - \param filein Filename coming in - \param fileout Filename of the out file - \return Number of bytes that were read into the output file. - - The first thing that this function does is build the command to be - executed. This consists of the first string (in_command) and then - the filename for input (filein). This file is put on the command - line. - - The next thing is that this function does is open a pipe to the - command and get the file handle in the ppipe variable. It then - opens the output file with the output file handle. Both of these - operations are checked extensively for errors. - - After both are opened, then the data is copied from the output - of the pipe into the file out using fread and fwrite. These two - functions are used because of their primitive nature they make - no assumptions about the data. A buffer is used in the transfer, - but the output of fread is stored so the exact number of bytes - is handled gracefully. - - At the very end (after the data has been copied) both of the files - are closed, and we return to what we were doing. -*/ -int -Script::execute (const gchar * in_command, const gchar * filein, const gchar * fileout) -{ - g_return_val_if_fail(in_command != NULL, 0); - // printf("Executing: %s\n", in_command); - - gchar * errorFile; - gint errorFileNum; - errorFileNum = g_file_open_tmp("ink_ext_stderr_XXXXXX", &errorFile, NULL); - if (errorFileNum != 0) { - close(errorFileNum); - } else { - g_free(errorFile); - errorFile = NULL; - } - - char *command = g_strdup_printf("%s \"%s\"", in_command, filein); - // std::cout << "Command to run: " << command << std::endl; - - pipe_t pipe; - bool open_success = pipe.open(command, errorFile, pipe_t::mode_read); - g_free(command); - - /* Run script */ - if (!open_success) { - /* Error - could not open pipe - check errno */ - if (errno == EINVAL) { - perror("Extension::Script: Invalid mode argument in popen\n"); - } else if (errno == ECHILD) { - perror("Extension::Script: Cannot obtain child extension status in popen\n"); + if (!strcmp("sodipodi:namedview", child->name())) { + if (oldroot_namedview != NULL) { + for (Inkscape::XML::Node * newroot_namedview_child = child->firstChild(); + newroot_namedview_child != NULL; + newroot_namedview_child = newroot_namedview_child->next()) { + oldroot_namedview->appendChild(newroot_namedview_child->duplicate(oldroot->document())); + } + } } else { - perror("Extension::Script: Unknown error for popen\n"); + oldroot->appendChild(child->duplicate(oldroot->document())); } - return 0; } - Inkscape::IO::dump_fopen_call(fileout, "J"); - FILE *pfile = Inkscape::IO::fopen_utf8name(fileout, "w"); + { + using Inkscape::Util::List; + using Inkscape::XML::AttributeRecord; + std::vector attribs; - if (pfile == NULL) { - /* Error - could not open file */ - if (errno == EINVAL) { - perror("Extension::Script: The mode provided to fopen was invalid\n"); - } else { - perror("Extension::Script: Unknown error attempting to open temporary file\n"); + // Make a list of all attributes of the old root node. + for (List iter = oldroot->attributeList(); iter; ++iter) { + attribs.push_back(g_quark_to_string(iter->key)); } - return 0; - } - - /* Copy pipe output to a temporary file */ - int amount_read = 0; - char buf[BUFSIZE]; - int num_read; - while ((num_read = pipe.read(buf, BUFSIZE)) != 0) { - amount_read += num_read; - fwrite(buf, 1, num_read, pfile); - } - /* Close file */ - if (fclose(pfile) == EOF) { - if (errno == EBADF) { - perror("Extension::Script: The filedescriptor for the temporary file is invalid\n"); - return 0; - } else { - perror("Extension::Script: Unknown error closing temporary file\n"); + // Delete the attributes of the old root nodes. + for (std::vector::const_iterator it = attribs.begin(); it != attribs.end(); it++) { + oldroot->setAttribute(*it, NULL); } - } - /* Close pipe */ - if (!pipe.close()) { - if (errno == EINVAL) { - perror("Extension::Script: Invalid mode set for pclose\n"); - } else if (errno == ECHILD) { - perror("Extension::Script: Could not obtain child status for pclose\n"); - } else { - if (errorFile != NULL) { - checkStderr(errorFile, Gtk::MESSAGE_ERROR, - _("Inkscape has received an error from the script that it called. " - "The text returned with the error is included below. " - "Inkscape will continue working, but the action you requested has been cancelled.")); - } else { - perror("Extension::Script: Unknown error for pclose\n"); - } - } - /* Could be a lie, but if there is an error, we don't want - * to count on what was read being good */ - amount_read = 0; - } else { - if (errorFile != NULL) { - checkStderr(errorFile, Gtk::MESSAGE_INFO, - _("Inkscape has received additional data from the script executed. " - "The script did not return an error, but this may indicate the results will not be as expected.")); + // Set the new attributes. + for (List iter = newroot->attributeList(); iter; ++iter) { + gchar const *name = g_quark_to_string(iter->key); + oldroot->setAttribute(name, newroot->attribute(name)); } } - if (errorFile != NULL) { - unlink(errorFile); - g_free(errorFile); - } - - return amount_read; + /** \todo Restore correct layer */ + /** \todo Restore correct selection */ } /** \brief This function checks the stderr file, and if it has data, shows it in a warning dialog to the user \param filename Filename of the stderr file */ -void -Script::checkStderr (gchar * filename, Gtk::MessageType type, gchar * message) +void Script::checkStderr (const Glib::ustring &data, + Gtk::MessageType type, + const Glib::ustring &message) { - // magic win32 crlf->lf conversion means the file length is not the same as - // the text length, but luckily gtk will accept crlf in textviews so we can - // just use binary mode - std::ifstream stderrf (filename, std::ios_base::in | std::ios_base::binary); - if (!stderrf.is_open()) return; - - stderrf.seekg(0, std::ios::end); - int length = stderrf.tellg(); - if (0 == length) return; - stderrf.seekg(0, std::ios::beg); - Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true); warning.set_resizable(true); + GtkWidget *dlg = GTK_WIDGET(warning.gobj()); + sp_transientize(dlg); Gtk::VBox * vbox = warning.get_vbox(); @@ -883,11 +839,13 @@ Script::checkStderr (gchar * filename, Gtk::MessageType type, gchar * message) textview->set_wrap_mode(Gtk::WRAP_WORD); textview->show(); - char * buffer = new char [length]; - stderrf.read(buffer, length); - textview->get_buffer()->set_text(buffer, buffer + length); - delete buffer; - stderrf.close(); + // Remove the last character + char *errormsg = (char*) data.c_str(); + while (*errormsg != '\0') errormsg++; + errormsg -= 1; + *errormsg = '\0'; + + textview->get_buffer()->set_text(_(data.c_str())); Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow(); scrollwindow->add(*textview); @@ -902,144 +860,151 @@ Script::checkStderr (gchar * filename, Gtk::MessageType type, gchar * message) return; } -#ifdef WIN32 - -bool pipe_t::open(char *command, char const *errorFile, int mode_p) { - HANDLE pipe_write; - - // Create pipe - { - SECURITY_ATTRIBUTES secattrs; - ZeroMemory(&secattrs, sizeof(secattrs)); - secattrs.nLength = sizeof(secattrs); - secattrs.lpSecurityDescriptor = 0; - secattrs.bInheritHandle = TRUE; - HANDLE t_pipe_read = 0; - if ( !CreatePipe(&t_pipe_read, &pipe_write, &secattrs, 0) ) { - errno = translate_error(GetLastError()); - return false; - } - // This duplicate handle makes the read pipe uninheritable - if ( !DuplicateHandle(GetCurrentProcess(), t_pipe_read, GetCurrentProcess(), &hpipe, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS) ) { - int en = translate_error(GetLastError()); - CloseHandle(t_pipe_read); - CloseHandle(pipe_write); - errno = en; - return false; - } - } - // Open stderr file - HANDLE hStdErrFile = CreateFile(errorFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL); - HANDLE hInheritableStdErr; - DuplicateHandle(GetCurrentProcess(), hStdErrFile, GetCurrentProcess(), &hInheritableStdErr, 0, TRUE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); - - // Create process - { - PROCESS_INFORMATION procinfo; - STARTUPINFO startupinfo; - ZeroMemory(&procinfo, sizeof(procinfo)); - ZeroMemory(&startupinfo, sizeof(startupinfo)); - startupinfo.cb = sizeof(startupinfo); - //startupinfo.lpReserved = 0; - //startupinfo.lpDesktop = 0; - //startupinfo.lpTitle = 0; - startupinfo.dwFlags = STARTF_USESTDHANDLES; - startupinfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); - startupinfo.hStdOutput = pipe_write; - startupinfo.hStdError = hInheritableStdErr; - - if ( !CreateProcess(NULL, command, NULL, NULL, TRUE, 0, NULL, NULL, &startupinfo, &procinfo) ) { - errno = translate_error(GetLastError()); - return false; - } - CloseHandle(procinfo.hThread); - CloseHandle(procinfo.hProcess); - } - - // Close our copy of the write handle - CloseHandle(hInheritableStdErr); - CloseHandle(pipe_write); +bool Script::cancelProcessing (void) { + _canceled = true; + _main_loop->quit(); + Glib::spawn_close_pid(_pid); return true; } -bool pipe_t::close() { - BOOL retval = CloseHandle(hpipe); - if ( !retval ) { - errno = translate_error(GetLastError()); - } - return retval != FALSE; -} -size_t pipe_t::read(void *buffer, size_t size) { - DWORD bytes_read = 0; - ReadFile(hpipe, buffer, size, &bytes_read, 0); - return bytes_read; -} +/** \brief This is the core of the extension file as it actually does + the execution of the extension. + \param in_command The command to be executed + \param filein Filename coming in + \param fileout Filename of the out file + \return Number of bytes that were read into the output file. -size_t pipe_t::write(void const *buffer, size_t size) { - DWORD bytes_written = 0; - WriteFile(hpipe, buffer, size, &bytes_written, 0); - return bytes_written; -} + The first thing that this function does is build the command to be + executed. This consists of the first string (in_command) and then + the filename for input (filein). This file is put on the command + line. + + The next thing is that this function does is open a pipe to the + command and get the file handle in the ppipe variable. It then + opens the output file with the output file handle. Both of these + operations are checked extensively for errors. + + After both are opened, then the data is copied from the output + of the pipe into the file out using fread and fwrite. These two + functions are used because of their primitive nature they make + no assumptions about the data. A buffer is used in the transfer, + but the output of fread is stored so the exact number of bytes + is handled gracefully. -int pipe_t::translate_error(DWORD err) { - switch (err) { - case ERROR_FILE_NOT_FOUND: - return ENOENT; - case ERROR_INVALID_HANDLE: - case ERROR_INVALID_PARAMETER: - return EINVAL; - default: - return 0; + At the very end (after the data has been copied) both of the files + are closed, and we return to what we were doing. +*/ +int Script::execute (const std::list &in_command, + const std::list &in_params, + const Glib::ustring &filein, + file_listener &fileout) +{ + g_return_val_if_fail(!in_command.empty(), 0); + // printf("Executing\n"); + + std::vector argv; + + bool interpreted = (in_command.size() == 2); + std::string program = in_command.front(); + std::string script = interpreted ? in_command.back() : ""; + std::string working_directory = ""; + + // Use Glib::find_program_in_path instead of the equivalent + // Glib::spawn_* functionality, because _wspawnp is broken on Windows: + // it doesn't work when PATH contains Unicode directories + if (!Glib::path_is_absolute(program)) { + program = Glib::find_program_in_path(program); + } + argv.push_back(program); + + if (interpreted) { + // On Windows, Python garbles Unicode command line parameters + // in an useless way. This means extensions fail when Inkscape + // is run from an Unicode directory. + // As a workaround, we set the working directory to the one + // containing the script. + working_directory = Glib::path_get_dirname(script); + script = Glib::path_get_basename(script); + #ifdef G_OS_WIN32 + // ANNOYING: glibmm does not wrap g_win32_locale_filename_from_utf8 + gchar *workdir_s = g_win32_locale_filename_from_utf8(working_directory.data()); + working_directory = workdir_s; + g_free(workdir_s); + #endif + + argv.push_back(script); + } + + // assemble the rest of argv + std::copy(in_params.begin(), in_params.end(), std::back_inserter(argv)); + if (!filein.empty()) { + argv.push_back(filein); + } + + int stdout_pipe, stderr_pipe; + + try { + Glib::spawn_async_with_pipes(working_directory, // working directory + argv, // arg v + static_cast(0), // no flags + sigc::slot(), + &_pid, // Pid + NULL, // STDIN + &stdout_pipe, // STDOUT + &stderr_pipe); // STDERR + } catch (Glib::Error e) { + printf("Can't Spawn!!! spawn returns: %s\n", e.what().data()); + return 0; } -} -#else // Win32 + _main_loop = Glib::MainLoop::create(false); -bool pipe_t::open(char *command, char const *errorFile, int mode_p) { - char popen_mode[4] = {0,0,0,0}; - char *popen_mode_cur = popen_mode; + file_listener fileerr; + fileout.init(stdout_pipe, _main_loop); + fileerr.init(stderr_pipe, _main_loop); - if ( (mode_p & mode_read) != 0 ) { - *popen_mode_cur++ = 'r'; - } + _canceled = false; + _main_loop->run(); - if ( (mode_p & mode_write) != 0 ) { - *popen_mode_cur++ = 'w'; + // Ensure all the data is out of the pipe + while (!fileout.isDead()) { + fileout.read(Glib::IO_IN); + } + while (!fileerr.isDead()) { + fileerr.read(Glib::IO_IN); } - /* Get the commandline to be run */ - if (errorFile != NULL) { - char * temp; - temp = g_strdup_printf("%s 2> %s", command, errorFile); - ppipe = popen(temp, popen_mode); - g_free(temp); - } else - ppipe = popen(command, popen_mode); - - return ppipe != NULL; -} + if (_canceled) { + // std::cout << "Script Canceled" << std::endl; + return 0; + } -bool pipe_t::close() { - return fclose(ppipe) == 0; -} + Glib::ustring stderr_data = fileerr.string(); + if (stderr_data.length() != 0 && + inkscape_use_gui() + ) { + checkStderr(stderr_data, Gtk::MESSAGE_INFO, + _("Inkscape has received additional data from the script executed. " + "The script did not return an error, but this may indicate the results will not be as expected.")); + } -size_t pipe_t::read(void *buffer, size_t size) { - return fread(buffer, 1, size, ppipe); -} + Glib::ustring stdout_data = fileout.string(); + if (stdout_data.length() == 0) { + return 0; + } -size_t pipe_t::write(void const *buffer, size_t size) { - return fwrite(buffer, 1, size, ppipe); + // std::cout << "Finishing Execution." << std::endl; + return stdout_data.length(); } -#endif // (Non-)Win32 -} /* Inkscape */ -} /* module */ -} /* Implementation */ +} // namespace Implementation +} // namespace Extension +} // namespace Inkscape /* Local Variables: @@ -1050,4 +1015,4 @@ size_t pipe_t::write(void const *buffer, size_t size) { fill-column:99 End: */ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :