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