[glib/wip/gdesktopappinfo: 13/13] Add g_desktop_app_info_search()
- From: Ryan Lortie <ryanl src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib/wip/gdesktopappinfo: 13/13] Add g_desktop_app_info_search()
- Date: Thu, 1 Aug 2013 09:06:27 +0000 (UTC)
commit 3d8e8844da28ba4d898a552621837620729feb6c
Author: Ryan Lortie <desrt desrt ca>
Date: Sun Jul 28 13:41:22 2013 -0400
Add g_desktop_app_info_search()
gio/gdesktopappinfo.c | 496 +++++++++++++++++++++++++++++++++++++++++++++++--
gio/gdesktopappinfo.h | 5 +
2 files changed, 481 insertions(+), 20 deletions(-)
---
diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c
index e56cdc7..eec7366 100644
--- a/gio/gdesktopappinfo.c
+++ b/gio/gdesktopappinfo.c
@@ -147,6 +147,8 @@ 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;
+/* DesktopFileDirIndex implementation {{{1 */
+
/* DesktopFileDir implementation {{{1 */
typedef struct
@@ -156,6 +158,10 @@ typedef struct
GHashTable *app_names;
gboolean is_setup;
struct dfi_index *dfi;
+ gint8 *key_id_map;
+ guint16 key_id_map_length;
+ guint16 this_locale_id;
+ guint16 desktop_entry_id;
} DesktopFileDir;
static DesktopFileDir *desktop_file_dirs;
@@ -335,39 +341,186 @@ desktop_file_dir_unindexed_get_all (DesktopFileDir *dir,
}
}
+static void
+desktop_file_dir_unindexed_search (DesktopFileDir *dir,
+ GSList *categories,
+ const gchar *term,
+ gint max_hits)
+{
+ /* TODO */
+}
+
/* Support for indexed DesktopFileDirs {{{2 */
-#if 0
enum
{
- DFI_ID_THIS_LOCALE,
-
- DFI_ID_Desktop_Entry,
+ DESKTOP_KEY_nil,
+
+ /* NB: must keep this list sorted */
+ DESKTOP_KEY_Actions,
+ DESKTOP_KEY_Categories,
+ DESKTOP_KEY_Comment,
+ DESKTOP_KEY_DBusActivatable,
+ DESKTOP_KEY_Exec,
+ DESKTOP_KEY_GenericName,
+ DESKTOP_KEY_Hidden,
+ DESKTOP_KEY_Icon,
+ DESKTOP_KEY_Keywords,
+ DESKTOP_KEY_MimeType,
+ DESKTOP_KEY_Name,
+ DESKTOP_KEY_NoDisplay,
+ DESKTOP_KEY_NotShowIn,
+ DESKTOP_KEY_OnlyShowIn,
+ DESKTOP_KEY_Path,
+ DESKTOP_KEY_StartupNotify,
+ DESKTOP_KEY_StartupWMClass,
+ DESKTOP_KEY_Terminal,
+ DESKTOP_KEY_TryExec,
+ DESKTOP_KEY_Type,
+ DESKTOP_KEY_Version,
+ DESKTOP_KEY_X_GNOME_FullName,
+
+ N_DESKTOP_KEYS
+};
- DFI_ID_Name,
- DFI_ID_Keywords,
- DFI_ID_GenericName,
- DFI_ID_X_GNOME_FullName,
- DFI_ID_Comment,
+const gchar *desktop_key_names[N_DESKTOP_KEYS];
- N_DFI_IDS
+const gchar desktop_key_match_category[N_DESKTOP_KEYS] = {
+ /* NB: no harm in repeating numbers in case of a tie */
+ [DESKTOP_KEY_Name] = 1,
+ [DESKTOP_KEY_GenericName] = 2,
+ [DESKTOP_KEY_Keywords] = 3,
+ [DESKTOP_KEY_X_GNOME_FullName] = 4,
+ [DESKTOP_KEY_Comment] = 5
};
-#endif
+
+#define DESKTOP_KEY_NUM_MATCH_CATEGORIES 5
+
+static void
+desktop_key_init (void)
+{
+ if G_LIKELY (desktop_key_names[1])
+ return;
+
+ desktop_key_names[DESKTOP_KEY_Actions] = g_intern_static_string ("Actions");
+ desktop_key_names[DESKTOP_KEY_Categories] = g_intern_static_string ("Categories");
+ desktop_key_names[DESKTOP_KEY_Comment] = g_intern_static_string ("Comment");
+ desktop_key_names[DESKTOP_KEY_DBusActivatable] = g_intern_static_string ("DBusActivatable");
+ desktop_key_names[DESKTOP_KEY_Exec] = g_intern_static_string ("Exec");
+ desktop_key_names[DESKTOP_KEY_GenericName] = g_intern_static_string ("GenericName");
+ desktop_key_names[DESKTOP_KEY_Hidden] = g_intern_static_string ("Hidden");
+ desktop_key_names[DESKTOP_KEY_Icon] = g_intern_static_string ("Icon");
+ desktop_key_names[DESKTOP_KEY_Keywords] = g_intern_static_string ("Keywords");
+ desktop_key_names[DESKTOP_KEY_MimeType] = g_intern_static_string ("MimeType");
+ desktop_key_names[DESKTOP_KEY_Name] = g_intern_static_string ("Name");
+ desktop_key_names[DESKTOP_KEY_NoDisplay] = g_intern_static_string ("NoDisplay");
+ desktop_key_names[DESKTOP_KEY_NotShowIn] = g_intern_static_string ("NotShowIn");
+ desktop_key_names[DESKTOP_KEY_OnlyShowIn] = g_intern_static_string ("OnlyShowIn");
+ desktop_key_names[DESKTOP_KEY_Path] = g_intern_static_string ("Path");
+ desktop_key_names[DESKTOP_KEY_StartupNotify] = g_intern_static_string ("StartupNotify");
+ desktop_key_names[DESKTOP_KEY_StartupWMClass] = g_intern_static_string ("StartupWMClass");
+ desktop_key_names[DESKTOP_KEY_Terminal] = g_intern_static_string ("Terminal");
+ desktop_key_names[DESKTOP_KEY_TryExec] = g_intern_static_string ("TryExec");
+ desktop_key_names[DESKTOP_KEY_Type] = g_intern_static_string ("Type");
+ desktop_key_names[DESKTOP_KEY_Version] = g_intern_static_string ("Version");
+ desktop_key_names[DESKTOP_KEY_X_GNOME_FullName] = g_intern_static_string ("X-GNOME-FullName");
+}
static void
desktop_file_dir_indexed_init (DesktopFileDir *dir)
{
- const struct dfi_string_list *app_names;
- guint n_app_names, i;
+ desktop_key_init ();
- 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);
+ /* Find the current locale */
+ {
+ const struct dfi_string_list *locale_names;
+ const gchar * const *language_names;
+ gint i;
+
+ locale_names = dfi_index_get_locale_names (dir->dfi);
+
+ language_names = g_get_language_names ();
+ locale_names = dfi_index_get_locale_names (dir->dfi);
+
+ /* If we don't get anything, the C locale is always zero, so set
+ * it as a default.
+ */
+ dir->this_locale_id = 0;
+
+ /* Iterate over our language names, in order of preference */
+ for (i = 0; language_names[i]; i++)
+ {
+ gint result = dfi_string_list_binary_search (locale_names, dir->dfi, language_names[i]);
+
+ if (result >= 0)
+ {
+ dir->this_locale_id = result;
+ break;
+ }
+ }
+
+ g_printerr ("found my locale is %d/%s\n", dir->this_locale_id, language_names[i]);
+ }
+
+ /* 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;
- 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));
+ 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;
+
+ 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)
+ {
+ g_printerr ("mapped keyname %s(%d) to %d\n", keyname, i, j);
+ dir->key_id_map[i++] = j++;
+ }
+ else if (cmp < 0)
+ i++;
+ else
+ j++;
+ }
+ }
}
static GDesktopAppInfo *
@@ -409,6 +562,177 @@ desktop_file_dir_indexed_get_all (DesktopFileDir *dir,
}
}
+static void
+insert_into_list (GSList *categories,
+ const gchar *item,
+ gint into_bucket,
+ gint max_hits)
+{
+ GSList *before_node;
+ GSList **ptr;
+ gint i = 0;
+
+ if (into_bucket < DESKTOP_KEY_NUM_MATCH_CATEGORIES - 1)
+ before_node = &categories[into_bucket + 1];
+ else
+ before_node = NULL;
+
+ for (ptr = &categories; *ptr != before_node; ptr = &(*ptr)->next)
+ {
+ GSList *node = *ptr;
+
+ if (node->data)
+ {
+ /* This item is already in the list, at higher priority.
+ *
+ * Note; pointer compare works for two reasons:
+ *
+ * - within a given index, a string only appears once
+ *
+ * - we use masking to prevent seeing the same application
+ * from multiple different directories
+ */
+ if (node->data == item)
+ return;
+
+ g_assert_cmpstr (node->data, !=, item);
+
+ /* Some other item. Count it. */
+ i++;
+ }
+
+ /* We already have enough higher-ranking hits */
+ if (i == max_hits)
+ return;
+ }
+
+ /* Add and count ourselves... */
+ *ptr = g_slist_prepend (*ptr, (gchar *) item);
+ ptr = &(*ptr)->next;
+ i++;
+
+ /* We may have to remove an item now:
+ *
+ * - if the item we inserted already comes later in the list, we must
+ * remove that one
+ *
+ * - if we went over max_hits, we need to remove some other item
+ *
+ * This loop will skip past non-duplicates until we hit the maximum
+ * number of items (in which case the next item, duplicate-or-not will
+ * be the one we remove).
+ */
+ while (i != max_hits)
+ {
+ GSList *node = *ptr;
+
+ /* End of list before we had enough hits */
+ if (node == NULL)
+ return;
+
+ if (node->data == item)
+ {
+ /* A later version of ourselves. Remove it. */
+ *ptr = g_slist_delete_link (node, node);
+
+ /* We added one item and removed one item, so we definitely
+ * didn't grow the list past the max. We can stop now.
+ */
+ return;
+ }
+
+ if (node->data)
+ i++;
+
+ ptr = &node->next;
+ }
+
+ /* Now we delete the first item we find (if there is one) */
+ while (*ptr)
+ {
+ GSList *node = *ptr;
+
+ if (node->data)
+ {
+ *ptr = g_slist_delete_link (node, node);
+ return;
+ }
+
+ ptr = &(*ptr)->next;
+ }
+}
+
+static void
+desktop_file_dir_indexed_search (DesktopFileDir *dir,
+ GSList *categories,
+ const gchar *term,
+ gint max_hits)
+{
+ 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,
dir->this_locale_id));
+ if (text_index == NULL)
+ return;
+
+ dfi_text_index_prefix_search (text_index, dir->dfi, term, &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);
+ //n_ids = 0;
+ //ids = dfi_text_index_get_ids_for_exact_match (dir->dfi, text_index, term, (gint *) &n_ids);
+ g_printerr ("n_ids is %u\n", 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;
+
+ insert_into_list (categories, app_name, match_category - 1, max_hits);
+ }
+ }
+}
+
/* DesktopFileDir "API" {{{2 */
/*< internal >
@@ -458,6 +782,16 @@ 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;
+ }
+
+ dir->key_id_map_length = 0;
+ dir->desktop_entry_id = 0;
+ dir->this_locale_id = 0;
+
dir->is_setup = FALSE;
}
@@ -536,6 +870,27 @@ 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,
+ GSList *categories,
+ const gchar *term,
+ gint max_hits)
+{
+ if (dir->dfi)
+ desktop_file_dir_indexed_search (dir, categories, term, max_hits);
+ else
+ desktop_file_dir_unindexed_search (dir, categories, term, max_hits);
+}
+
/* Lock/unlock and global setup API {{{2 */
static void
@@ -3202,6 +3557,107 @@ g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
/* "Get all" API {{{2 */
+gchar **
+g_desktop_app_info_search (const char *token,
+ gint max_hits)
+{
+ GSList categories[DESKTOP_KEY_NUM_MATCH_CATEGORIES];
+ GSList *node;
+ gchar **results;
+ gint i, n;
+
+ for (i = 0; i < DESKTOP_KEY_NUM_MATCH_CATEGORIES; i++)
+ {
+ categories[i].next = &categories[i + 1];
+ categories[i].data = NULL;
+ }
+ categories[DESKTOP_KEY_NUM_MATCH_CATEGORIES - 1].next = NULL;
+
+ desktop_file_dirs_lock ();
+
+ for (i = 0; i < n_desktop_file_dirs; i++)
+ desktop_file_dir_search (&desktop_file_dirs[i], categories, token, max_hits);
+
+ /* Must hold lock while processing results because otherwise strings
+ * could go missing out from under us.
+ */
+ node = &categories[0];
+ n = g_slist_length (categories) - DESKTOP_KEY_NUM_MATCH_CATEGORIES;
+ results = g_new (gchar *, n + 1);
+ i = 0;
+ while (node)
+ {
+ GSList *next = node->next;
+
+ if (node->data)
+ {
+ results[i++] = g_strdup (node->data);
+ g_slist_free_1 (node);
+ }
+
+ node = next;
+ }
+
+ g_assert_cmpint (i, ==, n);
+ results[i] = NULL;
+
+ desktop_file_dirs_unlock ();
+
+ return results;
+}
+
+gchar ***
+g_desktop_app_info_search_full (const gchar *token,
+ gint max_hits)
+{
+ GSList categories[DESKTOP_KEY_NUM_MATCH_CATEGORIES];
+ gchar ***results;
+ gint i, j;
+
+ for (i = 0; i < DESKTOP_KEY_NUM_MATCH_CATEGORIES; i++)
+ {
+ categories[i].next = &categories[i + 1];
+ categories[i].data = NULL;
+ }
+ categories[DESKTOP_KEY_NUM_MATCH_CATEGORIES - 1].next = NULL;
+
+ desktop_file_dirs_lock ();
+
+ for (i = 0; i < n_desktop_file_dirs; i++)
+ desktop_file_dir_search (&desktop_file_dirs[i], categories, token, max_hits);
+
+ /* As above, must hold the lock. */
+ results = g_new (gchar **, DESKTOP_KEY_NUM_MATCH_CATEGORIES + 1);
+ j = 0;
+
+ for (i = 0; i < DESKTOP_KEY_NUM_MATCH_CATEGORIES; i++)
+ {
+ gchar **list;
+ GSList *node;
+ gint n;
+
+ n = 0;
+ for (node = categories[i].next; node && node->data; node = node->next)
+ n++;
+
+ if (n == 0)
+ continue;
+
+ list = g_new (gchar *, n + 1);
+ n = 0;
+ for (node = categories[i].next; node && node->data; node = node->next)
+ list[n++] = g_strdup (node->data);
+ list[n] = NULL;
+
+ results[j++] = list;
+ }
+ results[j] = NULL;
+
+ desktop_file_dirs_unlock ();
+
+ return results;
+}
+
/**
* g_app_info_get_all:
*
diff --git a/gio/gdesktopappinfo.h b/gio/gdesktopappinfo.h
index 5f7f68a..749dfb1 100644
--- a/gio/gdesktopappinfo.h
+++ b/gio/gdesktopappinfo.h
@@ -165,6 +165,11 @@ gboolean g_desktop_app_info_launch_uris_as_manager (GDesktopAppInfo
GError **error);
+GLIB_AVAILABLE_IN_2_30
+gchar ** g_desktop_app_info_search (const gchar *term, gint max_hits);
+GLIB_AVAILABLE_IN_2_30
+gchar *** g_desktop_app_info_search_full (const gchar *term, gint max_hits);
+
G_END_DECLS
#endif /* __G_DESKTOP_APP_INFO_H__ */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]