summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libcaja-private/caja-query.c51
-rw-r--r--libcaja-private/caja-query.h4
-rw-r--r--libcaja-private/caja-search-engine-simple.c159
-rw-r--r--src/caja-query-editor.c123
4 files changed, 328 insertions, 9 deletions
diff --git a/libcaja-private/caja-query.c b/libcaja-private/caja-query.c
index e0359a04..a5e783e4 100644
--- a/libcaja-private/caja-query.c
+++ b/libcaja-private/caja-query.c
@@ -35,6 +35,7 @@ struct CajaQueryDetails
char *text;
char *location_uri;
GList *mime_types;
+ GList *tags;
};
static void caja_query_class_init (CajaQueryClass *class);
@@ -129,6 +130,29 @@ caja_query_add_mime_type (CajaQuery *query, const char *mime_type)
g_strdup (mime_type));
}
+GList *
+caja_query_get_tags (CajaQuery *query)
+{
+ return eel_g_str_list_copy (query->details->tags);
+}
+
+void
+caja_query_set_tags (CajaQuery *query, GList *tags)
+{
+ g_list_free_full (query->details->tags, g_free);
+ query->details->tags = eel_g_str_list_copy (tags);
+}
+
+void
+caja_query_add_tag (CajaQuery *query, const char *tag)
+{
+ gchar *normalized = g_utf8_normalize (tag, -1, G_NORMALIZE_NFD);
+ gchar *lower_case = g_utf8_strdown (normalized, -1);
+
+ g_free (normalized);
+ query->details->tags = g_list_append (query->details->tags, lower_case);
+}
+
char *
caja_query_to_readable_string (CajaQuery *query)
{
@@ -196,6 +220,8 @@ typedef struct
gboolean in_location;
gboolean in_mimetypes;
gboolean in_mimetype;
+ gboolean in_tags;
+ gboolean in_tag;
} ParserInfo;
static void
@@ -218,6 +244,10 @@ start_element_cb (GMarkupParseContext *ctx,
info->in_mimetypes = TRUE;
else if (strcmp (element_name, "mimetype") == 0)
info->in_mimetype = TRUE;
+ else if (strcmp (element_name, "tags") == 0)
+ info->in_tags = TRUE;
+ else if (strcmp (element_name, "tag") == 0)
+ info->in_tag = TRUE;
}
static void
@@ -238,6 +268,10 @@ end_element_cb (GMarkupParseContext *ctx,
info->in_mimetypes = FALSE;
else if (strcmp (element_name, "mimetype") == 0)
info->in_mimetype = FALSE;
+ else if (strcmp (element_name, "tags") == 0)
+ info->in_tags = FALSE;
+ else if (strcmp (element_name, "tag") == 0)
+ info->in_tag = FALSE;
}
static void
@@ -268,6 +302,10 @@ text_cb (GMarkupParseContext *ctx,
{
caja_query_add_mime_type (info->query, t);
}
+ else if (info->in_tags && info->in_tag)
+ {
+ caja_query_add_tag (info->query, t);
+ }
g_free (t);
@@ -339,6 +377,7 @@ caja_query_to_xml (CajaQuery *query)
char *text;
char *uri;
char *mimetype;
+ char *tag;
GList *l;
xml = g_string_new ("");
@@ -369,6 +408,18 @@ caja_query_to_xml (CajaQuery *query)
g_string_append (xml, " </mimetypes>\n");
}
+ if (query->details->tags)
+ {
+ g_string_append (xml, " <tags>\n");
+ for (l = query->details->tags; l != NULL; l = l->next)
+ {
+ tag = g_markup_escape_text (l->data, -1);
+ g_string_append_printf (xml, " <tag>%s</tag>\n", tag);
+ g_free (tag);
+ }
+ g_string_append (xml, " </tags>\n");
+ }
+
g_string_append (xml, "</query>\n");
return g_string_free (xml, FALSE);
diff --git a/libcaja-private/caja-query.h b/libcaja-private/caja-query.h
index a13c930e..8e0f1047 100644
--- a/libcaja-private/caja-query.h
+++ b/libcaja-private/caja-query.h
@@ -57,6 +57,10 @@ void caja_query_set_text (CajaQuery *query, const char *text
char * caja_query_get_location (CajaQuery *query);
void caja_query_set_location (CajaQuery *query, const char *uri);
+GList * caja_query_get_tags (CajaQuery *query);
+void caja_query_set_tags (CajaQuery *query, GList *tags);
+void caja_query_add_tag (CajaQuery *query, const char *tag);
+
GList * caja_query_get_mime_types (CajaQuery *query);
void caja_query_set_mime_types (CajaQuery *query, GList *mime_types);
void caja_query_add_mime_type (CajaQuery *query, const char *mime_type);
diff --git a/libcaja-private/caja-search-engine-simple.c b/libcaja-private/caja-search-engine-simple.c
index e4078b36..016b03e6 100644
--- a/libcaja-private/caja-search-engine-simple.c
+++ b/libcaja-private/caja-search-engine-simple.c
@@ -38,6 +38,7 @@ typedef struct
GCancellable *cancellable;
GList *mime_types;
+ GList *tags;
char **words;
GList *found_list;
@@ -121,6 +122,7 @@ search_thread_data_new (CajaSearchEngineSimple *engine,
g_free (lower);
g_free (normalized);
+ data->tags = caja_query_get_tags (query);
data->mime_types = caja_query_get_mime_types (query);
data->cancellable = g_cancellable_new ();
@@ -137,6 +139,7 @@ search_thread_data_free (SearchThreadData *data)
g_hash_table_destroy (data->visited);
g_object_unref (data->cancellable);
g_strfreev (data->words);
+ g_list_free_full (data->tags, g_free);
g_list_free_full (data->mime_types, g_free);
g_list_free_full (data->uri_hits, g_free);
g_free (data);
@@ -203,6 +206,8 @@ send_batch (SearchThreadData *data)
data->uri_hits = NULL;
}
+#define G_FILE_ATTRIBUTE_XATTR_XDG_TAGS "xattr::xdg.tags"
+
#define STD_ATTRIBUTES \
G_FILE_ATTRIBUTE_STANDARD_NAME "," \
G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," \
@@ -210,13 +215,131 @@ send_batch (SearchThreadData *data)
G_FILE_ATTRIBUTE_STANDARD_TYPE "," \
G_FILE_ATTRIBUTE_ID_FILE
+
+/* Stolen code
+ * file: glocalfileinfo.c
+ * function: hex_unescape_string
+ * GIO - GLib Input, Output and Streaming Library
+ */
+static char *
+hex_unescape_string (const char *str,
+ int *out_len,
+ gboolean *free_return)
+{
+ int i;
+ char *unescaped_str, *p;
+ unsigned char c;
+ int len;
+
+ len = strlen (str);
+
+ if (strchr (str, '\\') == NULL)
+ {
+ if (out_len)
+ *out_len = len;
+ *free_return = FALSE;
+ return (char *)str;
+ }
+
+ unescaped_str = g_malloc (len + 1);
+
+ p = unescaped_str;
+ for (i = 0; i < len; i++)
+ {
+ if (str[i] == '\\' &&
+ str[i+1] == 'x' &&
+ len - i >= 4)
+ {
+ c =
+ (g_ascii_xdigit_value (str[i+2]) << 4) |
+ g_ascii_xdigit_value (str[i+3]);
+ *p++ = c;
+ i += 3;
+ }
+ else
+ *p++ = str[i];
+ }
+ *p++ = 0;
+
+ if (out_len)
+ *out_len = p - unescaped_str;
+ *free_return = TRUE;
+ return unescaped_str;
+}
+/* End of stolen code */
+
+static inline gchar **
+get_tags_from_info (GFileInfo *info)
+{
+ char **result;
+ const gchar *escaped_tags_string
+ = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_XATTR_XDG_TAGS);
+
+ gboolean new_created;
+ gchar *tags_string = hex_unescape_string (escaped_tags_string,
+ NULL,
+ &new_created);
+
+ gchar *normalized = g_utf8_normalize (tags_string, -1, G_NORMALIZE_NFD);
+
+ if (new_created)
+ g_free (tags_string);
+
+ gchar *lower_case = g_utf8_strdown (normalized, -1);
+ g_free (normalized);
+
+ result = g_strsplit (lower_case, ",", -1);
+ g_free (lower_case);
+
+ return result;
+}
+
+static inline gboolean
+file_has_all_tags (GFileInfo *info, GList *tags)
+{
+ if (g_list_length (tags) == 0) {
+ return TRUE;
+ }
+
+ if (!g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_XATTR_XDG_TAGS))
+ {
+ return FALSE;
+ }
+
+ char **file_tags = get_tags_from_info (info);
+
+ guint file_tags_len = g_strv_length (file_tags);
+ if (file_tags_len < g_list_length (tags)) {
+ g_strfreev (file_tags);
+ return FALSE;
+ }
+
+ for (GList *l = tags; l != NULL; l = l->next) {
+ gboolean found = FALSE;
+ for (int i = 0; i < file_tags_len; ++i) {
+ if (g_strcmp0 (l->data, file_tags[i]) == 0) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (found == FALSE) {
+ g_strfreev (file_tags);
+ return FALSE;
+ }
+ }
+
+ g_strfreev (file_tags);
+ return TRUE;
+}
+
static void
visit_directory (GFile *dir, SearchThreadData *data)
{
GFileEnumerator *enumerator;
GFileInfo *info;
GFile *child;
- const char *mime_type, *display_name;
+ const char *tag, *mime_type, *display_name;
char *lower_name, *normalized;
gboolean hit;
int i;
@@ -224,14 +347,27 @@ visit_directory (GFile *dir, SearchThreadData *data)
const char *id;
gboolean visited;
- enumerator = g_file_enumerate_children (dir,
- data->mime_types != NULL ?
- STD_ATTRIBUTES ","
- G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
- :
- STD_ATTRIBUTES
- ,
- 0, data->cancellable, NULL);
+ const char *attributes;
+ if (data->mime_types != NULL) {
+ if (data->tags != NULL) {
+ attributes = STD_ATTRIBUTES ","
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
+ G_FILE_ATTRIBUTE_XATTR_XDG_TAGS;
+ } else {
+ attributes = STD_ATTRIBUTES ","
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE;
+ }
+ } else {
+ if (data->tags != NULL) {
+ attributes = STD_ATTRIBUTES ","
+ G_FILE_ATTRIBUTE_XATTR_XDG_TAGS;
+ } else {
+ attributes = STD_ATTRIBUTES;
+ }
+ }
+
+ enumerator = g_file_enumerate_children (dir, attributes, 0,
+ data->cancellable, NULL);
if (enumerator == NULL)
{
@@ -281,6 +417,11 @@ visit_directory (GFile *dir, SearchThreadData *data)
}
}
+ if (hit && data->tags)
+ {
+ hit = file_has_all_tags (info, data->tags);
+ }
+
child = g_file_get_child (dir, g_file_info_get_name (info));
if (hit)
diff --git a/src/caja-query-editor.c b/src/caja-query-editor.c
index 8319db25..5bfbf7ae 100644
--- a/src/caja-query-editor.c
+++ b/src/caja-query-editor.c
@@ -38,6 +38,7 @@ typedef enum
{
CAJA_QUERY_EDITOR_ROW_LOCATION,
CAJA_QUERY_EDITOR_ROW_TYPE,
+ CAJA_QUERY_EDITOR_ROW_TAGS,
CAJA_QUERY_EDITOR_ROW_LAST
} CajaQueryEditorRowType;
@@ -95,6 +96,8 @@ static guint signals[LAST_SIGNAL] = { 0 };
static void caja_query_editor_class_init (CajaQueryEditorClass *class);
static void caja_query_editor_init (CajaQueryEditor *editor);
+static void go_search_cb (GtkButton *clicked_button, CajaQueryEditor *editor);
+
static void entry_activate_cb (GtkWidget *entry, CajaQueryEditor *editor);
static void entry_changed_cb (GtkWidget *entry, CajaQueryEditor *editor);
static void caja_query_editor_changed_force (CajaQueryEditor *editor,
@@ -109,6 +112,14 @@ static void location_row_add_to_query (CajaQueryEditorRow *row,
static void location_row_free_data (CajaQueryEditorRow *row);
static void location_add_rows_from_query (CajaQueryEditor *editor,
CajaQuery *query);
+
+static GtkWidget *tags_row_create_widgets (CajaQueryEditorRow *row);
+static void tags_row_add_to_query (CajaQueryEditorRow *row,
+ CajaQuery *query);
+static void tags_row_free_data (CajaQueryEditorRow *row);
+static void tags_add_rows_from_query (CajaQueryEditor *editor,
+ CajaQuery *query);
+
static GtkWidget *type_row_create_widgets (CajaQueryEditorRow *row);
static void type_row_add_to_query (CajaQueryEditorRow *row,
CajaQuery *query);
@@ -134,6 +145,13 @@ static CajaQueryEditorRowOps row_type[] =
type_row_free_data,
type_add_rows_from_query
},
+ {
+ N_("Tags"),
+ tags_row_create_widgets,
+ tags_row_add_to_query,
+ tags_row_free_data,
+ tags_add_rows_from_query
+ }
};
EEL_CLASS_BOILERPLATE (CajaQueryEditor,
@@ -344,6 +362,111 @@ location_add_rows_from_query (CajaQueryEditor *editor,
g_free (folder);
}
+/* Tags */
+static void
+tags_entry_changed_cb (GtkWidget *entry, gpointer *data)
+{
+ /* remove commas from string */
+ const gchar *text = gtk_entry_get_text ( GTK_ENTRY (entry));
+ if (g_strrstr (text, ",") == NULL) {
+ return;
+ }
+
+ gchar **words = g_strsplit (text, ",", -1);
+ gchar *sanitized = g_strjoinv ("", words);
+ g_strfreev (words);
+
+ gtk_entry_set_text (GTK_ENTRY (entry), sanitized);
+ g_free(sanitized);
+}
+
+#define MAX_TAGS_ENTRY_LEN 4096 // arbitrary value.
+
+static GtkWidget *
+tags_row_create_widgets (CajaQueryEditorRow *row)
+{
+ GtkWidget *entry = gtk_entry_new();
+ gtk_entry_set_max_length (GTK_ENTRY (entry), MAX_TAGS_ENTRY_LEN);
+ gtk_widget_set_tooltip_text (entry,
+ _("Tags separated by spaces. "
+ "Matches files that contains ALL specified tags."));
+
+ gtk_entry_set_placeholder_text (GTK_ENTRY (entry),
+ _("Tags separated by spaces. "
+ "Matches files that contains ALL specified tags."));
+
+ gtk_widget_show (entry);
+ gtk_box_pack_start (GTK_BOX (row->hbox), entry, TRUE, TRUE, 0);
+ g_signal_connect (entry, "changed", G_CALLBACK (tags_entry_changed_cb), entry);
+ g_signal_connect (entry, "activate", G_CALLBACK (go_search_cb), row->editor);
+
+ return entry;
+}
+
+static void
+tags_row_add_to_query (CajaQueryEditorRow *row,
+ CajaQuery *query)
+{
+ GtkEntry *entry = GTK_ENTRY (row->type_widget);
+ const gchar *tags = gtk_entry_get_text (entry);
+
+ char **strv = g_strsplit (tags, " ", -1);
+ guint len = g_strv_length (strv);
+ for (int i=0; i<len; ++i) {
+ strv[i] = g_strstrip (strv[i]);
+ if (strlen (strv[i]) > 0) {
+ caja_query_add_tag (query, strv[i]);
+ }
+ }
+ g_strfreev (strv);
+}
+
+static void
+tags_row_free_data (CajaQueryEditorRow *row)
+{
+}
+
+gchar *
+xattr_tags_list_to_str (const GList *tags)
+{
+ gchar *result = NULL;
+
+ const GList *tags_iter = NULL;
+ for (tags_iter = tags; tags_iter; tags_iter = tags_iter->next) {
+ gchar *tmp;
+
+ if (result != NULL) {
+ tmp = g_strconcat (result, ",", tags_iter->data, NULL);
+ g_free (result);
+ } else {
+ tmp = g_strdup (tags_iter->data);
+ }
+
+ result = tmp;
+ }
+
+ return result;
+}
+
+static void
+tags_add_rows_from_query (CajaQueryEditor *editor,
+ CajaQuery *query)
+{
+ GList *tags = caja_query_get_tags (query);
+ if (tags == NULL) {
+ return;
+ }
+
+ CajaQueryEditorRow *row;
+ row = caja_query_editor_add_row (editor, CAJA_QUERY_EDITOR_ROW_TAGS);
+
+ const gchar *tags_str = xattr_tags_list_to_str (tags);
+ g_list_free_full (tags, g_free);
+
+ gtk_entry_set_text (GTK_ENTRY (row->type_widget), tags_str);
+ g_free (tags_str);
+}
+
/* Type */