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