Home | History | Annotate | Download | only in gio
      1 /* GIO - GLib Input, Output and Streaming Library
      2  *
      3  * Copyright (C) 2006-2007 Red Hat, Inc.
      4  *
      5  * This library is free software; you can redistribute it and/or
      6  * modify it under the terms of the GNU Lesser General Public
      7  * License as published by the Free Software Foundation; either
      8  * version 2 of the License, or (at your option) any later version.
      9  *
     10  * This library is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13  * Lesser General Public License for more details.
     14  *
     15  * You should have received a copy of the GNU Lesser General
     16  * Public License along with this library; if not, write to the
     17  * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
     18  * Boston, MA 02111-1307, USA.
     19  *
     20  * Author: Alexander Larsson <alexl (at) redhat.com>
     21  */
     22 
     23 #include "config.h"
     24 #include "gfilenamecompleter.h"
     25 #include "gfileenumerator.h"
     26 #include "gfileattribute.h"
     27 #include "gfile.h"
     28 #include "gfileinfo.h"
     29 #include "gcancellable.h"
     30 #include <string.h>
     31 #include "glibintl.h"
     32 
     33 #include "gioalias.h"
     34 
     35 /**
     36  * SECTION:gfilenamecompleter
     37  * @short_description: Filename Completer
     38  * @include: gio/gio.h
     39  *
     40  * Completes partial file and directory names given a partial string by
     41  * looking in the file system for clues. Can return a list of possible
     42  * completion strings for widget implementations.
     43  *
     44  **/
     45 
     46 enum {
     47   GOT_COMPLETION_DATA,
     48   LAST_SIGNAL
     49 };
     50 
     51 static guint signals[LAST_SIGNAL] = { 0 };
     52 
     53 typedef struct {
     54   GFilenameCompleter *completer;
     55   GFileEnumerator *enumerator;
     56   GCancellable *cancellable;
     57   gboolean should_escape;
     58   GFile *dir;
     59   GList *basenames;
     60   gboolean dirs_only;
     61 } LoadBasenamesData;
     62 
     63 struct _GFilenameCompleter {
     64   GObject parent;
     65 
     66   GFile *basenames_dir;
     67   gboolean basenames_are_escaped;
     68   GList *basenames;
     69   gboolean dirs_only;
     70 
     71   LoadBasenamesData *basename_loader;
     72 };
     73 
     74 G_DEFINE_TYPE (GFilenameCompleter, g_filename_completer, G_TYPE_OBJECT);
     75 
     76 static void cancel_load_basenames (GFilenameCompleter *completer);
     77 
     78 static void
     79 g_filename_completer_finalize (GObject *object)
     80 {
     81   GFilenameCompleter *completer;
     82 
     83   completer = G_FILENAME_COMPLETER (object);
     84 
     85   cancel_load_basenames (completer);
     86 
     87   if (completer->basenames_dir)
     88     g_object_unref (completer->basenames_dir);
     89 
     90   g_list_foreach (completer->basenames, (GFunc)g_free, NULL);
     91   g_list_free (completer->basenames);
     92 
     93   G_OBJECT_CLASS (g_filename_completer_parent_class)->finalize (object);
     94 }
     95 
     96 static void
     97 g_filename_completer_class_init (GFilenameCompleterClass *klass)
     98 {
     99   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
    100 
    101   gobject_class->finalize = g_filename_completer_finalize;
    102   /**
    103    * GFilenameCompleter::got-completion-data:
    104    *
    105    * Emitted when the file name completion information comes available.
    106    **/
    107   signals[GOT_COMPLETION_DATA] = g_signal_new (I_("got-completion-data"),
    108 					  G_TYPE_FILENAME_COMPLETER,
    109 					  G_SIGNAL_RUN_LAST,
    110 					  G_STRUCT_OFFSET (GFilenameCompleterClass, got_completion_data),
    111 					  NULL, NULL,
    112 					  g_cclosure_marshal_VOID__VOID,
    113 					  G_TYPE_NONE, 0);
    114 }
    115 
    116 static void
    117 g_filename_completer_init (GFilenameCompleter *completer)
    118 {
    119 }
    120 
    121 /**
    122  * g_filename_completer_new:
    123  *
    124  * Creates a new filename completer.
    125  *
    126  * Returns: a #GFilenameCompleter.
    127  **/
    128 GFilenameCompleter *
    129 g_filename_completer_new (void)
    130 {
    131   return g_object_new (G_TYPE_FILENAME_COMPLETER, NULL);
    132 }
    133 
    134 static char *
    135 longest_common_prefix (char *a, char *b)
    136 {
    137   char *start;
    138 
    139   start = a;
    140 
    141   while (g_utf8_get_char (a) == g_utf8_get_char (b))
    142     {
    143       a = g_utf8_next_char (a);
    144       b = g_utf8_next_char (b);
    145     }
    146 
    147   return g_strndup (start, a - start);
    148 }
    149 
    150 static void
    151 load_basenames_data_free (LoadBasenamesData *data)
    152 {
    153   if (data->enumerator)
    154     g_object_unref (data->enumerator);
    155 
    156   g_object_unref (data->cancellable);
    157   g_object_unref (data->dir);
    158 
    159   g_list_foreach (data->basenames, (GFunc)g_free, NULL);
    160   g_list_free (data->basenames);
    161 
    162   g_free (data);
    163 }
    164 
    165 static void
    166 got_more_files (GObject *source_object,
    167 		GAsyncResult *res,
    168 		gpointer user_data)
    169 {
    170   LoadBasenamesData *data = user_data;
    171   GList *infos, *l;
    172   GFileInfo *info;
    173   const char *name;
    174   gboolean append_slash;
    175   char *t;
    176   char *basename;
    177 
    178   if (data->completer == NULL)
    179     {
    180       /* Was cancelled */
    181       load_basenames_data_free (data);
    182       return;
    183     }
    184 
    185   infos = g_file_enumerator_next_files_finish (data->enumerator, res, NULL);
    186 
    187   for (l = infos; l != NULL; l = l->next)
    188     {
    189       info = l->data;
    190 
    191       if (data->dirs_only &&
    192 	  g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
    193 	{
    194 	  g_object_unref (info);
    195 	  continue;
    196 	}
    197 
    198       append_slash = g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY;
    199       name = g_file_info_get_name (info);
    200       if (name == NULL)
    201 	{
    202 	  g_object_unref (info);
    203 	  continue;
    204 	}
    205 
    206 
    207       if (data->should_escape)
    208 	basename = g_uri_escape_string (name,
    209 					G_URI_RESERVED_CHARS_ALLOWED_IN_PATH,
    210 					TRUE);
    211       else
    212 	/* If not should_escape, must be a local filename, convert to utf8 */
    213 	basename = g_filename_to_utf8 (name, -1, NULL, NULL, NULL);
    214 
    215       if (basename)
    216 	{
    217 	  if (append_slash)
    218 	    {
    219 	      t = basename;
    220 	      basename = g_strconcat (basename, "/", NULL);
    221 	      g_free (t);
    222 	    }
    223 
    224 	  data->basenames = g_list_prepend (data->basenames, basename);
    225 	}
    226 
    227       g_object_unref (info);
    228     }
    229 
    230   g_list_free (infos);
    231 
    232   if (infos)
    233     {
    234       /* Not last, get more files */
    235       g_file_enumerator_next_files_async (data->enumerator,
    236 					  100,
    237 					  0,
    238 					  data->cancellable,
    239 					  got_more_files, data);
    240     }
    241   else
    242     {
    243       data->completer->basename_loader = NULL;
    244 
    245       if (data->completer->basenames_dir)
    246 	g_object_unref (data->completer->basenames_dir);
    247       g_list_foreach (data->completer->basenames, (GFunc)g_free, NULL);
    248       g_list_free (data->completer->basenames);
    249 
    250       data->completer->basenames_dir = g_object_ref (data->dir);
    251       data->completer->basenames = data->basenames;
    252       data->completer->basenames_are_escaped = data->should_escape;
    253       data->basenames = NULL;
    254 
    255       g_file_enumerator_close_async (data->enumerator, 0, NULL, NULL, NULL);
    256 
    257       g_signal_emit (data->completer, signals[GOT_COMPLETION_DATA], 0);
    258       load_basenames_data_free (data);
    259     }
    260 }
    261 
    262 
    263 static void
    264 got_enum (GObject *source_object,
    265 	  GAsyncResult *res,
    266 	  gpointer user_data)
    267 {
    268   LoadBasenamesData *data = user_data;
    269 
    270   if (data->completer == NULL)
    271     {
    272       /* Was cancelled */
    273       load_basenames_data_free (data);
    274       return;
    275     }
    276 
    277   data->enumerator = g_file_enumerate_children_finish (G_FILE (source_object), res, NULL);
    278 
    279   if (data->enumerator == NULL)
    280     {
    281       data->completer->basename_loader = NULL;
    282 
    283       if (data->completer->basenames_dir)
    284 	g_object_unref (data->completer->basenames_dir);
    285       g_list_foreach (data->completer->basenames, (GFunc)g_free, NULL);
    286       g_list_free (data->completer->basenames);
    287 
    288       /* Mark uptodate with no basenames */
    289       data->completer->basenames_dir = g_object_ref (data->dir);
    290       data->completer->basenames = NULL;
    291       data->completer->basenames_are_escaped = data->should_escape;
    292 
    293       load_basenames_data_free (data);
    294       return;
    295     }
    296 
    297   g_file_enumerator_next_files_async (data->enumerator,
    298 				      100,
    299 				      0,
    300 				      data->cancellable,
    301 				      got_more_files, data);
    302 }
    303 
    304 static void
    305 schedule_load_basenames (GFilenameCompleter *completer,
    306 			 GFile *dir,
    307 			 gboolean should_escape)
    308 {
    309   LoadBasenamesData *data;
    310 
    311   cancel_load_basenames (completer);
    312 
    313   data = g_new0 (LoadBasenamesData, 1);
    314   data->completer = completer;
    315   data->cancellable = g_cancellable_new ();
    316   data->dir = g_object_ref (dir);
    317   data->should_escape = should_escape;
    318   data->dirs_only = completer->dirs_only;
    319 
    320   completer->basename_loader = data;
    321 
    322   g_file_enumerate_children_async (dir,
    323 				   G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE,
    324 				   0, 0,
    325 				   data->cancellable,
    326 				   got_enum, data);
    327 }
    328 
    329 static void
    330 cancel_load_basenames (GFilenameCompleter *completer)
    331 {
    332   LoadBasenamesData *loader;
    333 
    334   if (completer->basename_loader)
    335     {
    336       loader = completer->basename_loader;
    337       loader->completer = NULL;
    338 
    339       g_cancellable_cancel (loader->cancellable);
    340 
    341       completer->basename_loader = NULL;
    342     }
    343 }
    344 
    345 
    346 /* Returns a list of possible matches and the basename to use for it */
    347 static GList *
    348 init_completion (GFilenameCompleter *completer,
    349 		 const char *initial_text,
    350 		 char **basename_out)
    351 {
    352   gboolean should_escape;
    353   GFile *file, *parent;
    354   char *basename;
    355   char *t;
    356   int len;
    357 
    358   *basename_out = NULL;
    359 
    360   should_escape = ! (g_path_is_absolute (initial_text) || *initial_text == '~');
    361 
    362   len = strlen (initial_text);
    363 
    364   if (len > 0 &&
    365       initial_text[len - 1] == '/')
    366     return NULL;
    367 
    368   file = g_file_parse_name (initial_text);
    369   parent = g_file_get_parent (file);
    370   if (parent == NULL)
    371     {
    372       g_object_unref (file);
    373       return NULL;
    374     }
    375 
    376   if (completer->basenames_dir == NULL ||
    377       completer->basenames_are_escaped != should_escape ||
    378       !g_file_equal (parent, completer->basenames_dir))
    379     {
    380       schedule_load_basenames (completer, parent, should_escape);
    381       g_object_unref (file);
    382       return NULL;
    383     }
    384 
    385   basename = g_file_get_basename (file);
    386   if (should_escape)
    387     {
    388       t = basename;
    389       basename = g_uri_escape_string (basename, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
    390       g_free (t);
    391     }
    392   else
    393     {
    394       t = basename;
    395       basename = g_filename_to_utf8 (basename, -1, NULL, NULL, NULL);
    396       g_free (t);
    397 
    398       if (basename == NULL)
    399 	return NULL;
    400     }
    401 
    402   *basename_out = basename;
    403 
    404   return completer->basenames;
    405 }
    406 
    407 /**
    408  * g_filename_completer_get_completion_suffix:
    409  * @completer: the filename completer.
    410  * @initial_text: text to be completed.
    411  *
    412  * Obtains a completion for @initial_text from @completer.
    413  *
    414  * Returns: a completed string, or %NULL if no completion exists.
    415  *     This string is not owned by GIO, so remember to g_free() it
    416  *     when finished.
    417  **/
    418 char *
    419 g_filename_completer_get_completion_suffix (GFilenameCompleter *completer,
    420 					    const char *initial_text)
    421 {
    422   GList *possible_matches, *l;
    423   char *prefix;
    424   char *suffix;
    425   char *possible_match;
    426   char *lcp;
    427 
    428   g_return_val_if_fail (G_IS_FILENAME_COMPLETER (completer), NULL);
    429   g_return_val_if_fail (initial_text != NULL, NULL);
    430 
    431   possible_matches = init_completion (completer, initial_text, &prefix);
    432 
    433   suffix = NULL;
    434 
    435   for (l = possible_matches; l != NULL; l = l->next)
    436     {
    437       possible_match = l->data;
    438 
    439       if (g_str_has_prefix (possible_match, prefix))
    440 	{
    441 	  if (suffix == NULL)
    442 	    suffix = g_strdup (possible_match + strlen (prefix));
    443 	  else
    444 	    {
    445 	      lcp = longest_common_prefix (suffix,
    446 					   possible_match + strlen (prefix));
    447 	      g_free (suffix);
    448 	      suffix = lcp;
    449 
    450 	      if (*suffix == 0)
    451 		break;
    452 	    }
    453 	}
    454     }
    455 
    456   g_free (prefix);
    457 
    458   return suffix;
    459 }
    460 
    461 /**
    462  * g_filename_completer_get_completions:
    463  * @completer: the filename completer.
    464  * @initial_text: text to be completed.
    465  *
    466  * Gets an array of completion strings for a given initial text.
    467  *
    468  * Returns: array of strings with possible completions for @initial_text.
    469  * This array must be freed by g_strfreev() when finished.
    470  **/
    471 char **
    472 g_filename_completer_get_completions (GFilenameCompleter *completer,
    473 				      const char *initial_text)
    474 {
    475   GList *possible_matches, *l;
    476   char *prefix;
    477   char *possible_match;
    478   GPtrArray *res;
    479 
    480   g_return_val_if_fail (G_IS_FILENAME_COMPLETER (completer), NULL);
    481   g_return_val_if_fail (initial_text != NULL, NULL);
    482 
    483   possible_matches = init_completion (completer, initial_text, &prefix);
    484 
    485   res = g_ptr_array_new ();
    486   for (l = possible_matches; l != NULL; l = l->next)
    487     {
    488       possible_match = l->data;
    489 
    490       if (g_str_has_prefix (possible_match, prefix))
    491 	g_ptr_array_add (res,
    492 			 g_strconcat (initial_text, possible_match + strlen (prefix), NULL));
    493     }
    494 
    495   g_free (prefix);
    496 
    497   return (char**)g_ptr_array_free (res, FALSE);
    498 }
    499 
    500 /**
    501  * g_filename_completer_set_dirs_only:
    502  * @completer: the filename completer.
    503  * @dirs_only: a #gboolean.
    504  *
    505  * If @dirs_only is %TRUE, @completer will only
    506  * complete directory names, and not file names.
    507  **/
    508 void
    509 g_filename_completer_set_dirs_only (GFilenameCompleter *completer,
    510 				    gboolean dirs_only)
    511 {
    512   g_return_if_fail (G_IS_FILENAME_COMPLETER (completer));
    513 
    514   completer->dirs_only = dirs_only;
    515 }
    516 
    517 #define __G_FILENAME_COMPLETER_C__
    518 #include "gioaliasdef.c"
    519