From 77335d89837b66c81d618cee9c44cc5563a22f64 Mon Sep 17 00:00:00 2001 From: Konstantin Dmitriev Date: Dec 14 2019 10:34:02 +0000 Subject: Fix for issue #949: File not saved properly after executing a plugin --- diff --git a/synfig-core/src/synfig/filesystemtemporary.cpp b/synfig-core/src/synfig/filesystemtemporary.cpp index 05ab87c..9c17c68 100644 --- a/synfig-core/src/synfig/filesystemtemporary.cpp +++ b/synfig-core/src/synfig/filesystemtemporary.cpp @@ -67,7 +67,7 @@ FileSystemTemporary::FileSystemTemporary(const String &tag, const String &tempor FileSystemTemporary::~FileSystemTemporary() { - discard_changes(); + } String diff --git a/synfig-studio/src/gui/CMakeLists.txt b/synfig-studio/src/gui/CMakeLists.txt index 0ba29ba..220ddbc 100644 --- a/synfig-studio/src/gui/CMakeLists.txt +++ b/synfig-studio/src/gui/CMakeLists.txt @@ -43,6 +43,7 @@ target_sources(synfigstudio "${CMAKE_CURRENT_LIST_DIR}/ipc.cpp" "${CMAKE_CURRENT_LIST_DIR}/keymapsettings.cpp" "${CMAKE_CURRENT_LIST_DIR}/onemoment.cpp" + "${CMAKE_CURRENT_LIST_DIR}/pluginmanager.cpp" "${CMAKE_CURRENT_LIST_DIR}/preview.cpp" "${CMAKE_CURRENT_LIST_DIR}/renddesc.cpp" "${CMAKE_CURRENT_LIST_DIR}/render.cpp" diff --git a/synfig-studio/src/gui/Makefile.am b/synfig-studio/src/gui/Makefile.am index 989fd71..61ee7b9 100644 --- a/synfig-studio/src/gui/Makefile.am +++ b/synfig-studio/src/gui/Makefile.am @@ -52,6 +52,7 @@ OTHER_HH = \ ipc.h \ keymapsettings.h \ onemoment.h \ + pluginmanager.h \ preview.h \ renddesc.h \ render.h \ @@ -83,6 +84,7 @@ OTHER_CC = \ ipc.cpp \ keymapsettings.cpp \ onemoment.cpp \ + pluginmanager.cpp \ preview.cpp \ renddesc.cpp \ render.cpp \ diff --git a/synfig-studio/src/gui/app.cpp b/synfig-studio/src/gui/app.cpp index c90d134..78054eb 100644 --- a/synfig-studio/src/gui/app.cpp +++ b/synfig-studio/src/gui/app.cpp @@ -313,7 +313,7 @@ int studio::App::preferred_y_size = 270; String studio::App::predefined_size (DEFAULT_PREDEFINED_SIZE); String studio::App::predefined_fps (DEFAULT_PREDEFINED_FPS); float studio::App::preferred_fps = 24.0; -synfigapp::PluginManager studio::App::plugin_manager; +PluginManager studio::App::plugin_manager; std::set< String > studio::App::brushes_path; String studio::App::image_editor_path; @@ -1203,12 +1203,12 @@ DEFINE_ACTION("keyframe-properties", _("Properties")); " " ; - list plugin_list = studio::App::plugin_manager.get_list(); - for(list::const_iterator p=plugin_list.begin();p!=plugin_list.end();++p) { + list plugin_list = studio::App::plugin_manager.get_list(); + for(list::const_iterator p=plugin_list.begin();p!=plugin_list.end();++p) { // TODO: (Plugins) Arrange menu items into groups - synfigapp::PluginManager::plugin plugin = *p; + PluginManager::plugin plugin = *p; DEFINE_ACTION(plugin.id, plugin.name); ui_info_menu += strprintf(" ", plugin.id.c_str()); @@ -3746,23 +3746,17 @@ App::wrap_into_temporary_filesystem( } bool -App::open(std::string filename) -{ - return open_as(filename,filename); -} - -bool -App::open_as(std::string filename,std::string as,synfig::FileContainerZip::file_size_t truncate_storage_size) +App::open(std::string filename, /* std::string as, */ synfig::FileContainerZip::file_size_t truncate_storage_size) { #ifdef _WIN32 size_t buf_size = PATH_MAX - 1; char* long_name = (char*)malloc(buf_size); long_name[0] = '\0'; - if(GetLongPathName(as.c_str(),long_name,sizeof(long_name))); + if(GetLongPathName(filename.c_str(),long_name,sizeof(long_name))); // when called from autorecover.cpp, filename doesn't exist, and so long_name is empty // don't use it if that's the case if (long_name[0] != '\0') - as=String(long_name); + filename=String(long_name); free(long_name); #endif @@ -3780,12 +3774,12 @@ App::open_as(std::string filename,std::string as,synfig::FileContainerZip::file_ FileSystem::Handle canvas_file_system = CanvasFileNaming::make_filesystem(container); // wrap into temporary file system - canvas_file_system = wrap_into_temporary_filesystem(canvas_file_system, filename, as, truncate_storage_size); + canvas_file_system = wrap_into_temporary_filesystem(canvas_file_system, filename, filename, truncate_storage_size); // file to open inside canvas file-system String canvas_filename = CanvasFileNaming::project_file(filename); - etl::handle canvas = open_canvas_as(canvas_file_system ->get_identifier(canvas_filename), as, errors, warnings); + etl::handle canvas = open_canvas_as(canvas_file_system ->get_identifier(canvas_filename), filename, errors, warnings); if(canvas && get_instance(canvas)) { get_instance(canvas)->find_canvas_view(canvas)->present(); @@ -3805,8 +3799,8 @@ App::open_as(std::string filename,std::string as,synfig::FileContainerZip::file_ _("Close"), warnings); - if (as.find(custom_filename_prefix.c_str()) != 0) - add_recent_file(as); + if (filename.find(custom_filename_prefix.c_str()) != 0) + add_recent_file(filename); handle instance(Instance::create(canvas, container)); @@ -4122,7 +4116,7 @@ App::dialog_open(string filename) truncate_storage_size = i->storage_size; } - if(open_as(filename,filename,truncate_storage_size)) + if(open(filename,truncate_storage_size)) break; get_ui_interface()->error(_("Unable to open file")); @@ -4248,3 +4242,26 @@ studio::App::process_all_events(long unsigned int us) Glib::usleep(us); } } + +bool +studio::App::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; +} \ No newline at end of file diff --git a/synfig-studio/src/gui/app.h b/synfig-studio/src/gui/app.h index 44fc661..95d533a 100644 --- a/synfig-studio/src/gui/app.h +++ b/synfig-studio/src/gui/app.h @@ -47,7 +47,7 @@ #include #include #include -#include +#include "pluginmanager.h" #include "iconcontroller.h" #include "mainwindow.h" @@ -218,7 +218,7 @@ public: static bool use_dark_theme; static bool show_file_toolbar; - static synfigapp::PluginManager plugin_manager; + static PluginManager plugin_manager; static synfig::String image_editor_path; static std::set< synfig::String > brushes_path; static synfig::String custom_filename_prefix; @@ -366,11 +366,9 @@ public: std::string as, synfig::FileContainerZip::file_size_t truncate_storage_size = 0 ); - static bool open(std::string filename); - - static bool open_as( + static bool open( std::string filename, - std::string as, + /* std::string as, */ synfig::FileContainerZip::file_size_t truncate_storage_size = 0 ); static bool open_from_temporary_filesystem(std::string temporary_filename); @@ -459,6 +457,7 @@ public: static void setup_changed(); static void process_all_events(long unsigned int us = 1); + static bool check_python_version( std::string path); }; // END of class App void delete_widget(Gtk::Widget *widget); diff --git a/synfig-studio/src/gui/instance.cpp b/synfig-studio/src/gui/instance.cpp index 705ab0d..556e9dc 100644 --- a/synfig-studio/src/gui/instance.cpp +++ b/synfig-studio/src/gui/instance.cpp @@ -43,6 +43,7 @@ #include #include +#include #include #include @@ -61,6 +62,7 @@ #include #include #include +#include #include "instance.h" #include "canvasview.h" @@ -226,10 +228,10 @@ studio::Instance::run_plugin(std::string plugin_path) { handle uim = this->find_canvas_view(this->get_canvas())->get_ui_interface(); - string message = strprintf(_("Do you really want to run plugin for file \"%s\"?" ), + String message = strprintf(_("Do you really want to run plugin for file \"%s\"?" ), this->get_canvas()->get_name().c_str()); - string details = strprintf(_("This operation cannot be undone and all undo history will be cleared.")); + String details = strprintf(_("This operation cannot be undone and all undo history will be cleared.")); int answer = uim->confirmation( message, @@ -238,69 +240,178 @@ studio::Instance::run_plugin(std::string plugin_path) _("Proceed"), synfigapp::UIInterface::RESPONSE_OK); - if(answer == synfigapp::UIInterface::RESPONSE_OK){ + if(answer != synfigapp::UIInterface::RESPONSE_OK) + return; - OneMoment one_moment; + OneMoment one_moment; + + Canvas::Handle canvas(this->get_canvas()); - Canvas::Handle canvas(this->get_canvas()); + backup(true); + FileSystemTemporary::Handle temporary_filesystem = FileSystemTemporary::Handle::cast_dynamic(get_canvas()->get_file_system()); + String tmp_filename = temporary_filesystem->get_temporary_directory() + ETL_DIRECTORY_SEPARATOR + temporary_filesystem->get_temporary_filename_base(); - synfigapp::PluginLauncher launcher(canvas); + Time cur_time; + cur_time = canvas->get_time(); - Time cur_time; - cur_time = canvas->get_time(); + // Generate temporary file name + String filename_original = get_canvas()->get_file_name(); + String filename_processed; + struct stat buf; + do { + synfig::GUID guid; + filename_processed = filename_original+"."+guid.get_string().substr(0,8)+".sif"; + } while (stat(filename_processed.c_str(), &buf) != -1); + + close(false); + + if(canvas->count()!=1) + { + one_moment.hide(); + App::dialog_message_1b( + "ERROR", + _("The plugin operation has failed."), + _("This can be due to current file " + "being referenced by another composition that is already open, " + "or because of an internal error in Synfig Studio. Try closing " + "any compositions that might reference this file and try again, " + "or restart Synfig Studio."), + _("Close")); - this->close(); + } else { - if(canvas->count()!=1) + // Save file copy + String filename_ext = filename_extension(filename_original); + if (filename_ext.empty()) + filename_ext = ".sifz"; + FileSystem::ReadStream::Handle stream_in = temporary_filesystem->get_read_stream("#project"+filename_ext); + if (!stream_in) { - one_moment.hide(); - App::dialog_message_1b( - "ERROR", - _("The plugin operation has failed."), - _("This can be due to current file " - "being referenced by another composition that is already open, " - "or because of an internal error in Synfig Studio. Try closing " - "any compositions that might reference this file and try again, " - "or restart Synfig Studio."), - _("Close")); + synfig::error(strprintf("run_plugin(): Unable to open file for reading - %s", temporary_filesystem->get_real_uri("#project"+filename_ext).c_str())); + } + if (filename_ext == ".sifz") + stream_in = new ZReadStream(stream_in); + std::ofstream outfile(filename_processed, std::ios::binary); + outfile << stream_in->rdbuf(); + outfile.close(); + stream_in.reset(); + + + bool result; + int exitcode; + String output; + String command = ""; + + // Path to python binary can be overridden + // with SYNFIG_PYTHON_BINARY env variable: + const char* custom_python_binary=getenv("SYNFIG_PYTHON_BINARY"); + if(custom_python_binary) { + command=custom_python_binary; + if (!App::check_python_version(command)) { + output=_("Error: You need to have Python 3 installed."); + command=""; + } + } 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 = "\"" + App::get_base_path()+ETL_DIRECTORY_SEPARATOR+"python"+ETL_DIRECTORY_SEPARATOR+*iter+".exe" + "\""; +#else + python_path = *iter; +#endif + if (App::check_python_version(python_path)) + { + command = python_path; + break; + } - one_moment.show(); + } + } + if (command.empty()) + { + output=_("Error: No Python 3 binary found.\n\nHint: You can set SYNFIG_PYTHON_BINARY environment variable pointing at your custom python installation."); } else { - bool result; - result = launcher.execute( plugin_path, App::get_base_path() ); - if (!result){ - one_moment.hide(); - App::dialog_message_1b( - "Error", - launcher.get_output(), - "details", - _("Close")); + synfig::info("Python 3 binary found: "+command); + + + // Construct the full command: + command = command+" \""+plugin_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 - one_moment.show(); + FILE* pipe = popen(command.c_str(), "r"); + if (!pipe) { + output = "ERROR: pipe failed!"; + } else { + 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){ + result=true; + } } } + if (!result){ + one_moment.hide(); + App::dialog_message_1b( + "Error", + output, + "details", + _("Close")); - canvas=0; + one_moment.show(); - App::open_as(launcher.get_result_path(),launcher.get_original_path()); + } else { + // Restore file copy + FileSystem::WriteStream::Handle stream = temporary_filesystem->get_write_stream("#project"+filename_ext); + if (!stream) + { + synfig::error("run_plugin(): Unable to open file for write"); + } + if (filename_ext == ".sifz") + stream = FileSystem::WriteStream::Handle(new ZWriteStream(stream)); + std::ifstream infile(filename_processed, std::ios::binary); + *stream << infile.rdbuf(); + infile.close(); + stream.reset(); + remove(filename_processed.c_str()); + } + } + canvas=0; - etl::handle new_instance = App::instance_list.back(); - new_instance->inc_action_count(); // This file isn't saved! mark it as such + App::open_from_temporary_filesystem(tmp_filename); + etl::handle new_instance = App::instance_list.back(); - // Restore time cursor position - canvas = App::instance_list.back()->get_canvas(); - etl::handle new_canvas_interface(new_instance->find_canvas_interface(canvas)); - new_canvas_interface->set_time(cur_time); + // Restore time cursor position + canvas = new_instance->get_canvas(); + etl::handle new_canvas_interface(new_instance->find_canvas_interface(canvas)); + new_canvas_interface->set_time(cur_time); - } - return; } - bool studio::Instance::save_as(const synfig::String &file_name) { @@ -518,7 +629,7 @@ Instance::update_all_titles() } void -Instance::close() +Instance::close(bool remove_temporary_files) { // This will increase the reference count so we don't get DELETED // until we are ready @@ -577,6 +688,11 @@ Instance::close() { studio::App::instance_list.front()->canvas_view_list().front()->present(); } + + if (remove_temporary_files) { + FileSystemTemporary::Handle temporary_filesystem = FileSystemTemporary::Handle::cast_dynamic(get_canvas()->get_file_system()); + temporary_filesystem->discard_changes(); + } } void diff --git a/synfig-studio/src/gui/instance.h b/synfig-studio/src/gui/instance.h index 25b6e86..7ba79a7 100644 --- a/synfig-studio/src/gui/instance.h +++ b/synfig-studio/src/gui/instance.h @@ -204,7 +204,7 @@ public: void dialog_cvs_revert(); //! Closes the instance of this composition - void close(); + void close(bool remove_temporary_files = true); void revert(); diff --git a/synfig-studio/src/gui/mainwindow.cpp b/synfig-studio/src/gui/mainwindow.cpp index 5df031a..2edf9d8 100644 --- a/synfig-studio/src/gui/mainwindow.cpp +++ b/synfig-studio/src/gui/mainwindow.cpp @@ -391,7 +391,7 @@ MainWindow::on_recent_files_changed() menu_items += ""; action_group->add( Gtk::Action::create(action_name, quoted, fullnames[i]), - sigc::hide_return(sigc::bind(sigc::ptr_fun(&App::open),fullnames[i])) + sigc::hide_return(sigc::bind(sigc::ptr_fun(&App::open),fullnames[i],0)) ); } diff --git a/synfig-studio/src/gui/pluginmanager.cpp b/synfig-studio/src/gui/pluginmanager.cpp new file mode 100644 index 0000000..6f88c06 --- /dev/null +++ b/synfig-studio/src/gui/pluginmanager.cpp @@ -0,0 +1,226 @@ +/* === 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 + +#include + +#endif + +/* === U S I N G =========================================================== */ + +using namespace std; +using namespace etl; +using namespace synfig; +using namespace studio; + +/* === M A C R O S ========================================================= */ + + +/* === G L O B A L S ======================================================= */ + +/* === M E T H O D S ======================================================= */ + +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) { + 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(plugindir); + } + else + synfig::warning("Can't read plugin directory!"); + + /*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() +{ +} diff --git a/synfig-studio/src/gui/pluginmanager.h b/synfig-studio/src/gui/pluginmanager.h new file mode 100644 index 0000000..e75ee16 --- /dev/null +++ b/synfig-studio/src/gui/pluginmanager.h @@ -0,0 +1,92 @@ +/* === S Y N F I G ========================================================= */ +/*! \file synfigapp/pluginmanager.h +** \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 +*/ +/* ========================================================================= */ + +/* === S T A R T =========================================================== */ + +#ifndef __SYNFIG_GTKMM_PLUGINMANAGER_H +#define __SYNFIG_GTKMM_PLUGINMANAGER_H + +/* === H E A D E R S ======================================================= */ + +#include +#include +#include +#include + +/* === M A C R O S ========================================================= */ + +/* === T Y P E D E F S ===================================================== */ + +/* === C L A S S E S & S T R U C T S ======================================= */ + +namespace studio { + +class PluginManager +{ + /* + -- ** -- P U B L I C T Y P E S --------------------------------------------- + */ + +public: + struct plugin{ + std::string id; + std::string name; + std::string path; + }; + + /* + -- ** -- P U B L I C D A T A ------------------------------------------------ + */ + +public: + + /* + -- ** -- P R I V A T E D A T A --------------------------------------------- + */ + +private: + + std::list< plugin > list_; + +protected: + + /* + -- ** -- P U B L I C M E T H O D S ----------------------------------------- + */ + +public: + PluginManager(); + ~PluginManager(); + + void load_dir( const std::string &pluginsprefix ); + void load_plugin( const std::string &path ); + + std::list< plugin > get_list() { return list_; }; + +}; // END class PluginManager + + +}; // END namespace synfigapp + +/* === E N D =============================================================== */ + +#endif diff --git a/synfig-studio/src/synfigapp/CMakeLists.txt b/synfig-studio/src/synfigapp/CMakeLists.txt index b060cad..1b449db 100644 --- a/synfig-studio/src/synfigapp/CMakeLists.txt +++ b/synfig-studio/src/synfigapp/CMakeLists.txt @@ -14,7 +14,6 @@ target_sources(synfigapp "${CMAKE_CURRENT_LIST_DIR}/inputdevice.cpp" "${CMAKE_CURRENT_LIST_DIR}/instance.cpp" "${CMAKE_CURRENT_LIST_DIR}/main.cpp" - "${CMAKE_CURRENT_LIST_DIR}/pluginmanager.cpp" "${CMAKE_CURRENT_LIST_DIR}/settings.cpp" "${CMAKE_CURRENT_LIST_DIR}/timegather.cpp" "${CMAKE_CURRENT_LIST_DIR}/uimanager.cpp" diff --git a/synfig-studio/src/synfigapp/Makefile.am b/synfig-studio/src/synfigapp/Makefile.am index 3229bd8..35e6266 100644 --- a/synfig-studio/src/synfigapp/Makefile.am +++ b/synfig-studio/src/synfigapp/Makefile.am @@ -324,7 +324,6 @@ SYNFIGAPPHH = \ inputdevice.h \ instance.h \ main.h \ - pluginmanager.h \ settings.h \ uimanager.h \ value_desc.h \ @@ -343,7 +342,6 @@ SYNFIGAPPCC = \ inputdevice.cpp \ instance.cpp \ main.cpp \ - pluginmanager.cpp \ settings.cpp \ uimanager.cpp \ value_desc.cpp diff --git a/synfig-studio/src/synfigapp/instance.cpp b/synfig-studio/src/synfigapp/instance.cpp index ff50dc4..4456297 100644 --- a/synfig-studio/src/synfigapp/instance.cpp +++ b/synfig-studio/src/synfigapp/instance.cpp @@ -575,9 +575,9 @@ Instance::save_all_layers() } bool -Instance::backup() +Instance::backup(bool save_even_if_unchanged) { - if (!get_action_count()) + if (!get_action_count() && !save_even_if_unchanged) return true; FileSystemTemporary::Handle temporary_filesystem = FileSystemTemporary::Handle::cast_dynamic(get_canvas()->get_file_system()); @@ -638,6 +638,7 @@ Instance::save_as(const synfig::String &file_name) new_temporary_filesystem->set_meta("as", new_canvas_filename); new_temporary_filesystem->set_meta("truncate", "0"); new_canvas_filesystem = new_temporary_filesystem; + previous_temporary_filesystem->discard_changes(); } new_canvas_identifier = new_canvas_filesystem->get_identifier(CanvasFileNaming::project_file(new_canvas_filename)); diff --git a/synfig-studio/src/synfigapp/instance.h b/synfig-studio/src/synfigapp/instance.h index 011703c..a4f0a91 100644 --- a/synfig-studio/src/synfigapp/instance.h +++ b/synfig-studio/src/synfigapp/instance.h @@ -174,7 +174,7 @@ public: bool save_as(const synfig::String &filename); //! Saves the instance to current temporary container - bool backup(); + bool backup(bool save_even_if_unchanged = false); //! generate layer name (also known in code as 'description') synfig::String generate_new_description(const synfig::Layer::Handle &layer); diff --git a/synfig-studio/src/synfigapp/pluginmanager.cpp b/synfig-studio/src/synfigapp/pluginmanager.cpp deleted file mode 100644 index 9163d10..0000000 --- a/synfig-studio/src/synfigapp/pluginmanager.cpp +++ /dev/null @@ -1,389 +0,0 @@ -/* === 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 -#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)+".sif"; // without .sif suffix it won't be read back - } while (stat(filename_processed.c_str(), &buf) != -1); - - /* The plugin could die with nonzero exit code - * synfig could crash loading the modified file (should not happen) - * having a backup file should protect against both cases - */ - do { - synfig::GUID guid; - filename_backup = filename_base+"."+guid.get_string().substr(0,8)+".sif"; - } while (stat(filename_backup.c_str(), &buf) != -1); - - save_canvas(FileSystemNative::instance()->get_identifier(filename_processed),canvas); - // copy file would be faster .. - save_canvas(FileSystemNative::instance()->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 overridden - // 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) { - 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(plugindir); - } - else - synfig::warning("Can't read plugin directory!"); - - /*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() -{ -} diff --git a/synfig-studio/src/synfigapp/pluginmanager.h b/synfig-studio/src/synfigapp/pluginmanager.h deleted file mode 100644 index 84b634f..0000000 --- a/synfig-studio/src/synfigapp/pluginmanager.h +++ /dev/null @@ -1,125 +0,0 @@ -/* === S Y N F I G ========================================================= */ -/*! \file synfigapp/pluginmanager.h -** \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 -*/ -/* ========================================================================= */ - -/* === S T A R T =========================================================== */ - -#ifndef __SYNFIG_APP_PLUGINMANAGER_H -#define __SYNFIG_APP_PLUGINMANAGER_H - -/* === H E A D E R S ======================================================= */ - -#include -#include -#include -#include - -/* === M A C R O S ========================================================= */ - -/* === T Y P E D E F S ===================================================== */ - -/* === C L A S S E S & S T R U C T S ======================================= */ - -namespace synfigapp { - -class PluginLauncher -{ - - /* - -- ** -- P R I V A T E D A T A --------------------------------------------- - */ - -private: - - std::string filename_original; // location of original file - std::string filename_processed; // processed file - std::string filename_backup; // backup copy - std::string output; - int exitcode; - -protected: - - /* - -- ** -- P U B L I C M E T H O D S ----------------------------------------- - */ - -public: - PluginLauncher( synfig::Canvas::Handle ); - ~PluginLauncher(); - - bool execute( std::string script_path, const std::string& synfig_root ); - bool check_python_version( std::string path); - std::string get_result_path(); - std::string get_original_path() { return filename_original; }; - std::string get_output() { return output; }; - -}; // END class Plugin - -class PluginManager -{ - /* - -- ** -- P U B L I C T Y P E S --------------------------------------------- - */ - -public: - struct plugin{ - std::string id; - std::string name; - std::string path; - }; - - /* - -- ** -- P U B L I C D A T A ------------------------------------------------ - */ - -public: - - /* - -- ** -- P R I V A T E D A T A --------------------------------------------- - */ - -private: - - std::list< plugin > list_; - -protected: - - /* - -- ** -- P U B L I C M E T H O D S ----------------------------------------- - */ - -public: - PluginManager(); - ~PluginManager(); - - void load_dir( const std::string &pluginsprefix ); - void load_plugin( const std::string &path ); - - std::list< plugin > get_list() { return list_; }; - -}; // END class PluginManager - - -}; // END namespace synfigapp - -/* === E N D =============================================================== */ - -#endif