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