Fix warning: excess elements in struct initializer
[claws.git] / src / plugins / python / python_plugin.c
index fb015f9..03c2ea4 100644 (file)
 #include "claws-features.h"
 #endif
 
+#include <Python.h>
+
 #include <glib.h>
 #include <glib/gi18n.h>
 
-#include <Python.h>
-
 #include <errno.h>
 
 #include "common/hooks.h"
@@ -56,7 +56,7 @@ static GSList *python_compose_scripts_names = NULL;
 
 static GtkWidget *python_console = NULL;
 
-static guint hook_compose_create;
+static gulong hook_compose_create = 0;
 
 static gboolean python_console_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
 {
@@ -160,11 +160,12 @@ static void run_script_file(const gchar *filename, Compose *compose)
   FILE *fp;
   fp = fopen(filename, "r");
   if(!fp) {
-    g_print("Error: Could not open file '%s'\n", filename);
+    debug_print("Error: Could not open file '%s'\n", filename);
     return;
   }
   put_composewindow_into_module(compose);
-  PyRun_SimpleFile(fp, filename);
+  if(PyRun_SimpleFile(fp, filename) == 0)
+    debug_print("Problem running script file '%s'\n", filename);
   fclose(fp);
 }
 
@@ -297,9 +298,9 @@ static void migrate_scripts_out_of_base_dir(void)
       gchar *dest_file;
       dest_file = g_strconcat(dest_dir, G_DIR_SEPARATOR_S, filename, NULL);
       if(move_file(filepath, dest_file, FALSE) == 0)
-        g_print("Python plugin: Moved file '%s' to %s subdir\n", filename, PYTHON_SCRIPTS_MAIN_DIR);
+        debug_print("Python plugin: Moved file '%s' to %s subdir\n", filename, PYTHON_SCRIPTS_MAIN_DIR);
       else
-        g_print("Python plugin: Warning: Could not move file '%s' to %s subdir\n", filename, PYTHON_SCRIPTS_MAIN_DIR);
+        debug_print("Python plugin: Warning: Could not move file '%s' to %s subdir\n", filename, PYTHON_SCRIPTS_MAIN_DIR);
       g_free(dest_file);
     }
     g_free(filepath);
@@ -354,7 +355,7 @@ static void create_compose_menus_and_items(GSList *filenames)
 }
 
 static GtkActionEntry compose_tools_python_actions[] = {
-    {"Tools/PythonScripts", NULL, N_("Python scripts") },
+    {"Tools/PythonScripts", NULL, N_("Python scripts"), NULL, NULL, NULL },
 };
 
 static void ComposeActionData_destroy_cb(gpointer data)
@@ -429,7 +430,7 @@ static void refresh_scripts_in_dir(const gchar *subdir, ToolbarType toolbar_type
   g_free(scripts_dir);
 
   if(!dir) {
-    g_print("Could not open directory '%s': %s\n", subdir, error->message);
+    debug_print("Could not open directory '%s': %s\n", subdir, error->message);
     g_error_free(error);
     return;
   }
@@ -498,20 +499,24 @@ static GtkToggleActionEntry mainwindow_tools_python_toggle[] = {
 };
 
 static GtkActionEntry mainwindow_tools_python_actions[] = {
-    {"Tools/PythonScripts", NULL, N_("Python scripts") },
+    {"Tools/PythonScripts", NULL, N_("Python scripts"), NULL, NULL, NULL },
     {"Tools/PythonScripts/Refresh", NULL, N_("Refresh"),
         NULL, NULL, G_CALLBACK(refresh_python_scripts_menus) },
     {"Tools/PythonScripts/Browse", NULL, N_("Browse"),
         NULL, NULL, G_CALLBACK(browse_python_scripts_dir) },
-    {"Tools/PythonScripts/---", NULL, "---" },
+    {"Tools/PythonScripts/---", NULL, "---", NULL, NULL, NULL },
 };
 
-void python_menu_init(void)
+static int python_menu_init(char **error)
 {
   MainWindow *mainwin;
   guint id;
 
   mainwin =  mainwindow_get_mainwindow();
+  if(!mainwin) {
+    *error = g_strdup("Could not get main window");
+    return 0;
+  }
 
   gtk_action_group_add_toggle_actions(mainwin->action_group, mainwindow_tools_python_toggle, 1, mainwin);
   gtk_action_group_add_actions(mainwin->action_group, mainwindow_tools_python_actions, 3, mainwin);
@@ -537,9 +542,11 @@ void python_menu_init(void)
   menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));
 
   refresh_python_scripts_menus(NULL, NULL);
+
+  return !0;
 }
 
-void python_menu_done(void)
+static void python_menu_done(void)
 {
   MainWindow *mainwin;
 
@@ -560,40 +567,145 @@ void python_menu_done(void)
   }
 }
 
+
+static PyObject *get_StringIO_instance(void)
+{
+  PyObject *module_StringIO = NULL;
+  PyObject *class_StringIO = NULL;
+  PyObject *inst_StringIO = NULL;
+
+  module_StringIO = PyImport_ImportModule("cStringIO");
+  if(!module_StringIO) {
+    debug_print("Error getting traceback: Could not import module cStringIO\n");
+    goto done;
+  }
+
+  class_StringIO = PyObject_GetAttrString(module_StringIO, "StringIO");
+  if(!class_StringIO) {
+    debug_print("Error getting traceback: Could not get StringIO class\n");
+    goto done;
+  }
+
+  inst_StringIO = PyObject_CallObject(class_StringIO, NULL);
+  if(!inst_StringIO) {
+    debug_print("Error getting traceback: Could not create an instance of the StringIO class\n");
+    goto done;
+  }
+
+done:
+  Py_XDECREF(module_StringIO);
+  Py_XDECREF(class_StringIO);
+
+  return inst_StringIO;
+}
+
+static char* get_exception_information(PyObject *inst_StringIO)
+{
+  char *retval = NULL;
+  PyObject *meth_getvalue = NULL;
+  PyObject *result_getvalue = NULL;
+
+  if(!inst_StringIO)
+    goto done;
+
+  if(PySys_SetObject("stderr", inst_StringIO) != 0) {
+    debug_print("Error getting traceback: Could not set sys.stderr to a StringIO instance\n");
+    goto done;
+  }
+
+  meth_getvalue = PyObject_GetAttrString(inst_StringIO, "getvalue");
+  if(!meth_getvalue) {
+    debug_print("Error getting traceback: Could not get the getvalue method of the StringIO instance\n");
+    goto done;
+  }
+
+  PyErr_Print();
+
+  result_getvalue = PyObject_CallObject(meth_getvalue, NULL);
+  if(!result_getvalue) {
+    debug_print("Error getting traceback: Could not call the getvalue method of the StringIO instance\n");
+    goto done;
+  }
+
+  retval = g_strdup(PyString_AsString(result_getvalue));
+
+done:
+
+  Py_XDECREF(meth_getvalue);
+  Py_XDECREF(result_getvalue);
+
+  return retval ? retval : g_strdup("Unspecified error occurred");
+}
+
+static void log_func(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data)
+{
+}
+
 gint plugin_init(gchar **error)
 {
+  guint log_handler;
+  int parasite_retval;
+  PyObject *inst_StringIO = NULL;
+
   /* Version check */
   if(!check_plugin_version(MAKE_NUMERIC_VERSION(3,7,6,9), VERSION_NUMERIC, _("Python"), error))
     return -1;
 
   /* load hooks */
   hook_compose_create = hooks_register_hook(COMPOSE_CREATED_HOOKLIST, my_compose_create_hook, NULL);
-  if(hook_compose_create == (guint)-1) {
+  if(hook_compose_create == 0) {
     *error = g_strdup(_("Failed to register \"compose create hook\" in the Python plugin"));
     return -1;
   }
 
   /* script directories */
   if(!make_sure_directories_exist(error))
-    return -1;
+    goto err;
 
   /* initialize python interpreter */
   Py_Initialize();
 
-  /* initialize python interactive shell */
-  parasite_python_init();
+  /* The Python C API only offers to print an exception to sys.stderr. In order to catch it
+   * in a string, a StringIO object is created, to which sys.stderr can be redirected in case
+   * an error occurred. */
+  inst_StringIO = get_StringIO_instance();
 
   /* initialize Claws Mail Python module */
-  claws_mail_python_init();
+  initclawsmail();
+  if(PyErr_Occurred()) {
+    *error = get_exception_information(inst_StringIO);
+    goto err;
+  }
+
+  if(PyRun_SimpleString("import clawsmail") == -1) {
+    *error = g_strdup("Error importing the clawsmail module");
+    goto err;
+  }
+
+  /* initialize python interactive shell */
+  log_handler = g_log_set_handler(NULL, G_LOG_LEVEL_WARNING | G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO, log_func, NULL);
+  parasite_retval = parasite_python_init(error);
+  g_log_remove_handler(NULL, log_handler);
+  if(!parasite_retval) {
+    goto err;
+  }
 
   /* load menu options */
-  python_menu_init();
+  if(!python_menu_init(error)) {
+    goto err;
+  }
 
+  /* problems here are not fatal */
   run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_STARTUP, NULL);
 
   debug_print("Python plugin loaded\n");
 
   return 0;
+
+err:
+  hooks_unregister_hook(COMPOSE_CREATED_HOOKLIST, hook_compose_create);
+  Py_XDECREF(inst_StringIO);
+  return -1;
 }
 
 gboolean plugin_done(void)
@@ -612,6 +724,8 @@ gboolean plugin_done(void)
   /* finialize python interpreter */
   Py_Finalize();
 
+  parasite_python_done();
+
   debug_print("Python plugin done and unloaded.\n");
   return FALSE;
 }
@@ -624,14 +738,36 @@ const gchar *plugin_name(void)
 const gchar *plugin_desc(void)
 {
   return _("This plugin provides Python integration features.\n"
-           "\nFor the most up-to-date API documentation, type\n"
-           "\n help(clawsmail)\n"
-           "\nin the interactive Python console under Tools -> Show Python console.\n"
-           "\nThe source distribution of this plugin comes with various example scripts "
-           "in the \"examples\" subdirectory. If you wrote a script that you would be "
-           "interested in sharing, feel free to send it to me to have it considered "
-           "for inclusion in the examples.\n"
-           "\nFeedback to <berndth@gmx.de> is welcome.");
+      "Python code can be entered interactively into an embedded Python console, "
+      "under Tools -> Show Python console, or stored in scripts.\n\n"
+      "These scripts are then available via the menu. You can assign "
+      "keyboard shortcuts to them just like it is done with other menu items. "
+      "You can also put buttons for script invocation into the toolbars "
+      "using Claws Mail's builtin toolbar editor.\n\n"
+      "You can provide scripts working on the main window by placing files "
+      "into ~/.claws-mail/python-scripts/main.\n\n"
+      "You can also provide scripts working on an open compose window "
+      "by placing files into ~/.claws-mail/python-scripts/compose.\n\n"
+      "The folder ~/.claws-mail/python-scripts/auto/ may contain some "
+      "scripts that are automatically executed when certain events "
+      "occur. Currently, the following files in this directory "
+      "are recognised:\n\n"
+      "compose_any\n"
+      "Gets executed whenever a compose window is opened, no matter "
+      "if that opening happened as a result of composing a new message, "
+      "replying or forwarding a message.\n\n"
+      "startup\n"
+      "Executed at plugin load\n\n"
+      "shutdown\n"
+      "Executed at plugin unload\n\n"
+      "\nFor the most up-to-date API documentation, type\n"
+      "\n help(clawsmail)\n"
+      "\nin the interactive Python console.\n"
+      "\nThe source distribution of this plugin comes with various example scripts "
+      "in the \"examples\" subdirectory. If you wrote a script that you would be "
+      "interested in sharing, feel free to send it to me to have it considered "
+      "for inclusion in the examples.\n"
+      "\nFeedback to <berndth@gmx.de> is welcome.");
 }
 
 const gchar *plugin_type(void)