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