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 (at) 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 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 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 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 * 129 g_filename_completer_new (void) 130 { 131 return g_object_new (G_TYPE_FILENAME_COMPLETER, NULL); 132 } 133 134 static char * 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 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 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 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 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 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 * 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 * 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 ** 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 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