[glib/wip/gdesktopappinfo: 4/6] Add g_desktop_app_info_search()
- From: Ryan Lortie <desrt src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib/wip/gdesktopappinfo: 4/6] Add g_desktop_app_info_search()
- Date: Wed, 16 Oct 2013 23:32:33 +0000 (UTC)
commit 52552e4231eee47ef3a2416d8687d770b7314d0d
Author: Ryan Lortie <desrt desrt ca>
Date: Sun Jul 28 13:41:22 2013 -0400
Add g_desktop_app_info_search()
gio/gdesktopappinfo.c | 904 ++++++++++++++++++++++++++++++++++++++++++++-----
gio/gdesktopappinfo.h | 2 +
2 files changed, 817 insertions(+), 89 deletions(-)
---
diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c
index 63ba0fd..95759e2 100644
--- a/gio/gdesktopappinfo.c
+++ b/gio/gdesktopappinfo.c
@@ -29,6 +29,7 @@
#include <errno.h>
#include <string.h>
#include <unistd.h>
+#include <locale.h>
#ifdef HAVE_CRT_EXTERNS_H
#include <crt_externs.h>
@@ -148,6 +149,143 @@ G_DEFINE_TYPE_WITH_CODE (GDesktopAppInfo, g_desktop_app_info, G_TYPE_OBJECT,
G_LOCK_DEFINE_STATIC (g_desktop_env);
static gchar *g_desktop_env = NULL;
+/* locale utilities {{{1 */
+
+/* We need more control over the output of this function than we get
+ * from the public APIs of glib, so roll our own version of this.
+ *
+ * We don't make this a public API of GLib because it's extremely
+ * difficult to get this working correctly on Windows, but we don't have
+ * to worry about that here.
+ *
+ * If LANGUAGE is given (and l10n is enabled), we want the list of
+ * languages from this environment variable. Otherwise, we want a
+ * single language code. If l10n is disabled, NULL will do.
+ *
+ * Language codes should have the form xx[_YY][ zzz]
+ */
+static gchar **
+get_locale_list (void)
+{
+ const gchar *lc_messages;
+ const gchar *language;
+
+ /* We really should be checking LC_MESSAGES, but it's not in POSIX.
+ * If we don't have it, use LC_CTYPE instead and manually check the
+ * variables that may have impacted the setting of LC_MESSAGES if we
+ * had it.
+ *
+ * Of course, none of this is particularly threadsafe...
+ */
+#ifdef LC_MESSAGES
+ lc_messages = setlocale (LC_MESSAGES, NULL);
+#else
+ lc_messages = setlocale (LC_CTYPE, NULL);
+
+ /* If LC_CTYPE is the "C" locale then this is a good indication that
+ * l10n has not been enabled for this program.
+ *
+ * Otherwise, do some more checking to determine what should be the
+ * correct LC_MESSAGES value.
+ */
+ if (lc_messages != NULL && !g_str_equal (lc_messages, "C") && !g_str_equal (lc_messages, "POSIX"))
+ {
+ /* We want to consult the value of LC_MESSAGES to get a more
+ * specific value than we saw from LC_CTYPE, but we only want to
+ * do this in the case that LC_ALL was not set.
+ *
+ * We do not consult LANG because this would already have impacted
+ * the setting of LC_CTYPE as well.
+ */
+ if (!g_getenv ("LC_ALL"))
+ {
+ const gchar *var;
+
+ var = g_getenv ("LC_MESSAGES");
+
+ if (var)
+ lc_messages = var;
+ }
+ }
+#endif
+
+ /* Hopefully we now have a reasonable value for 'lc_messages'.
+ *
+ * If it's a C locale, just return NULL now, ignoring LANGUAGE.
+ */
+ if (lc_messages == NULL || g_str_equal (lc_messages, "C") || g_str_equal (lc_messages, "POSIX"))
+ return NULL;
+
+ /* Otherwise, check LANGUAGE. */
+ language = g_getenv ("LANGUAGE");
+
+ /* If LANGUAGE was set, split it out and return it. */
+ if (language)
+ return g_strsplit (language, ":", 0);
+
+ /* Otherwise, return the LC_MESSAGES value as a one-item array. */
+ {
+ gchar **locales;
+
+ locales = g_new (gchar *, 1 + 1);
+ locales[0] = g_strdup (lc_messages);
+ locales[1] = NULL;
+
+ return locales;
+ }
+}
+
+/* This function returns an array of arrays of strings such that each
+ * item in the outermost array corresponds to one language that the user
+ * has selected (with the first item being the most-preferred language).
+ *
+ * Each inner array gives a priority-ordered list of locales that would
+ * be a suitable match for that language.
+ *
+ * The idea is that for each item in the outer array, we should have a
+ * corresponding locale in the desktop file index that we perform
+ * searches on, even if it is the C locale (since missing translations
+ * would result in the C locale being used anyway).
+ *
+ * The very first matching locale is the one we use for querying
+ * strings, however.
+ */
+static const gchar * const * const *
+get_exploded_locale_list (void)
+{
+ static gchar ***list;
+
+ if (g_once_init_enter (&list))
+ {
+ gchar ***result;
+ gchar **locales;
+ gint i, n;
+
+ locales = get_locale_list ();
+ if (locales == NULL || locales[0] == NULL)
+ {
+ g_strfreev (locales);
+ locales = g_new (gchar *, 2);
+ locales[0] = g_strdup ("C");
+ locales[1] = NULL;
+ }
+
+ n = g_strv_length (locales);
+
+ result = g_new (gchar **, n + 1);
+ for (i = 0; i < n; i++)
+ result[i] = g_get_locale_variants (locales[i]);
+ result[i] = NULL;
+
+ g_once_init_leave (&list, result);
+ g_strfreev (locales);
+ }
+
+ return (const gchar * const * const *) list;
+}
+
+/* DesktopFileDirIndex implementation {{{1 */
+
/* DesktopFileDir implementation {{{1 */
typedef struct
@@ -157,6 +295,11 @@ typedef struct
GHashTable *app_names;
gboolean is_setup;
struct dfi_index *dfi;
+ gint8 *key_id_map;
+ guint16 key_id_map_length;
+ guint16 *locale_ids;
+ guint16 desktop_entry_id;
+ GHashTable *memory_index;
} DesktopFileDir;
static DesktopFileDir *desktop_file_dirs;
@@ -323,93 +466,211 @@ desktop_key_init (void)
desktop_key_names[DESKTOP_KEY_X_GNOME_FullName] = g_intern_static_string ("X-GNOME-FullName");
}
+/* Search global state {{{2
+ *
+ * We only ever search under a global lock, so we can use (and reuse)
+ * some global data to reduce allocations made while searching.
+ *
+ * In short, we keep around arrays of results that we expand as needed
+ * (and never shrink).
+ *
+ * static_token_results: this is where we append the results for each
+ * token within a given desktop directory, as we handle it (which is
+ * a union of all matches for this term)
+ *
+ * static_search_results: this is where we build the complete results
+ * for a single directory (which is an intersection of the matches
+ * found for each term)
+ *
+ * static_total_results: this is where we build the complete results
+ * across all directories (which is a union of the matches found in
+ * each directory)
+ */
+struct result
+{
+ const gchar *app_name;
+ gint category;
+};
+
+static struct result *static_token_results;
+static gint static_token_results_size;
+static gint static_token_results_allocated;
+static struct result *static_search_results;
+static gint static_search_results_size;
+static gint static_search_results_allocated;
+static struct result *static_total_results;
+static gint static_total_results_size;
+static gint static_total_results_allocated;
+
+/* And some functions for performing nice operations against it */
+static gint
+compare_results (gconstpointer a,
+ gconstpointer b)
+{
+ const struct result *ra = a;
+ const struct result *rb = b;
+
+ if (ra->app_name < rb->app_name)
+ return -1;
+
+ else if (ra->app_name > rb->app_name)
+ return 1;
+
+ else
+ return ra->category - rb->category;
+}
+
+static gint
+compare_categories (gconstpointer a,
+ gconstpointer b)
+{
+ const struct result *ra = a;
+ const struct result *rb = b;
+
+ return ra->category - rb->category;
+}
+
static void
-insert_into_list (GSList **categories,
- const gchar *item,
- gint into_bucket,
- gint max_hits)
+add_token_result (const gchar *app_name,
+ guint16 category)
{
- gint n_hits = 0;
- gint i;
+ if G_UNLIKELY (static_token_results_size == static_token_results_allocated)
+ {
+ static_token_results_allocated = MAX (16, static_token_results_allocated * 2);
+ static_token_results = g_renew (struct result, static_token_results, static_token_results_allocated);
+ }
+
+ static_token_results[static_token_results_size].app_name = app_name;
+ static_token_results[static_token_results_size].category = category;
+ static_token_results_size++;
+}
- for (i = 0; i <= into_bucket; i++)
+static void
+merge_token_results (gboolean first)
+{
+ qsort (static_token_results, static_token_results_size, sizeof (struct result), compare_results);
+
+ /* If this is the first token then we are basically merging a list with
+ * itself -- we only perform de-duplication.
+ *
+ * If this is not the first token then we are doing a real merge.
+ */
+ if (first)
{
- GSList *node;
+ const gchar *last_name = NULL;
+ gint i;
- /* If we have enough hits from previous categories, stop.
- *
- * Note. This check will not be applied after counting up the
- * hits in categories[into_bucket] and that's intentional: we
- * don't want to toss out our new item if it has the same score as
- * items we're already returning.
+ /* We must de-duplicate, but we do so by taking the best category
+ * in each case.
*
- * That might mean that we get more than max_hits items, but
- * that's the desired behaviour.
+ * The final list can be as large as the input here, so make sure
+ * we have enough room (even if it's too much room).
*/
- if (max_hits >= 0 && n_hits >= max_hits)
- return;
- /* We want to do the dup-checking in all cases, though */
- for (node = categories[i]; node; node = node->next)
+ if G_UNLIKELY (static_search_results_allocated < static_token_results_size)
+ {
+ static_search_results_allocated = static_token_results_allocated;
+ static_search_results = g_renew (struct result, static_search_results,
static_search_results_allocated);
+ }
+
+ for (i = 0; i < static_token_results_size; i++)
{
- /* Pointer comparison is good enough because we're using the
- * name from the desktop index (where it is unique) and
- * because we'll never see the same desktop file name across
- * multiple indexes due to our use of masking.
+ /* The list is sorted so that the best match for a given id
+ * will be at the front, so once we have copied an id, skip
+ * the rest of the entries for the same id.
*/
- if (node->data == item)
- return;
+ if (static_token_results[i].app_name == last_name)
+ continue;
+
+ last_name = static_token_results[i].app_name;
- n_hits++;
+ static_search_results[static_search_results_size++] = static_token_results[i];
}
}
-
- /* No duplicate found at this level or any before, and not too many
- * items in earlier buckets, so add our item.
- */
- categories[into_bucket] = g_slist_prepend (categories[into_bucket], (gchar *) item);
- n_hits++;
-
- /* We may need to remove ourselves from a later category if we already
- * had a weaker hit for this item.
- *
- * We may also need to blow away an entire category if n_hits is big
- * enough.
- */
- for (i = into_bucket + 1; i < DESKTOP_KEY_NUM_MATCH_CATEGORIES; i++)
+ else
{
- if (!categories[i])
- continue;
+ const gchar *last_name = NULL;
+ gint i, j = 0;
+ gint k = 0;
- if (max_hits >= 0 && n_hits >= max_hits)
- {
- g_slist_free (categories[i]);
- categories[i] = NULL;
- }
- else
+ /* We only ever remove items from the results list, so no need to
+ * resize to ensure that we have enough room.
+ */
+ for (i = 0; i < static_token_results_size; i++)
{
- GSList **ptr;
-
- for (ptr = &categories[i]; *ptr; ptr = &(*ptr)->next)
- if ((*ptr)->data == item)
- {
- /* Found us! */
- *ptr = g_slist_delete_link (*ptr, *ptr);
- n_hits--;
-
- /* Since we effectively didn't add any items this time
- * around, we know we will not need to blow away later
- * categories. We also know that we will not appear in
- * a later category, since we appeared here.
- *
- * So, we're done.
- */
- return;
- }
+ if (static_token_results[i].app_name == last_name)
+ continue;
+
+ last_name = static_token_results[i].app_name;
+
+ /* Now we only want to have a result in static_search_results
+ * if we already have it there *and* we have it in
+ * static_token_results as well. The category will be the
+ * lesser of the two.
+ *
+ * Skip past the results in static_search_results that are not
+ * going to be matches.
+ */
+ while (k < static_search_results_size &&
+ static_search_results[k].app_name < static_token_results[i].app_name)
+ k++;
+
+ if (k < static_search_results_size &&
+ static_search_results[k].app_name == static_token_results[i].app_name)
+ {
+ /* We have a match.
+ *
+ * Category should be the worse of the two (ie:
+ * numerically larger).
+ */
+ static_search_results[j].app_name = static_search_results[k].app_name;
+ static_search_results[j].category = MAX (static_search_results[k].category,
+ static_token_results[i].category);
+ j++;
+ }
}
+
+ static_search_results_size = j;
}
+
+ /* Clear it out for next time... */
+ static_token_results_size = 0;
+}
+
+static void
+reset_total_search_results (void)
+{
+ static_total_results_size = 0;
+}
+
+static void
+sort_total_search_results (void)
+{
+ qsort (static_total_results, static_total_results_size, sizeof (struct result), compare_categories);
}
+static void
+merge_directory_results (void)
+{
+ if G_UNLIKELY (static_total_results_size + static_search_results_size > static_total_results_allocated)
+ {
+ static_total_results_allocated = MAX (16, static_total_results_allocated);
+ while (static_total_results_allocated < static_total_results_size + static_search_results_size)
+ static_total_results_allocated *= 2;
+ static_total_results = g_renew (struct result, static_total_results, static_total_results_allocated);
+ }
+
+ memcpy (static_total_results + static_total_results_size,
+ static_search_results,
+ static_search_results_size * sizeof (struct result));
+
+ static_total_results_size += static_search_results_size;
+
+ /* Clear it out for next time... */
+ static_search_results_size = 0;
+}
+
+
/* Support for unindexed DesktopFileDirs {{{2 */
static void
get_apps_from_dir (GHashTable **apps,
@@ -497,39 +758,287 @@ desktop_file_dir_unindexed_get_all (DesktopFileDir *dir,
}
}
-/* Support for indexed DesktopFileDirs {{{2 */
+typedef struct _MemoryIndexEntry MemoryIndexEntry;
+typedef GHashTable MemoryIndex;
-#if 0
-enum
+struct _MemoryIndexEntry
+{
+ const gchar *app_name; /* pointer to the hashtable key */
+ gint match_category;
+ MemoryIndexEntry *next;
+};
+
+static void
+memory_index_entry_free (gpointer data)
{
- DFI_ID_THIS_LOCALE,
+ MemoryIndexEntry *mie = data;
- DFI_ID_Desktop_Entry,
+ while (mie)
+ {
+ MemoryIndexEntry *next = mie->next;
- DFI_ID_Name,
- DFI_ID_Keywords,
- DFI_ID_GenericName,
- DFI_ID_X_GNOME_FullName,
- DFI_ID_Comment,
+ g_slice_free (MemoryIndexEntry, mie);
+ mie = next;
+ }
+}
- N_DFI_IDS
-};
-#endif
+static void
+memory_index_add_token (MemoryIndex *mi,
+ const gchar *token,
+ gint match_category,
+ const gchar *app_name)
+{
+ MemoryIndexEntry *mie, *first;
+
+ mie = g_slice_new (MemoryIndexEntry);
+ mie->app_name = app_name;
+ mie->match_category = match_category;
+
+ first = g_hash_table_lookup (mi, token);
+
+ if (first)
+ {
+ mie->next = first->next;
+ first->next = mie;
+ }
+ else
+ {
+ mie->next = NULL;
+ g_hash_table_insert (mi, g_strdup (token), mie);
+ }
+}
+
+static void
+memory_index_add_string (MemoryIndex *mi,
+ const gchar *string,
+ gint match_category,
+ const gchar *app_name)
+{
+ gchar **tokens, **alternates;
+ gint i;
+
+ tokens = g_str_tokenize_and_fold (string, NULL, &alternates);
+
+ for (i = 0; tokens[i]; i++)
+ memory_index_add_token (mi, tokens[i], match_category, app_name);
+
+ for (i = 0; alternates[i]; i++)
+ memory_index_add_token (mi, alternates[i], match_category, app_name);
+
+ g_strfreev (alternates);
+ g_strfreev (tokens);
+}
+
+static MemoryIndex *
+memory_index_new (void)
+{
+ return g_hash_table_new_full (g_str_hash, g_str_equal, g_free, memory_index_entry_free);
+}
+
+static void
+desktop_file_dir_unindexed_setup_search (DesktopFileDir *dir)
+{
+ GHashTableIter iter;
+ gpointer app, path;
+
+ desktop_key_init ();
+
+ dir->memory_index = memory_index_new ();
+
+ /* Nothing to search? */
+ if (dir->app_names == NULL)
+ return;
+
+ g_hash_table_iter_init (&iter, dir->app_names);
+ while (g_hash_table_iter_next (&iter, &app, &path))
+ {
+ GKeyFile *key_file;
+
+ key_file = g_key_file_new ();
+
+ if (g_key_file_load_from_file (key_file, path, G_KEY_FILE_NONE, NULL))
+ {
+ /* Index the interesting keys... */
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (desktop_key_match_category); i++)
+ {
+ gchar *value;
+
+ if (!desktop_key_match_category[i])
+ continue;
+
+ value = g_key_file_get_locale_string (key_file, "Desktop Entry", desktop_key_names[i], NULL,
NULL);
+
+ if (value)
+ memory_index_add_string (dir->memory_index, value, desktop_key_match_category[i], app);
+
+ g_free (value);
+ }
+ }
+
+ g_key_file_free (key_file);
+ }
+}
+
+static void
+desktop_file_dir_unindexed_search (DesktopFileDir *dir,
+ const gchar *search_token)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+
+ if (!dir->memory_index)
+ desktop_file_dir_unindexed_setup_search (dir);
+
+ g_hash_table_iter_init (&iter, dir->memory_index);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ MemoryIndexEntry *mie = value;
+
+ if (!g_str_has_prefix (key, search_token))
+ continue;
+
+ while (mie)
+ {
+ add_token_result (mie->app_name, mie->match_category);
+ mie = mie->next;
+ }
+ }
+}
+
+/* Support for indexed DesktopFileDirs {{{2 */
static void
desktop_file_dir_indexed_init (DesktopFileDir *dir)
{
- const struct dfi_string_list *app_names;
- guint n_app_names, i;
+ desktop_key_init ();
+
+ /* Find the locale IDs to use */
+ {
+ const gchar * const * const *language_names;
+ const struct dfi_string_list *locale_names;
+ gint i, j, k, n;
+
+ /* From get_exploded_locale_list() we will get an array that looks
+ * something like so:
+ *
+ * [ ['eo', 'C'], ['de_CH', 'de', 'C'], ['fr_CA', 'fr', 'C'] ]
+ *
+ * We want to produce a list of IDs such each item in the list
+ * corresponds to the 'best match' for each item in the original
+ * list.
+ *
+ * For example, if the index has entries for 'eo', 'fr_CA' and 'de',
+ * but not for 'de_CH' then our created array would have the ID
+ * numbers corresponding to the following locales:
+ *
+ * [ eo, de, fr_CA ]
+ *
+ * In the event that the best match for a particular item in the
+ * list was the C locale, we simply exclude that item from being
+ * added. So for example, if 'eo' was not in the index, we would
+ * not add the 'C' locale, but would rather just have this list:
+ *
+ * [ de, fr_CA ]
+ *
+ * The C locale is always ID 0, so we use that as a terminator.
+ *
+ * As a minor nag, this will cause problems if "C" was explicitly
+ * listed among the items in LANGUAGE= but this would be a pretty
+ * silly thing to do, since lookups in C never fail and it's already
+ * the universal fallback...
+ */
+ locale_names = dfi_index_get_locale_names (dir->dfi);
+ language_names = get_exploded_locale_list ();
+ for (n = 0; language_names[n]; n++)
+ ;
+
+ /* Allocate an array under the assumption that we will fill it. If
+ * we don't, it's no big loss.
+ *
+ * Note: the search code assumes that locale_ids will always have at
+ * least two items, and that even if locale_ids[0] is zero,
+ * locale_ids[1] will also be zero.
+ *
+ * Do not modify this allocation in a way that would invalidate that
+ * assumption.
+ */
+ g_assert (n != 0);
+ dir->locale_ids = g_new0 (guint16, n + 1);
+ i = 0; /* We use 'i' as a moving index into locale_ids, as we add items */
+
+ /* Iterate over our locales, looking up the ID for each */
+ for (j = 0; language_names[j]; j++)
+ for (k = 0; language_names[j][k] && !g_str_equal (language_names[j][k], "C"); k++)
+ {
+ gint result = dfi_string_list_binary_search (locale_names, dir->dfi, language_names[j][k]);
- dir->app_names = g_hash_table_new (g_str_hash, g_str_equal);
- app_names = dfi_index_get_app_names (dir->dfi);
- n_app_names = dfi_string_list_get_length (app_names);
+ if (result >= 0)
+ {
+ dir->locale_ids[i++] = result;
+ break;
+ }
+ }
+ g_assert (i <= n);
+ }
+
+ /* Populate the app names list. */
+ {
+ const struct dfi_string_list *app_names;
+ guint n_app_names, i;
+
+ app_names = dfi_index_get_app_names (dir->dfi);
+ n_app_names = dfi_string_list_get_length (app_names);
+
+ /* Store the index of each application so we don't need to do binary
+ * searches to find applications.
+ */
+ dir->app_names = g_hash_table_new (g_str_hash, g_str_equal);
+
+ for (i = 0; i < n_app_names; i++)
+ g_hash_table_insert (dir->app_names,
+ (gchar *) dfi_string_list_get_string_at_index (app_names, dir->dfi, i),
+ GUINT_TO_POINTER (i));
+ }
+
+ /* Find the ID of the [Desktop Entry] group */
+ {
+ const struct dfi_string_list *group_names;
+
+ group_names = dfi_index_get_group_names (dir->dfi);
+
+ dir->desktop_entry_id = dfi_string_list_binary_search (group_names, dir->dfi, "Desktop Entry");
+ }
+
+ /* Populate the key id map */
+ {
+ const struct dfi_string_list *key_names;
+ guint n_key_names;
+ guint i = 0;
+ guint j = 1;
- for (i = 0; i < n_app_names; i++)
- g_hash_table_insert (dir->app_names,
- (gchar *) dfi_string_list_get_string_at_index (app_names, dir->dfi, i),
- GUINT_TO_POINTER (i));
+ key_names = dfi_index_get_key_names (dir->dfi);
+ n_key_names = dfi_string_list_get_length (key_names);
+
+ dir->key_id_map = g_malloc0 (n_key_names);
+ dir->key_id_map_length = n_key_names;
+
+ while (i < n_key_names && j < N_DESKTOP_KEYS)
+ {
+ const gchar *keyname = dfi_string_list_get_string_at_index (key_names, dir->dfi, i);
+ gint cmp;
+
+ cmp = strcmp (keyname, desktop_key_names[j]);
+
+ if (cmp == 0)
+ dir->key_id_map[i++] = j++;
+ else if (cmp < 0)
+ i++;
+ else
+ j++;
+ }
+ }
}
static GDesktopAppInfo *
@@ -571,6 +1080,100 @@ desktop_file_dir_indexed_get_all (DesktopFileDir *dir,
}
}
+static void
+desktop_file_dir_indexed_search_for_one_token_in_one_locale (DesktopFileDir *dir,
+ const gchar *search_token,
+ guint16 locale_id)
+{
+ const struct dfi_pointer_array *text_indexes;
+ const struct dfi_string_list *app_names;
+ const struct dfi_text_index *text_index;
+ const struct dfi_text_index_item *start;
+ const struct dfi_text_index_item *end;
+ const struct dfi_text_index_item *item;
+
+ text_indexes = dfi_index_get_text_indexes (dir->dfi);
+ text_index = dfi_text_index_from_pointer (dir->dfi, dfi_pointer_array_get_pointer (text_indexes,
locale_id));
+ if (text_index == NULL)
+ return;
+
+ dfi_text_index_prefix_search (text_index, dir->dfi, search_token, &start, &end);
+
+ app_names = dfi_index_get_app_names (dir->dfi);
+
+ for (item = start; item < end; item++)
+ {
+ const dfi_id *ids;
+ guint n_ids;
+
+ gint j;
+
+ ids = dfi_text_index_item_get_ids (item, dir->dfi, &n_ids);
+
+ if (n_ids % 3 != 0)
+ continue;
+
+ for (j = 0; j < n_ids; j += 3)
+ {
+ guint16 app_id, group_id, key_id;
+ const gchar *app_name;
+ guint8 match_category;
+
+ app_id = dfi_id_get (ids[j + 0]);
+ group_id = dfi_id_get (ids[j + 1]);
+ key_id = dfi_id_get (ids[j + 2]);
+
+ app_name = dfi_string_list_get_string_at_index (app_names, dir->dfi, app_id);
+ if (!app_name)
+ continue;
+
+ if (desktop_file_dir_app_name_is_masked (dir, app_name))
+ continue;
+
+ if (group_id != dir->desktop_entry_id)
+ continue;
+
+ if (key_id >= dir->key_id_map_length)
+ continue;
+
+ match_category = desktop_key_match_category[dir->key_id_map[key_id]];
+
+ if (!match_category)
+ continue;
+
+ add_token_result (app_name, match_category);
+ }
+ }
+}
+
+static void
+desktop_file_dir_indexed_search (DesktopFileDir *dir,
+ const gchar *search_token)
+{
+ gint i;
+
+ /* We search every locale...
+ *
+ * If the user lists off multiple languages in LANGUAGE then it's
+ * probably because they understand multiple languages.
+ *
+ * If a particular app is translated or not for a given locale is
+ * semi-random, so don't let the existence of a translation in a
+ * higher-priority language impact the ability to find an app by
+ * searching in the lower-priority language.
+ *
+ * Note: if the only available locale is C, then we want to allow
+ * searching there... This will be the case if locale_list[0] is 0,
+ * so we always permit i = 0 to loop.
+ *
+ * Note: locale_ids always has at least two items in it, and it is
+ * allocated zero-filled, so locale_ids[1] is safe, even if
+ * locale_ids[0] was 0.
+ */
+ for (i = 0; i == 0 || dir->locale_ids[i]; i++)
+ desktop_file_dir_indexed_search_for_one_token_in_one_locale (dir, search_token, dir->locale_ids[i]);
+}
+
/* DesktopFileDir "API" {{{2 */
/*< internal >
@@ -620,6 +1223,27 @@ desktop_file_dir_reset (DesktopFileDir *dir)
dir->dfi = NULL;
}
+ if (dir->key_id_map)
+ {
+ g_free (dir->key_id_map);
+ dir->key_id_map = NULL;
+ }
+
+ if (dir->locale_ids)
+ {
+ g_free (dir->locale_ids);
+ dir->locale_ids = NULL;
+ }
+
+ if (dir->memory_index)
+ {
+ g_hash_table_unref (dir->memory_index);
+ dir->memory_index = NULL;
+ }
+
+ dir->key_id_map_length = 0;
+ dir->desktop_entry_id = 0;
+
dir->is_setup = FALSE;
}
@@ -698,6 +1322,25 @@ desktop_file_dir_get_all (DesktopFileDir *dir,
desktop_file_dir_unindexed_get_all (dir, apps);
}
+#define N_SEARCH_BUCKETS 1
+
+/*< internal >
+ * desktop_file_dir_search:
+ * @dir: a #DesktopFilEDir
+ * @term: a normalised and casefolded search term
+ *
+ * Finds the names of applications in @dir that match @term.
+ */
+static void
+desktop_file_dir_search (DesktopFileDir *dir,
+ const gchar *search_token)
+{
+ if (dir->dfi)
+ desktop_file_dir_indexed_search (dir, search_token);
+ else
+ desktop_file_dir_unindexed_search (dir, search_token);
+}
+
/* Lock/unlock and global setup API {{{2 */
static void
@@ -3372,6 +4015,89 @@ g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
/* "Get all" API {{{2 */
/**
+ * g_desktop_app_info_search:
+ * @search_string: the search string to use
+ *
+ * Searches desktop files for ones that match @search_string.
+ *
+ * The return value is an array of strvs. Each strv contains a list of
+ * applications that matched @search_string with an equal score. The
+ * outer list is sorted by score so that the first strv contains the
+ * best-matching applications, and so on.
+ *
+ * Returns: (array zero-terminated=1) (element-type GStrv) (transfer full): a
+ * list of strvs. Free each item with g_strfreev() and free the outer
+ * list with g_free().
+ */
+gchar ***
+g_desktop_app_info_search (const gchar *search_string)
+{
+ gchar **search_tokens;
+ gint last_category = -1;
+ gchar ***results;
+ gint n_categories = 0;
+ gint start_of_category;
+ gint i, j;
+
+ search_tokens = g_str_tokenize_and_fold (search_string, NULL, NULL);
+
+ desktop_file_dirs_lock ();
+
+ reset_total_search_results ();
+
+ for (i = 0; i < n_desktop_file_dirs; i++)
+ {
+ for (j = 0; search_tokens[j]; j++)
+ {
+ desktop_file_dir_search (&desktop_file_dirs[i], search_tokens[j]);
+ merge_token_results (j == 0);
+ }
+ merge_directory_results ();
+ }
+
+ sort_total_search_results ();
+
+ /* Count the total number of unique categories */
+ for (i = 0; i < static_total_results_size; i++)
+ if (static_total_results[i].category != last_category)
+ {
+ last_category = static_total_results[i].category;
+ n_categories++;
+ }
+
+ results = g_new (gchar **, n_categories + 1);
+
+ /* Start loading into the results list */
+ start_of_category = 0;
+ for (i = 0; i < n_categories; i++)
+ {
+ gint n_items_in_category = 0;
+ gint this_category;
+ gint j;
+
+ this_category = static_total_results[start_of_category].category;
+
+ while (start_of_category + n_items_in_category < static_total_results_size &&
+ static_total_results[start_of_category + n_items_in_category].category == this_category)
+ n_items_in_category++;
+
+ results[i] = g_new (gchar *, n_items_in_category + 1);
+ for (j = 0; j < n_items_in_category; j++)
+ results[i][j] = g_strdup (static_total_results[start_of_category + j].app_name);
+ results[i][j] = NULL;
+
+ start_of_category += n_items_in_category;
+ }
+ results[i] = NULL;
+
+ desktop_file_dirs_unlock ();
+
+ g_strfreev (search_tokens);
+
+ return results;
+}
+
+/**
* g_app_info_get_all:
*
* Gets a list of all of the applications currently registered
diff --git a/gio/gdesktopappinfo.h b/gio/gdesktopappinfo.h
index 5f7f68a..dbf31db 100644
--- a/gio/gdesktopappinfo.h
+++ b/gio/gdesktopappinfo.h
@@ -164,6 +164,8 @@ gboolean g_desktop_app_info_launch_uris_as_manager (GDesktopAppInfo
gpointer pid_callback_data,
GError **error);
+GLIB_AVAILABLE_IN_2_40
+gchar *** g_desktop_app_info_search (const gchar *search_string);
G_END_DECLS
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]