sdgui: load dictionaries in parallel, as sdtui did

Also, resolve some use-after-frees in GTK+.
This commit is contained in:
Přemysl Eric Janouch 2021-10-16 08:27:42 +02:00
parent f147b54393
commit c0a094e473
Signed by: p
GPG Key ID: A0420B94F92B9493
4 changed files with 178 additions and 175 deletions

View File

@ -26,15 +26,6 @@
#include "utils.h" #include "utils.h"
#include "stardict-view.h" #include "stardict-view.h"
typedef struct dictionary Dictionary;
struct dictionary
{
const gchar *filename; ///< Filename
StardictDict *dict; ///< Stardict dictionary data
gchar *name; ///< Name to show
};
static struct static struct
{ {
GtkWidget *window; ///< Top-level window GtkWidget *window; ///< Top-level window
@ -43,8 +34,7 @@ static struct
GtkWidget *view; ///< Entries view GtkWidget *view; ///< Entries view
gint dictionary; ///< Index of the current dictionary gint dictionary; ///< Index of the current dictionary
Dictionary *dictionaries; ///< All open dictionaries GPtrArray *dictionaries; ///< All open dictionaries
gsize dictionaries_len; ///< Total number of dictionaries
gboolean watch_selection; ///< Following X11 PRIMARY? gboolean watch_selection; ///< Following X11 PRIMARY?
} }
@ -52,31 +42,15 @@ g;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static gboolean
dictionary_load (Dictionary *self, GError **e)
{
if (!(self->dict = stardict_dict_new (self->filename, e)))
return FALSE;
if (!self->name)
{
self->name = g_strdup (stardict_info_get_book_name
(stardict_dict_get_info (self->dict)));
}
return TRUE;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void static void
init (gchar **filenames) init (gchar **filenames)
{ {
while (filenames[g.dictionaries_len]) for (gsize i = 0; filenames[i]; i++)
g.dictionaries_len++; {
Dictionary *dict = g_malloc0 (sizeof *dict);
g.dictionaries = g_malloc0_n (sizeof *g.dictionaries, g.dictionaries_len); dict->filename = g_strdup (filenames[i]);
for (gsize i = 0; i < g.dictionaries_len; i++) g_ptr_array_add (g.dictionaries, dict);
g.dictionaries[i].filename = filenames[i]; }
} }
// TODO: try to deduplicate, similar to app_load_config_values() // TODO: try to deduplicate, similar to app_load_config_values()
@ -84,19 +58,21 @@ static gboolean
init_from_key_file (GKeyFile *kf, GError **error) init_from_key_file (GKeyFile *kf, GError **error)
{ {
const gchar *dictionaries = "Dictionaries"; const gchar *dictionaries = "Dictionaries";
gchar **names = gchar **names = g_key_file_get_keys (kf, dictionaries, NULL, NULL);
g_key_file_get_keys (kf, dictionaries, &g.dictionaries_len, NULL);
if (!names) if (!names)
return TRUE; return TRUE;
g.dictionaries = g_malloc0_n (sizeof *g.dictionaries, g.dictionaries_len); for (gsize i = 0; names[i]; i++)
for (gsize i = 0; i < g.dictionaries_len; i++) {
g.dictionaries[i].name = names[i]; Dictionary *dict = g_malloc0 (sizeof *dict);
dict->name = names[i];
g_ptr_array_add (g.dictionaries, dict);
}
g_free (names); g_free (names);
for (gsize i = 0; i < g.dictionaries_len; i++) for (gsize i = 0; i < g.dictionaries->len; i++)
{ {
Dictionary *dict = &g.dictionaries[i]; Dictionary *dict = g_ptr_array_index (g.dictionaries, i);
gchar *path = gchar *path =
g_key_file_get_string (kf, dictionaries, dict->name, error); g_key_file_get_string (kf, dictionaries, dict->name, error);
if (!path) if (!path)
@ -142,7 +118,7 @@ search (Dictionary *dict)
static void static void
on_changed (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED gpointer data) on_changed (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED gpointer data)
{ {
search (&g.dictionaries[g.dictionary]); search (g_ptr_array_index (g.dictionaries, g.dictionary));
} }
static void static void
@ -178,7 +154,7 @@ on_switch_page (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED GtkWidget *page,
guint page_num, G_GNUC_UNUSED gpointer data) guint page_num, G_GNUC_UNUSED gpointer data)
{ {
g.dictionary = page_num; g.dictionary = page_num;
search (&g.dictionaries[g.dictionary]); search (g_ptr_array_index (g.dictionaries, g.dictionary));
} }
static gboolean static gboolean
@ -256,17 +232,19 @@ main (int argc, char *argv[])
return 1; return 1;
} }
g.dictionaries =
g_ptr_array_new_with_free_func ((GDestroyNotify) dictionary_destroy);
if (filenames) if (filenames)
init (filenames); init (filenames);
else if (!init_from_config (&error) && error) else if (!init_from_config (&error) && error)
die_with_dialog (error->message); die_with_dialog (error->message);
g_strfreev (filenames);
for (gsize i = 0; i < g.dictionaries_len; i++) if (!g.dictionaries->len)
if (!dictionary_load (&g.dictionaries[i], &error))
die_with_dialog (error->message);
if (!g.dictionaries_len)
die_with_dialog (_("No dictionaries found either in " die_with_dialog (_("No dictionaries found either in "
"the configuration or on the command line")); "the configuration or on the command line"));
if (!load_dictionaries (g.dictionaries, &error))
die_with_dialog (error->message);
// Some Adwaita stupidity // Some Adwaita stupidity
const char *style = "notebook header tab { padding: 2px 8px; margin: 0; }"; const char *style = "notebook header tab { padding: 2px 8px; margin: 0; }";
@ -331,9 +309,9 @@ main (int argc, char *argv[])
g.view = stardict_view_new (); g.view = stardict_view_new ();
gtk_box_pack_end (GTK_BOX (superbox), g.view, TRUE, TRUE, 0); gtk_box_pack_end (GTK_BOX (superbox), g.view, TRUE, TRUE, 0);
for (gsize i = 0; i < g.dictionaries_len; i++) for (gsize i = 0; i < g.dictionaries->len; i++)
{ {
Dictionary *dict = &g.dictionaries[i]; Dictionary *dict = g_ptr_array_index (g.dictionaries, i);
GtkWidget *dummy = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); GtkWidget *dummy = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
GtkWidget *label = gtk_label_new (dict->name); GtkWidget *label = gtk_label_new (dict->name);
gtk_notebook_append_page (GTK_NOTEBOOK (g.notebook), dummy, label); gtk_notebook_append_page (GTK_NOTEBOOK (g.notebook), dummy, label);
@ -346,7 +324,5 @@ main (int argc, char *argv[])
gtk_widget_grab_focus (g.entry); gtk_widget_grab_focus (g.entry);
gtk_widget_show_all (g.window); gtk_widget_show_all (g.window);
gtk_main (); gtk_main ();
g_strfreev (filenames);
return 0; return 0;
} }

View File

@ -125,7 +125,7 @@ struct attrs
/// Data relating to one entry within the dictionary. /// Data relating to one entry within the dictionary.
typedef struct view_entry ViewEntry; typedef struct view_entry ViewEntry;
/// Data relating to a dictionary file. /// Data relating to a dictionary file.
typedef struct dictionary Dictionary; typedef struct app_dictionary AppDictionary;
/// Encloses application data. /// Encloses application data.
typedef struct application Application; typedef struct application Application;
@ -136,12 +136,10 @@ struct view_entry
GPtrArray * formatting; ///< chtype * or NULL per definition GPtrArray * formatting; ///< chtype * or NULL per definition
}; };
struct dictionary struct app_dictionary
{ {
gchar * name; ///< Visible identifier Dictionary super; ///< Superclass
gsize name_width; ///< Visible width of the name gsize name_width; ///< Visible width of the name
gchar * filename; ///< Path to the dictionary
StardictDict * dict; ///< Dictionary
}; };
struct application struct application
@ -153,7 +151,7 @@ struct application
gboolean locale_is_utf8; ///< The locale is Unicode gboolean locale_is_utf8; ///< The locale is Unicode
gboolean focused; ///< Whether the terminal has focus gboolean focused; ///< Whether the terminal has focus
GArray * dictionaries; ///< All loaded dictionaries GPtrArray * dictionaries; ///< All loaded AppDictionaries
StardictDict * dict; ///< The current dictionary StardictDict * dict; ///< The current dictionary
guint show_help : 1; ///< Whether help can be shown guint show_help : 1; ///< Whether help can be shown
@ -352,42 +350,6 @@ view_entry_free (ViewEntry *ve)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static gboolean
dictionary_load (Dictionary *self, Application *app, GError **e)
{
if (!(self->dict = stardict_dict_new (self->filename, e)))
return FALSE;
if (!self->name)
{
self->name = g_strdup (stardict_info_get_book_name
(stardict_dict_get_info (self->dict)));
}
// Add some padding for decorative purposes
gchar *tmp = g_strdup_printf (" %s ", self->name);
g_free (self->name);
self->name = tmp;
gunichar *ucs4 = g_utf8_to_ucs4_fast (self->name, -1, NULL);
for (gunichar *it = ucs4; *it; it++)
self->name_width += app_char_width (app, *it);
g_free (ucs4);
return TRUE;
}
static void
dictionary_free (Dictionary *self)
{
g_free (self->name);
g_free (self->filename);
if (self->dict)
g_object_unref (self->dict);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/// Reload view items. /// Reload view items.
static void static void
app_reload_view (Application *self) app_reload_view (Application *self)
@ -491,8 +453,10 @@ app_load_config_values (Application *self, GKeyFile *kf)
else else
resolved = path; resolved = path;
Dictionary dict = { .name = g_strdup (*it), .filename = resolved }; AppDictionary *dict = g_malloc0 (sizeof *dict);
g_array_append_val (self->dictionaries, dict); dict->super.name = g_strdup (*it);
dict->super.filename = resolved;
g_ptr_array_add (self->dictionaries, dict);
} }
g_strfreev (names); g_strfreev (names);
} }
@ -521,67 +485,29 @@ app_init_attrs (Application *self)
#undef XX #undef XX
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static gboolean static gboolean
app_load_dictionaries (Application *self, GError **e) app_load_dictionaries (Application *self, GError **e)
{ {
for (guint i = 0; i < self->dictionaries->len; i++) if (!load_dictionaries (self->dictionaries, e))
if (!dictionary_load (&g_array_index (self->dictionaries, return FALSE;
Dictionary, i), self, e))
return FALSE; for (gsize i = 0; i < self->dictionaries->len; i++)
{
AppDictionary *dict = g_ptr_array_index (self->dictionaries, i);
// Add some padding for decorative purposes
gchar *tmp = g_strdup_printf (" %s ", dict->super.name);
g_free (dict->super.name);
dict->super.name = tmp;
gunichar *ucs4 = g_utf8_to_ucs4_fast (dict->super.name, -1, NULL);
for (gunichar *it = ucs4; *it; it++)
dict->name_width += app_char_width (self, *it);
g_free (ucs4);
}
return TRUE; return TRUE;
} }
// Parallelize dictionary loading if possible, because of collation reindexing
#if GLIB_CHECK_VERSION (2, 36, 0)
struct load_ctx
{
Application *self; ///< Application context
GAsyncQueue *error_queue; ///< Errors
};
static void
app_load_worker (gpointer data, gpointer user_data)
{
struct load_ctx *ctx = user_data;
GError *e = NULL;
dictionary_load (&g_array_index (ctx->self->dictionaries, Dictionary,
GPOINTER_TO_UINT (data) - 1), ctx->self, &e);
if (e)
g_async_queue_push (ctx->error_queue, e);
}
static gboolean
app_load_dictionaries_parallel (Application *self, GError **e)
{
struct load_ctx ctx;
GThreadPool *pool = g_thread_pool_new (app_load_worker, &ctx,
g_get_num_processors (), TRUE, NULL);
if G_UNLIKELY (!g_thread_pool_get_num_threads (pool))
{
g_thread_pool_free (pool, TRUE, TRUE);
return app_load_dictionaries (self, e);
}
ctx.self = self;
ctx.error_queue = g_async_queue_new_full ((GDestroyNotify) g_error_free);
for (guint i = 0; i < self->dictionaries->len; i++)
g_thread_pool_push (pool, GUINT_TO_POINTER (i + 1), NULL);
g_thread_pool_free (pool, FALSE, TRUE);
gboolean result = TRUE;
if ((*e = g_async_queue_try_pop (ctx.error_queue)))
result = FALSE;
g_async_queue_unref (ctx.error_queue);
return result;
}
#define app_load_dictionaries app_load_dictionaries_parallel
#endif // GLib >= 2.36
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/// Initialize the application core. /// Initialize the application core.
@ -623,9 +549,8 @@ app_init (Application *self, char **filenames)
self->focused = TRUE; self->focused = TRUE;
app_init_attrs (self); app_init_attrs (self);
self->dictionaries = g_array_new (FALSE, TRUE, sizeof (Dictionary)); self->dictionaries =
g_array_set_clear_func g_ptr_array_new_with_free_func ((GDestroyNotify) dictionary_destroy);
(self->dictionaries, (GDestroyNotify) dictionary_free);
GError *error = NULL; GError *error = NULL;
app_load_config (self, &error); app_load_config (self, &error);
@ -640,11 +565,12 @@ app_init (Application *self, char **filenames)
// Dictionaries given on the command line override the configuration // Dictionaries given on the command line override the configuration
if (*filenames) if (*filenames)
{ {
g_array_set_size (self->dictionaries, 0); g_ptr_array_set_size (self->dictionaries, 0);
while (*filenames) while (*filenames)
{ {
Dictionary dict = { .filename = g_strdup (*filenames++) }; AppDictionary *dict = g_malloc0 (sizeof *dict);
g_array_append_val (self->dictionaries, dict); dict->super.filename = g_strdup (*filenames++);
g_ptr_array_add (self->dictionaries, dict);
} }
} }
@ -659,7 +585,8 @@ app_init (Application *self, char **filenames)
"the configuration or on the command line")); "the configuration or on the command line"));
exit (EXIT_FAILURE); exit (EXIT_FAILURE);
} }
self->dict = g_array_index (self->dictionaries, Dictionary, 0).dict; self->dict = ((AppDictionary *)
g_ptr_array_index (self->dictionaries, 0))->super.dict;
app_reload_view (self); app_reload_view (self);
} }
@ -715,7 +642,7 @@ app_destroy (Application *self)
g_ptr_array_free (self->entries, TRUE); g_ptr_array_free (self->entries, TRUE);
g_free (self->search_label); g_free (self->search_label);
g_array_free (self->input, TRUE); g_array_free (self->input, TRUE);
g_array_free (self->dictionaries, TRUE); g_ptr_array_free (self->dictionaries, TRUE);
g_iconv_close (self->ucs4_to_locale); g_iconv_close (self->ucs4_to_locale);
} }
@ -935,7 +862,7 @@ app_redraw_top (Application *self)
for (guint i = 0; i < self->dictionaries->len; i++) for (guint i = 0; i < self->dictionaries->len; i++)
{ {
Dictionary *dict = &g_array_index (self->dictionaries, Dictionary, i); Dictionary *dict = g_ptr_array_index (self->dictionaries, i);
row_buffer_append (&buf, dict->name, row_buffer_append (&buf, dict->name,
APP_ATTR_IF (self->dictionaries->len > 1 APP_ATTR_IF (self->dictionaries->len > 1
&& self->dict == dict->dict, ACTIVE, HEADER)); && self->dict == dict->dict, ACTIVE, HEADER));
@ -1410,7 +1337,7 @@ app_goto_dictionary (Application *self, guint n)
if (n >= self->dictionaries->len) if (n >= self->dictionaries->len)
return FALSE; return FALSE;
Dictionary *dict = &g_array_index (self->dictionaries, Dictionary, n); Dictionary *dict = g_ptr_array_index (self->dictionaries, n);
self->dict = dict->dict; self->dict = dict->dict;
app_search_for_entry (self); app_search_for_entry (self);
app_redraw_top (self); app_redraw_top (self);
@ -1421,13 +1348,13 @@ app_goto_dictionary (Application *self, guint n)
static gboolean static gboolean
app_goto_dictionary_delta (Application *self, gint n) app_goto_dictionary_delta (Application *self, gint n)
{ {
GArray *dicts = self->dictionaries; GPtrArray *dicts = self->dictionaries;
if (dicts->len <= 1) if (dicts->len <= 1)
return FALSE; return FALSE;
guint i = 0; guint i = 0;
while (i < dicts->len && while (i < dicts->len &&
g_array_index (dicts, Dictionary, i).dict != self->dict) ((Dictionary *) g_ptr_array_index (dicts, i))->dict != self->dict)
i++; i++;
return app_goto_dictionary (self, (i + dicts->len + n) % dicts->len); return app_goto_dictionary (self, (i + dicts->len + n) % dicts->len);
@ -1858,10 +1785,10 @@ app_process_left_mouse_click (Application *self, int line, int column)
if (column < indent) if (column < indent)
return; return;
Dictionary *dicts = (Dictionary *) self->dictionaries->data;
for (guint i = 0; i < self->dictionaries->len; i++) for (guint i = 0; i < self->dictionaries->len; i++)
{ {
if (column < (indent += dicts[i].name_width)) AppDictionary *dict = g_ptr_array_index (self->dictionaries, i);
if (column < (indent += dict->name_width))
{ {
app_goto_dictionary (self, i); app_goto_dictionary (self, i);
return; return;

View File

@ -113,7 +113,7 @@ fatal (const gchar *format, ...)
// At times, GLib even with its sheer size is surprisingly useless, // At times, GLib even with its sheer size is surprisingly useless,
// and I need to port some code over from "liberty". // and I need to port some code over from "liberty".
static gchar ** static const gchar **
get_xdg_config_dirs (void) get_xdg_config_dirs (void)
{ {
GPtrArray *paths = g_ptr_array_new (); GPtrArray *paths = g_ptr_array_new ();
@ -122,12 +122,12 @@ get_xdg_config_dirs (void)
*system; system++) *system; system++)
g_ptr_array_add (paths, (gpointer) *system); g_ptr_array_add (paths, (gpointer) *system);
g_ptr_array_add (paths, NULL); g_ptr_array_add (paths, NULL);
return (gchar **) g_ptr_array_free (paths, FALSE); return (const gchar **) g_ptr_array_free (paths, FALSE);
} }
gchar * gchar *
resolve_relative_filename_generic resolve_relative_filename_generic
(gchar **paths, const gchar *tail, const gchar *filename) (const gchar **paths, const gchar *tail, const gchar *filename)
{ {
for (; *paths; paths++) for (; *paths; paths++)
{ {
@ -147,10 +147,10 @@ resolve_relative_filename_generic
gchar * gchar *
resolve_relative_config_filename (const gchar *filename) resolve_relative_config_filename (const gchar *filename)
{ {
gchar **paths = get_xdg_config_dirs (); const gchar **paths = get_xdg_config_dirs ();
gchar *result = resolve_relative_filename_generic gchar *result =
(paths, PROJECT_NAME, filename); resolve_relative_filename_generic (paths, PROJECT_NAME, filename);
g_strfreev (paths); g_free (paths);
return result; return result;
} }
@ -202,7 +202,7 @@ GKeyFile *
load_project_config_file (GError **error) load_project_config_file (GError **error)
{ {
GKeyFile *key_file = g_key_file_new (); GKeyFile *key_file = g_key_file_new ();
gchar **paths = get_xdg_config_dirs (); const gchar **paths = get_xdg_config_dirs ();
GError *e = NULL; GError *e = NULL;
// XXX: if there are dashes in the final path component, // XXX: if there are dashes in the final path component,
@ -210,8 +210,8 @@ load_project_config_file (GError **error)
// which is completely undocumented // which is completely undocumented
g_key_file_load_from_dirs (key_file, g_key_file_load_from_dirs (key_file,
PROJECT_NAME G_DIR_SEPARATOR_S PROJECT_NAME ".conf", PROJECT_NAME G_DIR_SEPARATOR_S PROJECT_NAME ".conf",
(const gchar **) paths, NULL, 0, &e); paths, NULL, 0, &e);
g_strfreev (paths); g_free (paths);
if (!e) if (!e)
return key_file; return key_file;
@ -223,3 +223,84 @@ load_project_config_file (GError **error)
g_key_file_free (key_file); g_key_file_free (key_file);
return NULL; return NULL;
} }
// --- Loading -----------------------------------------------------------------
void
dictionary_destroy (Dictionary *self)
{
g_free (self->name);
g_free (self->filename);
if (self->dict)
g_object_unref (self->dict);
g_free (self);
}
static gboolean
dictionary_load (Dictionary *self, GError **e)
{
if (!(self->dict = stardict_dict_new (self->filename, e)))
return FALSE;
if (!self->name)
{
self->name = g_strdup (stardict_info_get_book_name
(stardict_dict_get_info (self->dict)));
}
return TRUE;
}
static gboolean
load_dictionaries_sequentially (GPtrArray *dictionaries, GError **e)
{
for (guint i = 0; i < dictionaries->len; i++)
if (!dictionary_load (g_ptr_array_index (dictionaries, i), e))
return FALSE;
return TRUE;
}
// Parallelize dictionary loading if possible, because of collation reindexing
#if GLIB_CHECK_VERSION (2, 36, 0)
static void
load_worker (gpointer data, gpointer user_data)
{
GError *e = NULL;
dictionary_load (data, &e);
if (e)
g_async_queue_push (user_data, e);
}
gboolean
load_dictionaries (GPtrArray *dictionaries, GError **e)
{
GAsyncQueue *error_queue =
g_async_queue_new_full ((GDestroyNotify) g_error_free);
GThreadPool *pool = g_thread_pool_new (load_worker, error_queue,
g_get_num_processors (), TRUE, NULL);
if G_UNLIKELY (!g_thread_pool_get_num_threads (pool))
{
g_thread_pool_free (pool, TRUE, TRUE);
g_async_queue_unref (error_queue);
return load_dictionaries_sequentially (dictionaries, e);
}
for (guint i = 0; i < dictionaries->len; i++)
g_thread_pool_push (pool, g_ptr_array_index (dictionaries, i), NULL);
g_thread_pool_free (pool, FALSE, TRUE);
gboolean result = TRUE;
if ((*e = g_async_queue_try_pop (error_queue)))
result = FALSE;
g_async_queue_unref (error_queue);
return result;
}
#else // GLib < 2.36
gboolean
load_dictionaries (GPtrArray *dictionaries, GError **e)
{
return load_dictionaries_sequentially (dictionaries, e);
}
#endif // GLib < 2.36

View File

@ -19,6 +19,11 @@
#ifndef UTILS_H #ifndef UTILS_H
#define UTILS_H #define UTILS_H
#include <glib.h>
#include <gio/gio.h>
#include "stardict.h"
/// After this statement, the element has been found and its index is stored /// After this statement, the element has been found and its index is stored
/// in the variable "imid". /// in the variable "imid".
#define BINARY_SEARCH_BEGIN(max, compare) \ #define BINARY_SEARCH_BEGIN(max, compare) \
@ -44,10 +49,24 @@ gboolean xstrtoul (unsigned long *out, const char *s, int base);
void fatal (const gchar *format, ...) G_GNUC_PRINTF (1, 2) G_GNUC_NORETURN; void fatal (const gchar *format, ...) G_GNUC_PRINTF (1, 2) G_GNUC_NORETURN;
gchar *resolve_relative_filename_generic gchar *resolve_relative_filename_generic
(gchar **paths, const gchar *tail, const gchar *filename); (const gchar **paths, const gchar *tail, const gchar *filename);
gchar *resolve_relative_config_filename (const gchar *filename); gchar *resolve_relative_config_filename (const gchar *filename);
gchar *resolve_filename gchar *resolve_filename
(const gchar *filename, gchar *(*relative_cb) (const char *)); (const gchar *filename, gchar *(*relative_cb) (const char *));
GKeyFile *load_project_config_file (GError **error); GKeyFile *load_project_config_file (GError **error);
// --- Loading -----------------------------------------------------------------
typedef struct dictionary Dictionary;
struct dictionary
{
gchar *filename; ///< Path to the dictionary
StardictDict *dict; ///< StarDict dictionary data
gchar *name; ///< Name to show
};
void dictionary_destroy (Dictionary *self);
gboolean load_dictionaries (GPtrArray *dictionaries, GError **e);
#endif // ! UTILS_H #endif // ! UTILS_H