(root)/
glib-2.79.0/
gio/
gfilenamecompleter.c
       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  }