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