diff options
| author | Felipe Barriga Richards <[email protected]> | 2017-03-07 00:53:00 -0300 | 
|---|---|---|
| committer | lukefromdc <[email protected]> | 2017-03-23 14:38:01 -0400 | 
| commit | 96fdd4f17237dc55be5e2a9cde5a20a23ce53086 (patch) | |
| tree | cee0f8ae2400dd1feed10c40461c7dc99bc0b57a | |
| parent | 8eb3d9d5452eb42634f27de59178e64da1e8f86f (diff) | |
| download | caja-96fdd4f17237dc55be5e2a9cde5a20a23ce53086.tar.bz2 caja-96fdd4f17237dc55be5e2a9cde5a20a23ce53086.tar.xz  | |
search: added tag support (xattr::xdg.tags).
| -rw-r--r-- | libcaja-private/caja-query.c | 51 | ||||
| -rw-r--r-- | libcaja-private/caja-query.h | 4 | ||||
| -rw-r--r-- | libcaja-private/caja-search-engine-simple.c | 159 | ||||
| -rw-r--r-- | src/caja-query-editor.c | 123 | 
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 */  | 
