fix bug 4773, 'remove obsolescent AC_C_CONST'
[claws.git] / src / common / plugin.c
blob1ae02be782be7ac62606e083ae617fe8a408c167
1 /*
2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 2002-2022 the Claws Mail Team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #include "claws-features.h"
22 #endif
24 #include <stdio.h>
25 #ifdef HAVE_VALGRIND
26 #include <valgrind.h>
27 #endif
29 #include "defs.h"
30 #include <glib.h>
31 #ifdef ENABLE_NLS
32 #include <glib/gi18n.h>
33 #else
34 #define _(a) (a)
35 #define N_(a) (a)
36 #endif
37 #include <gmodule.h>
39 #include "utils.h"
40 #include "plugin.h"
41 #include "prefs.h"
42 #include "claws.h"
43 #include "timing.h"
44 #include "file-utils.h"
46 #ifdef G_OS_WIN32
47 #define PLUGINS_BLOCK_PREFIX "PluginsWin32_"
48 #else
49 #define PLUGINS_BLOCK_PREFIX "Plugins_"
50 #endif
52 struct _Plugin
54 gchar *filename;
55 GModule *module;
56 const gchar *(*name) (void);
57 const gchar *(*desc) (void);
58 const gchar *(*version) (void);
59 const gchar *(*type) (void);
60 const gchar *(*licence) (void);
61 struct PluginFeature *(*provides) (void);
63 GSList *rdeps;
64 gchar *error;
65 gboolean unloaded_hidden;
66 gboolean in_prefix_dir;
69 const gchar *plugin_feature_names[] =
70 { N_("Nothing"),
71 N_("a viewer"),
72 N_("a MIME parser"),
73 N_("folders"),
74 N_("filtering"),
75 N_("a privacy interface"),
76 N_("a notifier"),
77 N_("a utility"),
78 N_("things"),
79 NULL };
81 /* The plugin must be at least under one of these licences and have
82 the corresponding token returned by the plugin_licence function.
84 const gchar *plugin_licence_tokens[] = {
85 "LGPL2.1+", "LGPLv2.1+", "LGPL2.1", "LGPLv2.1",
86 "LGPL3+", "LGPLv3+", "LGPL3", "LGPLv3",
87 "GPL3+", "GPLv3+", "GPL3", "GPLv3",
88 "GPL2+", "GPLv2+",
89 "Apache2.0", "Apache 2.0", "Apache v2.0",
90 "2-clause BSD", "Simplified BSD", "FreeBSD",
91 "3-clause BSD", "New BSD", "Modified BSD",
92 NULL
95 /* Dual (or more) licences are allowed, must be separated by one of these.
97 #define IS_LICENCE_SEP(a) ((a) == ',' || (a) == ';' || (a) == '|' || (a) == '/' || (a) == '\0')
99 /**
100 * List of all loaded plugins
102 GSList *plugins = NULL;
103 GSList *plugin_types = NULL;
106 * List of plugins unloaded for some fixable reason
108 static GSList *unloaded_plugins = NULL;
110 static gint list_find_by_string(gconstpointer data, gconstpointer str)
112 return strcmp((gchar *)data, (gchar *)str) ? TRUE : FALSE;
115 static gint list_find_by_plugin_filename(const Plugin *plugin, const gchar *filename)
117 /* FIXME: There is a problem in case of symlinks or when a
118 user tries to load a plugin with the same name from a
119 different directory. I think it would be better to compare
120 only the basename of the filename here (case-insensitive on
121 W32). */
122 cm_return_val_if_fail(plugin, 1);
123 cm_return_val_if_fail(plugin->filename, 1);
124 cm_return_val_if_fail(filename, 1);
125 return strcmp(filename, plugin->filename);
128 static gboolean plugin_filename_is_standard_dir(const gchar *filename) {
129 return strncmp(filename, get_plugin_dir(), strlen(get_plugin_dir())) == 0;
132 static gchar * plugin_canonical_name(const Plugin *plugin)
134 if (plugin->in_prefix_dir == TRUE) {
135 if (plugin_filename_is_standard_dir(plugin->filename) == TRUE) {
136 gchar *plugin_name = g_path_get_basename(plugin->filename);
137 return plugin_name;
140 return g_strdup(plugin->filename);
143 void plugin_save_list(void)
145 gchar *rcpath, *block;
146 PrefFile *pfile;
147 GSList *type_cur, *plugin_cur;
148 Plugin *plugin;
150 for (type_cur = plugin_types; type_cur != NULL; type_cur = g_slist_next(type_cur)) {
151 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
152 block = g_strconcat(PLUGINS_BLOCK_PREFIX, type_cur->data, NULL);
153 if ((pfile = prefs_write_open(rcpath)) == NULL ||
154 (prefs_set_block_label(pfile, block) < 0)) {
155 g_warning("failed to write plugin list");
156 g_free(rcpath);
157 g_free(block);
158 return;
160 g_free(block);
162 for (plugin_cur = plugins; plugin_cur != NULL; plugin_cur = g_slist_next(plugin_cur)) {
163 plugin = (Plugin *) plugin_cur->data;
165 if (plugin->unloaded_hidden)
166 continue;
168 if (!strcmp(plugin->type(), type_cur->data)) {
169 gchar * name = plugin_canonical_name(plugin);
170 int err = fprintf(pfile->fp, "%s\n", name);
171 g_free(name);
172 if (err < 0)
173 goto revert;
176 for (plugin_cur = unloaded_plugins; plugin_cur != NULL; plugin_cur = g_slist_next(plugin_cur)) {
177 plugin = (Plugin *) plugin_cur->data;
179 if (plugin->unloaded_hidden)
180 continue;
182 if (!strcmp(plugin->type(), type_cur->data)) {
183 gchar * name = plugin_canonical_name(plugin);
184 int err = fprintf(pfile->fp, "%s\n", name);
185 g_free(name);
186 if (err < 0)
187 goto revert;
190 if (fprintf(pfile->fp, "\n") < 0)
191 goto revert;
193 if (prefs_file_close(pfile) < 0)
194 g_warning("failed to write plugin list");
196 g_free(rcpath);
198 continue;
200 revert:
201 g_warning("failed to write plugin list");
202 if (prefs_file_close_revert(pfile) < 0)
203 g_warning("failed to revert plugin list");
205 g_free(rcpath);
209 static gboolean plugin_is_loaded(const gchar *filename)
211 return (g_slist_find_custom(plugins, filename,
212 (GCompareFunc)list_find_by_plugin_filename) != NULL);
215 static Plugin *plugin_get_by_filename(const gchar *filename)
217 GSList *cur = plugins;
218 for(; cur; cur = cur->next) {
219 Plugin *p = (Plugin *)cur->data;
220 if (!strcmp(p->filename, filename)) {
221 return p;
224 return NULL;
227 /**
228 * Loads a plugin dependancies
230 * Plugin dependancies are, optionnaly, listed in a file in
231 * get_plugin_dir()/$pluginname.deps.
232 * \param filename The filename of the plugin for which we have to load deps
233 * \param error The location where an error string can be stored
234 * \return 0 on success, -1 otherwise
236 static gint plugin_load_deps(const gchar *filename, gchar **error)
238 gchar *tmp;
239 gchar *deps_file = NULL;
240 FILE *fp = NULL;
241 gchar buf[BUFFSIZE];
242 gchar *p;
244 tmp = g_strdup(filename);
245 if( (p = strrchr(tmp, '.')) )
246 *p = '\0';
247 deps_file = g_strconcat(tmp, ".deps", NULL);
248 g_free(tmp);
250 fp = claws_fopen(deps_file, "rb");
251 g_free(deps_file);
253 if (!fp)
254 return 0;
256 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
257 Plugin *dep_plugin = NULL;
258 gchar *path = NULL;
259 buf[strlen(buf)-1]='\0'; /* chop off \n */
260 path = g_strconcat(get_plugin_dir(), buf,
261 ".", G_MODULE_SUFFIX, NULL);
262 if ((dep_plugin = plugin_get_by_filename(path)) == NULL) {
263 debug_print("trying to load %s\n", path);
264 dep_plugin = plugin_load(path, error);
265 if (dep_plugin == NULL) {
266 g_free(path);
267 claws_fclose(fp);
268 return -1;
270 dep_plugin->in_prefix_dir = TRUE;
272 g_free(path);
273 if (!g_slist_find_custom(dep_plugin->rdeps,
274 (gpointer) filename, list_find_by_string)) {
275 debug_print("adding %s to %s rdeps\n",
276 filename,
277 dep_plugin->filename);
278 dep_plugin->rdeps =
279 g_slist_append(dep_plugin->rdeps,
280 g_strdup(filename));
283 claws_fclose(fp);
284 return 0;
287 static void plugin_unload_rdeps(Plugin *plugin)
289 GSList *cur = plugin->rdeps;
290 debug_print("removing %s rdeps\n", plugin->filename);
291 while (cur) {
292 gchar *file = (gchar *)cur->data;
293 Plugin *rdep_plugin = file?plugin_get_by_filename(file):NULL;
294 debug_print(" rdep %s: %p\n", file, rdep_plugin);
295 if (rdep_plugin) {
296 plugin_unload(rdep_plugin);
298 g_free(file);
299 cur = cur->next;
301 g_slist_free(plugin->rdeps);
302 plugin->rdeps = NULL;
305 static void plugin_remove_from_unloaded_list (const gchar *filename)
307 GSList *item = g_slist_find_custom(unloaded_plugins,
308 (gpointer) filename, (GCompareFunc)list_find_by_plugin_filename);
309 Plugin *unloaded_plugin = item ? ((Plugin *)item->data):NULL;
310 if (unloaded_plugin != NULL) {
311 debug_print("removing %s from unloaded list\n", unloaded_plugin->filename);
312 unloaded_plugins = g_slist_remove(unloaded_plugins, unloaded_plugin);
313 g_module_close(unloaded_plugin->module);
314 g_free(unloaded_plugin->filename);
315 g_free(unloaded_plugin->error);
316 g_free(unloaded_plugin);
320 static gchar *plugin_check_features(struct PluginFeature *features) {
321 int i = 0, j = 0;
322 GSList *cur = plugins;
324 if (features == NULL)
325 return NULL;
326 for(; cur; cur = cur->next) {
327 Plugin *p = (Plugin *)cur->data;
328 struct PluginFeature *cur_features = p->provides();
329 if (p->unloaded_hidden)
330 continue;
331 for (j = 0; cur_features[j].type != PLUGIN_NOTHING; j++) {
332 for (i = 0; features[i].type != PLUGIN_NOTHING; i++) {
333 if (cur_features[j].type == features[i].type &&
334 !strcmp(cur_features[j].subtype, features[i].subtype)) {
335 return g_strdup_printf(_(
336 "This plugin provides %s (%s), which is "
337 "already provided by the %s plugin."),
338 _(plugin_feature_names[features[i].type]),
339 _(features[i].subtype),
340 p->name());
346 return NULL;
349 static gboolean plugin_licence_check(const gchar *licence) {
350 gint i = 0;
351 gint len = 0;
353 if (licence != NULL) {
354 len = strlen(licence);
356 if (len == 0) {
357 g_warning("plugin licence check failed: empty licence");
358 return FALSE;
360 while (plugin_licence_tokens[i] != NULL) {
361 gchar *found = g_strstr_len(licence, len, plugin_licence_tokens[i]);
362 if (found != NULL) {
363 gint tlen = strlen(plugin_licence_tokens[i]);
364 if (len != tlen) { /* not a single license */
365 if (((found == licence) && (!IS_LICENCE_SEP(licence[tlen])))
366 || (!IS_LICENCE_SEP(*(found - 1)))
367 || (!IS_LICENCE_SEP(*(found + tlen)))) {
368 debug_print("plugin licence check failed: invalid separator\n");
369 return FALSE;
372 debug_print("plugin licence check passed: %s found\n", plugin_licence_tokens[i]);
373 return TRUE;
375 ++i;
377 debug_print("plugin licence check failed: %s is not a valid licence\n", licence);
378 return FALSE;
381 static Plugin *plugin_load_in_default_dir(const gchar *filename, gchar **error)
383 Plugin *plugin = NULL;
384 gchar *filename_default_dir = NULL;
385 gchar *default_error = NULL;
386 gchar *plugin_name = g_path_get_basename(filename);
388 filename_default_dir = g_strconcat(get_plugin_dir(), plugin_name, NULL);
390 debug_print("trying to load %s in default plugin directory %s\n",
391 plugin_name, get_plugin_dir());
393 g_free(plugin_name);
395 plugin = plugin_load(filename_default_dir, &default_error);
397 g_free(filename_default_dir);
399 if (plugin) {
400 g_free(*error);
401 *error = NULL;
402 plugin->in_prefix_dir = TRUE;
404 } else {
405 g_free(default_error);
408 return plugin;
412 * Loads a plugin
414 * \param filename The filename of the plugin to load
415 * \param error The location where an error string can be stored
416 * \return the plugin on success, NULL otherwise
418 Plugin *plugin_load(const gchar *filename, gchar **error)
420 Plugin *plugin;
421 gint (*plugin_init) (gchar **error);
422 gpointer plugin_name, plugin_desc, plugin_version;
423 const gchar *(*plugin_type)(void);
424 const gchar *(*plugin_licence)(void);
425 struct PluginFeature *(*plugin_provides)(void);
427 gint ok;
428 START_TIMING((filename?filename:"NULL plugin"));
429 cm_return_val_if_fail(filename != NULL, NULL);
430 cm_return_val_if_fail(error != NULL, NULL);
432 /* check duplicate plugin path name */
433 if (plugin_is_loaded(filename)) {
434 plugin = plugin_get_by_filename(filename);
435 if (plugin->unloaded_hidden) {
436 /* reshow it */
437 goto init_plugin;
438 } else {
439 *error = g_strdup(_("Plugin already loaded"));
440 return NULL;
444 plugin_remove_from_unloaded_list(filename);
446 if (plugin_load_deps(filename, error) < 0)
447 return NULL;
448 plugin = g_new0(Plugin, 1);
449 if (plugin == NULL) {
450 *error = g_strdup(_("Failed to allocate memory for Plugin"));
451 return NULL;
454 debug_print("trying to load `%s'\n", filename);
455 plugin->module = g_module_open(filename, 0);
456 if (plugin->module == NULL) {
457 *error = g_strdup(g_module_error());
458 g_free(plugin);
459 if (!plugin_filename_is_standard_dir(filename))
460 return plugin_load_in_default_dir(filename, error);
461 else
462 return NULL;
463 } else {
464 plugin->in_prefix_dir = FALSE;
467 init_plugin:
468 if (!g_module_symbol(plugin->module, "plugin_name", &plugin_name) ||
469 !g_module_symbol(plugin->module, "plugin_desc", &plugin_desc) ||
470 !g_module_symbol(plugin->module, "plugin_version", &plugin_version) ||
471 !g_module_symbol(plugin->module, "plugin_type", (gpointer)&plugin_type) ||
472 !g_module_symbol(plugin->module, "plugin_licence", (gpointer)&plugin_licence) ||
473 !g_module_symbol(plugin->module, "plugin_provides", (gpointer)&plugin_provides) ||
474 !g_module_symbol(plugin->module, "plugin_init", (gpointer)&plugin_init)) {
475 *error = g_strdup(g_module_error());
476 if (plugin->unloaded_hidden)
477 return NULL;
478 g_module_close(plugin->module);
479 g_free(plugin);
480 return NULL;
483 if (plugin_licence_check(plugin_licence()) != TRUE) {
484 *error = g_strdup(_("This module is not licensed under a GPL v3 or later compatible license."));
485 if (plugin->unloaded_hidden)
486 return NULL;
487 g_module_close(plugin->module);
488 g_free(plugin);
489 return NULL;
492 if (!strcmp(plugin_type(), "GTK")) {
493 *error = g_strdup(_("This module is for Claws Mail GTK1."));
494 if (plugin->unloaded_hidden)
495 return NULL;
496 g_module_close(plugin->module);
497 g_free(plugin);
498 return NULL;
501 if ((*error = plugin_check_features(plugin_provides())) != NULL) {
502 if (plugin->unloaded_hidden)
503 return NULL;
504 g_module_close(plugin->module);
505 g_free(plugin);
506 return NULL;
508 plugin->name = plugin_name;
509 plugin->desc = plugin_desc;
510 plugin->version = plugin_version;
511 plugin->type = plugin_type;
512 plugin->licence = plugin_licence;
513 plugin->provides = plugin_provides;
514 plugin->filename = g_strdup(filename);
515 plugin->error = NULL;
517 if ((ok = plugin_init(error)) < 0) {
518 if (*error)
519 plugin->error = g_strdup(*error);
520 unloaded_plugins = g_slist_append(unloaded_plugins, plugin);
521 return NULL;
524 if (!plugin->unloaded_hidden)
525 plugins = g_slist_append(plugins, plugin);
526 plugin->unloaded_hidden = FALSE;
528 debug_print("Plugin %s (from file %s) loaded\n", plugin->name(), filename);
529 END_TIMING();
530 return plugin;
533 void plugin_unload(Plugin *plugin)
535 gboolean (*plugin_done) (void);
536 gboolean can_unload = TRUE;
538 plugin_unload_rdeps(plugin);
540 if (plugin->unloaded_hidden)
541 return;
543 if (plugin->error) {
544 plugin_remove_from_unloaded_list(plugin->filename);
545 return;
547 if (g_module_symbol(plugin->module, "plugin_done", (gpointer) &plugin_done)) {
548 can_unload = plugin_done();
551 if (can_unload) {
552 #ifdef HAVE_VALGRIND
553 if (!RUNNING_ON_VALGRIND) {
554 g_module_close(plugin->module);
556 #else
557 g_module_close(plugin->module);
558 #endif
559 plugins = g_slist_remove(plugins, plugin);
560 g_free(plugin->filename);
561 g_free(plugin);
562 } else {
563 plugin->unloaded_hidden = TRUE;
568 static void replace_old_plugin_name(gchar *plugin_name)
570 gchar *old_name_end = g_strconcat("_plugin.", G_MODULE_SUFFIX, NULL);
571 gchar *matches = strstr(plugin_name, old_name_end);
573 if (!matches) {
574 g_free(old_name_end);
575 return;
576 } else if (plugin_name + strlen(plugin_name) != matches + strlen(matches)) {
577 g_free(old_name_end);
578 return;
579 } else {
580 gchar *new_name_end = g_strconcat(".", G_MODULE_SUFFIX, NULL);
581 int offset = strlen(plugin_name) - strlen(old_name_end);
583 debug_print("Replacing old plugin name %s\n", plugin_name);
585 strncpy(plugin_name + offset, new_name_end, strlen(old_name_end) - 1);
586 debug_print(" to %s\n", plugin_name);
587 g_free(new_name_end);
589 g_free(old_name_end);
592 void plugin_load_all(const gchar *type)
594 gchar *rcpath;
595 gchar buf[BUFFSIZE];
596 PrefFile *pfile;
597 gchar *error = NULL, *block;
598 gint failed = 0;
600 plugin_types = g_slist_append(plugin_types, g_strdup(type));
602 rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, COMMON_RC, NULL);
603 block = g_strconcat(PLUGINS_BLOCK_PREFIX, type, NULL);
604 if ((pfile = prefs_read_open(rcpath)) == NULL ||
605 ((failed = prefs_set_block_label(pfile, block)) < 0)) {
606 g_free(rcpath);
607 g_free(block);
608 if (!failed)
609 prefs_file_close(pfile);
610 return;
612 g_free(block);
614 while (claws_fgets(buf, sizeof(buf), pfile->fp) != NULL) {
615 if (buf[0] == '[')
616 break;
618 g_strstrip(buf);
619 replace_old_plugin_name(buf);
621 if ((buf[0] != '\0') && (plugin_load(buf, &error) == NULL)) {
622 g_warning("plugin loading error: %s", error);
623 g_free(error);
626 prefs_file_close(pfile);
628 g_free(rcpath);
631 void plugin_unload_all(const gchar *type)
633 GSList *list, *cur;
635 list = g_slist_copy(plugins);
636 list = g_slist_reverse(list);
638 for(cur = list; cur != NULL; cur = g_slist_next(cur)) {
639 Plugin *plugin = (Plugin *) cur->data;
641 if (!strcmp(type, plugin->type()))
642 plugin_unload(plugin);
644 g_slist_free(list);
646 cur = g_slist_find_custom(plugin_types, (gpointer) type, list_find_by_string);
647 if (cur) {
648 g_free(cur->data);
649 plugin_types = g_slist_remove(plugin_types, cur);
654 /* Load those plugins we always want to use. No error output; just
655 * try. */
656 void plugin_load_standard_plugins (void)
658 static const char *names[] = {
659 #ifdef G_OS_WIN32
660 "pgpmime",
661 "pgpinline",
662 #else
663 /* post-2.5 maybe
664 "bogofilter", */
665 #endif
666 NULL
668 int i;
669 gchar *error, *filename;
671 for (i=0; names[i]; i++) {
672 /* Simple hack to check whether the plugin has already
673 * been loaded but checking only for the basename. */
674 GSList *cur = plugins;
675 for(; cur; cur = cur->next) {
676 Plugin *p = (Plugin *)cur->data;
677 if (strstr(p->filename, names[i]))
678 break;
680 if (!cur) { /* Not yet loaded. */
681 /* FIXME: get_plugin_dir () returns with a trailing
682 * (back)slash; this should be fixed so that we can use
683 * g_module_build_path here. */
684 #ifdef G_OS_WIN32
685 filename = g_strconcat (get_plugin_dir(),
686 names[i], NULL);
687 #else
688 filename = g_strconcat (get_plugin_dir(),
689 names[i], ".", G_MODULE_SUFFIX, NULL);
690 #endif
691 error = NULL;
692 plugin_load(filename, &error);
693 g_free (error);
694 g_free(filename);
699 GSList *plugin_get_list(void)
701 GSList *new = NULL;
702 GSList *cur = plugins;
703 for (; cur; cur = cur->next) {
704 Plugin *p = (Plugin *)cur->data;
705 if (!p->unloaded_hidden)
706 new = g_slist_prepend(new, p);
708 new = g_slist_reverse(new);
709 return new;
712 Plugin *plugin_get_loaded_by_name(const gchar *name)
714 Plugin *plugin = NULL;
715 GSList *new, *cur;
716 new = plugin_get_list();
717 for (cur = new; cur; cur = g_slist_next(cur)) {
718 plugin = (Plugin *)cur->data;
719 if (!g_ascii_strcasecmp(plugin->name(), name))
720 break;
721 else
722 plugin = NULL;
724 g_slist_free(new);
725 return plugin;
728 GSList *plugin_get_unloaded_list(void)
730 return g_slist_copy(unloaded_plugins);
733 const gchar *plugin_get_name(Plugin *plugin)
735 return plugin->name();
738 const gchar *plugin_get_desc(Plugin *plugin)
740 return plugin->desc();
743 const gchar *plugin_get_version(Plugin *plugin)
745 return plugin->version();
748 const gchar *plugin_get_error(Plugin *plugin)
750 return plugin->error;
753 /* Generally called in plugin_init() function of each plugin. It check the
754 * minimal and compiled version of claws binary required by the plugin.
755 * If (@minimum_claws_version == 0 || @compiled_claws_version == 0), don't
756 * check the corresponding version.
758 * If an error occurs {
759 * If @error == NULL { don't allocate error string. }
760 * If @error != NULL { error string is allocated and must be freed after
761 * call function. }
763 * Returns: FALSE if an error occurs, TRUE if all is OK.
765 gint check_plugin_version(guint32 minimum_claws_version,
766 guint32 compiled_claws_version,
767 const gchar *plugin_name,
768 gchar **error)
770 guint32 claws_version = claws_get_version();
772 if (compiled_claws_version != 0 && claws_version > compiled_claws_version) {
773 if (error != NULL) {
774 *error = (plugin_name && *plugin_name)
775 ? g_strdup_printf(_("Your version of Claws Mail is newer than the "
776 "version the '%s' plugin was built with."),
777 plugin_name)
778 : g_strdup(_("Your version of Claws Mail is newer than the "
779 "version the plugin was built with."));
781 return FALSE;
784 if (minimum_claws_version != 0 && claws_version < minimum_claws_version) {
785 if (error != NULL) {
786 *error = (plugin_name && *plugin_name)
787 ? g_strdup_printf(_("Your version of Claws Mail is too old for "
788 "the '%s' plugin."), plugin_name)
789 : g_strdup(_("Your version of Claws Mail is too old for the plugin."));
791 return FALSE;
793 return TRUE;