• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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@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
g_filename_completer_finalize(GObject * object)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
g_filename_completer_class_init(GFilenameCompleterClass * klass)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
g_filename_completer_init(GFilenameCompleter * completer)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 *
g_filename_completer_new(void)129 g_filename_completer_new (void)
130 {
131   return g_object_new (G_TYPE_FILENAME_COMPLETER, NULL);
132 }
133 
134 static char *
longest_common_prefix(char * a,char * b)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
load_basenames_data_free(LoadBasenamesData * data)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
got_more_files(GObject * source_object,GAsyncResult * res,gpointer user_data)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
got_enum(GObject * source_object,GAsyncResult * res,gpointer user_data)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
schedule_load_basenames(GFilenameCompleter * completer,GFile * dir,gboolean should_escape)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
cancel_load_basenames(GFilenameCompleter * completer)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 *
init_completion(GFilenameCompleter * completer,const char * initial_text,char ** basename_out)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 *
g_filename_completer_get_completion_suffix(GFilenameCompleter * completer,const char * initial_text)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 **
g_filename_completer_get_completions(GFilenameCompleter * completer,const char * initial_text)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
g_filename_completer_set_dirs_only(GFilenameCompleter * completer,gboolean dirs_only)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