gvfs r1556 - in trunk: . daemon



Author: otte
Date: Wed Mar  5 15:40:59 2008
New Revision: 1556
URL: http://svn.gnome.org/viewvc/gvfs?rev=1556&view=rev

Log:
2008-03-05  Benjamin Otte  <otte gnome org>

	* daemon/gvfsbackendftp.c:
	rewrite file info construction and directory listing. It should now
	correctly resolve symlinks and be fast enough for general use.



Modified:
   trunk/ChangeLog
   trunk/daemon/gvfsbackendftp.c

Modified: trunk/daemon/gvfsbackendftp.c
==============================================================================
--- trunk/daemon/gvfsbackendftp.c	(original)
+++ trunk/daemon/gvfsbackendftp.c	Wed Mar  5 15:40:59 2008
@@ -69,6 +69,26 @@
  * use it without the need to escape. We also can operate on full paths as our 
  * paths exactly match those of a TVFS-using FTP server.
  */
+
+/* unsinged char is on purpose, so we get warnings when we misuse them */
+typedef unsigned char FtpFile;
+typedef struct _FtpConnection FtpConnection;
+
+typedef struct FtpDirReader FtpDirReader;
+struct FtpDirReader {
+  void		(* init_data)	(FtpConnection *conn,
+				 const FtpFile *dir);
+  GFileInfo *	(* get_root)	(FtpConnection *conn);
+  gpointer	(* iter_new)	(FtpConnection *conn);
+  GFileInfo *	(* iter_process)(gpointer       iter,
+				 FtpConnection *conn,
+				 const FtpFile *dirname,
+				 const FtpFile *must_match_file,
+				 const char    *line,
+				 char	      **symlink);
+  void		(* iter_free)	(gpointer	iter);
+};
+
 typedef enum {
   FTP_FEATURE_MDTM = (1 << 0),
   FTP_FEATURE_SIZE = (1 << 1),
@@ -85,12 +105,19 @@
   char *		user;
   char *		password;	/* password or NULL for anonymous */
 
+  /* vfuncs */
+  const FtpDirReader *	dir_ops;
+
   /* connection collection */
   GQueue *		queue;
   GMutex *		mutex;
   GCond *		cond;
   guint			connections;
   guint			max_connections;
+
+  /* caching results from dir queries */
+  GStaticRWLock		directory_cache_lock;
+  GHashTable *		directory_cache;
 };
 
 G_DEFINE_TYPE (GVfsBackendFtp, g_vfs_backend_ftp, G_VFS_TYPE_BACKEND)
@@ -99,8 +126,6 @@
 
 /*** FTP CONNECTION ***/
 
-typedef struct _FtpConnection FtpConnection;
-
 struct _FtpConnection
 {
   /* per-job data */
@@ -733,9 +758,6 @@
  * make it easy to distinguish from GVfs paths.
  */
 
-/* unsinged char is on purpose, so we get warnings when we misuse them */
-typedef unsigned char FtpFile;
-
 static FtpFile *
 ftp_filename_from_gvfs_path (FtpConnection *conn, const char *pathname)
 {
@@ -763,6 +785,8 @@
   return (FtpFile *) g_build_path ("/", (char *) dirname, basename, NULL);
 }
 
+#define ftp_filename_equal g_str_equal
+
 /*** COMMON FUNCTIONS WITH SPECIAL HANDLING ***/
 
 static gboolean
@@ -802,6 +826,159 @@
   return TRUE;
 }
 
+/*** default directory reading ***/
+
+static void
+dir_default_init_data (FtpConnection *conn, const FtpFile *dir)
+{
+  ftp_connection_cd (conn, dir);
+  ftp_connection_ensure_data_connection (conn);
+
+  ftp_connection_send (conn,
+		       RESPONSE_PASS_100 | RESPONSE_FAIL_200,
+		       "LIST");
+}
+
+static GFileInfo *
+dir_default_get_root (FtpConnection *conn)
+{
+  GFileInfo *info;
+  GIcon *icon;
+  char *display_name;
+  
+  info = g_file_info_new ();
+  g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
+
+  g_file_info_set_name (info, "/");
+  display_name = g_strdup_printf (_("/ on %s"),
+      soup_address_get_name (soup_socket_get_remote_address (conn->commands)));
+  g_file_info_set_display_name (info, display_name);
+  g_free (display_name);
+  g_file_info_set_edit_name (info, "/");
+
+  g_file_info_set_content_type (info, "inode/directory");
+  g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, "inode/directory");
+
+  icon = g_themed_icon_new ("remote-folder");
+  g_file_info_set_icon (info, icon);
+  g_object_unref (icon);
+
+  return info;
+}
+
+static gpointer
+dir_default_iter_new (FtpConnection *conn)
+{
+  return g_slice_new (struct list_state);
+}
+
+static GFileInfo *
+dir_default_iter_process (gpointer        iter,
+			  FtpConnection  *conn,
+			  const FtpFile  *dirname,
+			  const FtpFile  *must_match_file,
+			  const char     *line,
+			  char		**symlink)
+{
+  struct list_state *state = iter;
+  struct list_result result = { 0, };
+  GTimeVal tv = { 0, 0 };
+  GFileInfo *info;
+  int type;
+  FtpFile *name;
+  char *s, *t;
+
+  type = ParseFTPList (line, state, &result);
+  if (type != 'd' && type != 'f' && type != 'l')
+    return NULL;
+
+  s = g_strndup (result.fe_fname, result.fe_fnlen);
+  if (dirname)
+    {
+      name = ftp_filename_construct (conn, dirname, s);
+      g_free (s);
+    }
+  else
+    name = (FtpFile *) s;
+  if (name == NULL)
+    return NULL;
+
+  if (must_match_file && !ftp_filename_equal (name, must_match_file))
+    return NULL;
+
+  info = g_file_info_new ();
+
+  s = ftp_filename_to_gvfs_path (conn, name);
+
+  t = g_path_get_basename (s);
+  g_file_info_set_name (info, t);
+  g_free (t);
+
+  if (type == 'l')
+    {
+      char *link;
+
+      link = g_strndup (result.fe_lname, result.fe_lnlen);
+
+      /* FIXME: this whole stuff is not FtpFile save */
+      g_file_info_set_symlink_target (info, link);
+      g_file_info_set_is_symlink (info, TRUE);
+
+      if (symlink)
+	{
+	  char *str = g_path_get_dirname (s);
+	  char *symlink_file = g_build_path ("/", str, link, NULL);
+
+	  g_free (str);
+	  while ((str = strstr (symlink_file, "/../")))
+	    {
+	      char *end = str + 4;
+	      char *start;
+	      start = str - 1;
+	      while (*start != '/')
+		start--;
+	      memcpy (start + 1, end, strlen (end) + 1);
+	    }
+	  str = symlink_file + strlen (symlink_file) - 1;
+	  while (*str == '/' && str > symlink_file)
+	    *str-- = 0;
+	  *symlink = symlink_file;
+	}
+      g_free (link);
+    }
+  else if (symlink)
+    *symlink = NULL;
+
+  g_file_info_set_size (info, strtoul (result.fe_size, NULL, 10));
+
+  gvfs_file_info_populate_default (info, s,
+				   type == 'f' ? G_FILE_TYPE_REGULAR :
+				   type == 'l' ? G_FILE_TYPE_SYMBOLIC_LINK :
+				   G_FILE_TYPE_DIRECTORY);
+  g_free (s);
+  g_free (name);
+
+  tv.tv_sec = mktime (&result.fe_time);
+  if (tv.tv_sec != -1)
+    g_file_info_set_modification_time (info, &tv);
+
+  return info;
+}
+
+static void
+dir_default_iter_free (gpointer iter)
+{
+  g_slice_free (struct list_state, iter);
+}
+
+static const FtpDirReader dir_default = {
+  dir_default_init_data,
+  dir_default_get_root,
+  dir_default_iter_new,
+  dir_default_iter_process,
+  dir_default_iter_free
+};
+
 /*** BACKEND ***/
 
 static void
@@ -911,6 +1088,9 @@
   g_cond_free (ftp->cond);
   g_mutex_free (ftp->mutex);
 
+  g_hash_table_destroy (ftp->directory_cache);
+  g_static_rw_lock_free (&ftp->directory_cache_lock);
+
   g_free (ftp->user);
   g_free (ftp->password);
 
@@ -919,10 +1099,25 @@
 }
 
 static void
+list_free (gpointer list)
+{
+  g_list_foreach (list, (GFunc) g_free, NULL);
+  g_list_free (list);
+}
+
+static void
 g_vfs_backend_ftp_init (GVfsBackendFtp *ftp)
 {
   ftp->mutex = g_mutex_new ();
   ftp->cond = g_cond_new ();
+
+  ftp->directory_cache = g_hash_table_new_full (g_str_hash,
+					        g_str_equal,
+						g_free,
+						list_free);
+  g_static_rw_lock_init (&ftp->directory_cache_lock);
+
+  ftp->dir_ops = &dir_default;
 }
 
 static void
@@ -997,7 +1192,6 @@
 	}
 
 try_login:
-      DEBUG ("user: %s\n", username);
       g_free (ftp->user);
       g_free (ftp->password);
       if (anonymous)
@@ -1358,156 +1552,17 @@
   ftp_connection_pop_job (conn);
 }
 
-#if 0
-typedef enum {
-  FILE_INFO_SIZE         = (1 << 1),
-  FILE_INFO_MTIME	 = (1 << 2),
-  FILE_INFO_TYPE         = (1 << 3)
-} FileInfoFlags;
-
-static FileInfoFlags
-file_info_get_flags (FtpConnection *        conn,
-		     GFileAttributeMatcher *matcher)
-{
-  FileInfoFlags flags = 0;
-
-  if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_STANDARD_SIZE) &&
-      (conn->features& FTP_FEATURE_SIZE))
-    flags |= FILE_INFO_SIZE;
-
-  if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_TIME_MODIFIED) &&
-      (conn->features & FTP_FEATURE_MDTM))
-    flags |= FILE_INFO_MTIME;
-
-  if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_STANDARD_TYPE))
-    flags |= FILE_INFO_TYPE;
-
-  return flags;
-}
-
-static void
-file_info_query (FtpConnection *conn,
-		 const char *filename,
-		 GFileInfo *     info,
-		 FileInfoFlags  flags)
-{
-  GFileType type;
-  guint response;
-
-  DEBUG ("query %s (flags %u)\n", filename, flags);
-
-  if (flags & FILE_INFO_TYPE)
-    {
-      /* kind of an evil trick here to determine the type.
-       * We cwd to the given filename.
-       * If it succeeds, it's a directroy, otherwise it's a file.
-       */
-      response = ftp_connection_send (conn, 0, NULL, "CWD %s", filename);
-      if (response == 0)
-	type = G_FILE_TYPE_REGULAR;
-      else
-	type = G_FILE_TYPE_DIRECTORY;
-    }
-  else
-    type = G_FILE_TYPE_REGULAR;
-
-  gvfs_file_info_populate_default (info, filename, type);
-
-  if (flags & FILE_INFO_SIZE)
-    {
-      response = ftp_connection_send (conn, 0, NULL, "SIZE %s", filename);
-
-      if (response != 0)
-	{
-	  guint64 size = g_ascii_strtoull (conn->read_buffer + 4, NULL, 10);
-	  g_file_info_set_size (info, size);
-	}
-    }
-
-  if (flags & FILE_INFO_MTIME)
-    {
-      response = ftp_connection_send (conn, 0, NULL, "MDTM %s", filename);
-
-      if (response != 0)
-	{
-	  GTimeVal tv;
-	  char *date;
-
-	  /* modify read buffer to get a valid iso time */
-	  date = conn->read_buffer + 4;
-	  memmove (date + 9, date + 8, 10);
-	  date[8] = 'T';
-	  date[19] = 0;
-	  if (g_time_val_from_iso8601 (date, &tv))
-	    g_file_info_set_modification_time (info, &tv);
-	  else
-	    DEBUG ("not a time: %s\n", date);
-	}
-    }
-
-}
-
-static void
-do_query_info (GVfsBackend *backend,
-	       GVfsJobQueryInfo *job,
-	       const char *filename,
-	       GFileQueryInfoFlags query_flags,
-	       GFileInfo *info,
-	       GFileAttributeMatcher *matcher)
-{
-  GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend);
-  GError *error = NULL;
-  FtpConnection *conn;
-  FileInfoFlags flags;
-
-  conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)->cancellable, &error);
-  if (conn == NULL)
-    goto error;
-
-  g_file_info_set_name (info, filename);
-  flags = file_info_get_flags (conn, matcher);
-  file_info_query (conn, filename, info, flags);
-
-  g_vfs_backend_ftp_push_connection (ftp, conn);
-  g_vfs_job_succeeded (G_VFS_JOB (job));
-  return;
-
-error:
-  g_vfs_backend_ftp_push_connection (ftp, conn);
-  g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
-  g_error_free (error);
-}
-
-static void
-do_enumerate (GVfsBackend *backend,
-	      GVfsJobEnumerate *job,
-	      const char *filename,
-	      GFileAttributeMatcher *matcher,
-	      GFileQueryInfoFlags query_flags)
+static GList *
+do_enumerate_directory (FtpConnection *conn)
 {
-  GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend);
-  GError *error = NULL;
-  FtpConnection *conn;
-  char *name;
   gsize size, n_bytes, bytes_read;
   SoupSocketIOStatus status;
   gboolean got_boundary;
-  GList *walk, *list = NULL;
-  guint response;
-  FileInfoFlags flags;
-
-  conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)->cancellable, &error);
-  if (conn == NULL)
-    goto error;
-  if (!ftp_connection_ensure_data_connection (conn, &error))
-    goto error;
+  char *name;
+  GList *list = NULL;
 
-  response = ftp_connection_send (conn, 
-				  RESPONSE_PASS_100 | RESPONSE_FAIL_200,
-				  &error,
-				  "NLST %s", filename);
-  if (response == 0)
-    goto error;
+  if (ftp_connection_in_error (conn))
+    return NULL;
 
   size = 128;
   bytes_read = 0;
@@ -1519,7 +1574,7 @@
 	{
 	  if (size >= 16384)
 	    {
-	      g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FILENAME_TOO_LONG,
+	      g_set_error (&conn->error, G_IO_ERROR, G_IO_ERROR_FILENAME_TOO_LONG,
 		           _("filename too long"));
 	      break;
 	    }
@@ -1529,12 +1584,12 @@
       status = soup_socket_read_until (conn->data,
 				       name + bytes_read,
 				       size - bytes_read - 1,
-				       "\r\n",
-				       2,
+				       "\n",
+				       1,
 				       &n_bytes,
 				       &got_boundary,
 				       conn->job->cancellable,
-				       &error);
+				       &conn->error);
 
       bytes_read += n_bytes;
       switch (status)
@@ -1548,8 +1603,8 @@
 	      }
 	    if (got_boundary)
 	      {
-		name[bytes_read - 2] = 0;
-		DEBUG ("file: %s\n", name);
+		name[bytes_read - 1] = 0;
+		DEBUG ("--- %s\n", name);
 		list = g_list_prepend (list, g_strdup (name));
 		bytes_read = 0;
 	      }
@@ -1567,225 +1622,182 @@
   if (bytes_read)
     {
       name[bytes_read] = 0;
-      DEBUG ("file: %s\n", name);
+      DEBUG ("--- %s\n", name);
       list = g_list_prepend (list, name);
     }
   else
     g_free (name);
 
-  response = ftp_connection_receive (conn, 0, &error);
-  if (response == 0)
-    goto error;
   ftp_connection_close_data_connection (conn);
+  ftp_connection_receive (conn, 0);
+  if (ftp_connection_in_error (conn))
+    goto error;
 
-  flags = file_info_get_flags (conn, matcher);
-  for (walk = list; walk; walk = walk->next)
-    {
-      GFileInfo *info = g_file_info_new ();
-      g_file_info_set_attribute_mask (info, matcher);
-      g_file_info_set_name (info, walk->data);
-      file_info_query (conn, walk->data, info, flags);
-      g_vfs_job_enumerate_add_info (job, info);
-      g_free (walk->data);
-    }
-  g_list_free (list);
-
-  g_vfs_backend_ftp_push_connection (ftp, conn);
-  g_vfs_job_enumerate_done (job);
-  g_vfs_job_succeeded (G_VFS_JOB (job));
-  return;
+  return g_list_reverse (list);
 
 error2:
   ftp_connection_close_data_connection (conn);
-  ftp_connection_receive (conn, 0, NULL);
+  ftp_connection_receive (conn, 0);
 error:
+  ftp_connection_close_data_connection (conn);
   g_list_foreach (list, (GFunc) g_free, NULL);
   g_list_free (list);
-  ftp_connection_close_data_connection (conn);
-  g_vfs_backend_ftp_push_connection (ftp, conn);
-  g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
-  g_error_free (error);
+  return NULL;
 }
-#endif
 
+/* IMPORTANT: SUCK ALARM!
+ * locks ftp->directory_cache_lock but only iff it returns !NULL */
+static const GList *
+enumerate_directory (GVfsBackendFtp *ftp,
+                     FtpConnection * conn,
+		     const FtpFile * dir,
+		     gboolean	     use_cache)
+{
+  GList *files;
+
+  g_static_rw_lock_reader_lock (&ftp->directory_cache_lock);
+  do {
+    if (use_cache)
+      files = g_hash_table_lookup (ftp->directory_cache, dir);
+    else
+      {
+	use_cache = TRUE;
+	files = NULL;
+      }
+    if (files == NULL)
+      {
+	g_static_rw_lock_reader_unlock (&ftp->directory_cache_lock);
+	ftp->dir_ops->init_data (conn, dir);
+	files = do_enumerate_directory (conn);
+	if (files == NULL)
+	  {
+	    return NULL;
+	  }
+	g_static_rw_lock_writer_lock (&ftp->directory_cache_lock);
+	g_hash_table_insert (ftp->directory_cache, g_strdup ((const char *) dir), files);
+	g_static_rw_lock_writer_unlock (&ftp->directory_cache_lock);
+	files = NULL;
+	g_static_rw_lock_reader_lock (&ftp->directory_cache_lock);
+      }
+  } while (files == NULL);
+
+  return files;
+}
+
+/* NB: This gets a file info for the given object, no matter if it's a dir 
+ * or a file */
 static GFileInfo *
-process_line (FtpConnection *conn, const char *line, const FtpFile *dirname, struct list_state *state)
+create_file_info (GVfsBackendFtp *ftp, FtpConnection *conn, const char *filename, char **symlink)
 {
-  struct list_result result = { 0, };
-  GTimeVal tv = { 0, 0 };
+  const GList *walk, *files;
+  char *dirname;
+  FtpFile *dir, *file;
   GFileInfo *info;
-  int type;
-  FtpFile *name;
-  char *s, *t;
+  gpointer iter;
 
-  DEBUG ("--- %s\n", line);
-  type = ParseFTPList (line, state, &result);
-  if (type != 'd' && type != 'f' && type != 'l')
-    return NULL;
+  if (symlink)
+    *symlink = NULL;
 
-  s = g_strndup (result.fe_fname, result.fe_fnlen);
-  if (dirname)
-    {
-      name = ftp_filename_construct (conn, dirname, s);
-      g_free (s);
-    }
-  else
-    name = (FtpFile *) s;
-  if (name == NULL)
-    return NULL;
-
-  info = g_file_info_new ();
+  if (g_str_equal (filename, "/"))
+    return ftp->dir_ops->get_root (conn);
 
-  s = ftp_filename_to_gvfs_path (conn, name);
-  t = strrchr (s, '/');
-  if (t == NULL || t[1] == 0)
-    t = s;
-  else
-    t++;
+  dirname = g_path_get_dirname (filename);
+  dir = ftp_filename_from_gvfs_path (conn, dirname);
+  g_free (dirname);
 
-  g_file_info_set_name (info, t);
-
-  if (type == 'l')
+  files = enumerate_directory (ftp, conn, dir, TRUE);
+  if (files == NULL)
     {
-      char *link;
-
-      g_file_info_set_is_symlink (info, TRUE);
-
-      link = g_strndup (result.fe_lname, result.fe_lnlen);
-      if (!ftp_connection_cd (conn, dirname))
-	{
-	  g_clear_error (&conn->error);
-	  g_object_unref (info);
-	  g_free (link);
-	  g_free (s);
-	  g_free (name);
-	  return NULL;
-	}
-
-      if (ftp_connection_try_cd (conn, (FtpFile *) s))
-	type = 'd';
-      else
-	type = 'f';
-
-      /* FIXME: can we just copy paths for symlinks? */
-      g_file_info_set_symlink_target (info, link);
+      g_free (dir);
+      return NULL;
     }
 
-  g_file_info_set_size (info, strtoul (result.fe_size, NULL, 10));
-
-  gvfs_file_info_populate_default (info, s,
-				   type == 'd' ? G_FILE_TYPE_DIRECTORY :
-				   G_FILE_TYPE_REGULAR);
-  g_free (s);
-  g_free (name);
-
-  tv.tv_sec = mktime (&result.fe_time);
-  if (tv.tv_sec != -1)
-    g_file_info_set_modification_time (info, &tv);
-
+  file = ftp_filename_from_gvfs_path (conn, filename);
+  iter = ftp->dir_ops->iter_new (conn);
+  for (walk = files; walk; walk = walk->next)
+    {
+      info = ftp->dir_ops->iter_process (iter,
+					 conn,
+					 dir,
+					 file,
+					 walk->data,
+					 symlink);
+      if (info)
+	break;
+    }
+  ftp->dir_ops->iter_free (iter);
+  g_static_rw_lock_reader_unlock (&ftp->directory_cache_lock);
+  g_free (dir);
+  g_free (file);
   return info;
 }
 
-static GList *
-run_list_command (FtpConnection *conn, const char *command, ...) G_GNUC_PRINTF (2, 3);
-static GList *
-run_list_command (FtpConnection *conn, const char *command, ...)
+static GFileInfo *
+resolve_symlink (GVfsBackendFtp *ftp, FtpConnection *conn, GFileInfo *original, const char *filename)
 {
-  gsize size, n_bytes, bytes_read;
-  SoupSocketIOStatus status;
-  gboolean got_boundary;
-  va_list varargs;
-  char *name;
-  GList *list = NULL;
-
-  ftp_connection_ensure_data_connection (conn);
+  GFileInfo *info = NULL;
+  char *symlink, *newlink;
+  guint i;
+  static const char *copy_attributes[] = {
+    G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK,
+    G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN,
+    G_FILE_ATTRIBUTE_STANDARD_NAME,
+    G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
+    G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME,
+    G_FILE_ATTRIBUTE_STANDARD_COPY_NAME,
+    G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET
+  };
 
-  va_start (varargs, command);
-  ftp_connection_sendv (conn, 
-		        RESPONSE_PASS_100 | RESPONSE_FAIL_200,
-		        command,
-		        varargs);
-  va_end (varargs);
   if (ftp_connection_in_error (conn))
-    goto error;
-
-  size = 128;
-  bytes_read = 0;
-  name = g_malloc (size);
+    return original;
 
-  do
+  /* How many symlinks should we follow?
+   * <alex> maybe 8?
+   */
+  symlink = g_strdup (filename);
+  for (i = 0; i < 8 && symlink; i++)
     {
-      if (bytes_read + 3 >= size)
-	{
-	  if (size >= 16384)
-	    {
-	      g_set_error (&conn->error, G_IO_ERROR, G_IO_ERROR_FILENAME_TOO_LONG,
-		           _("filename too long"));
-	      break;
-	    }
-	  size += 128;
-	  name = g_realloc (name, size);
-	}
-      status = soup_socket_read_until (conn->data,
-				       name + bytes_read,
-				       size - bytes_read - 1,
-				       "\n",
-				       1,
-				       &n_bytes,
-				       &got_boundary,
-				       conn->job->cancellable,
-				       &conn->error);
+      info = create_file_info (ftp,
+			       conn,
+			       symlink,
+			       &newlink);
+      if (!newlink)
+	break;
 
-      bytes_read += n_bytes;
-      switch (status)
-	{
-	  case SOUP_SOCKET_EOF:
-	  case SOUP_SOCKET_OK:
-	    if (n_bytes == 0)
-	      {
-		status = SOUP_SOCKET_EOF;
-		break;
-	      }
-	    if (got_boundary)
-	      {
-		name[bytes_read - 1] = 0;
-		list = g_list_prepend (list, g_strdup (name));
-		bytes_read = 0;
-	      }
-	    break;
-	  case SOUP_SOCKET_ERROR:
-	    goto error2;
-	  case SOUP_SOCKET_WOULD_BLOCK:
-	  default:
-	    g_assert_not_reached ();
-	    break;
-	}
+      g_free (symlink);
+      symlink = newlink;
     }
-  while (status == SOUP_SOCKET_OK);
+  g_free (symlink);
 
-  if (bytes_read)
+  if (ftp_connection_in_error (conn))
     {
-      name[bytes_read] = 0;
-      list = g_list_prepend (list, name);
+      g_assert (info == NULL);
+      g_clear_error (&conn->error);
+      return original;
     }
-  else
-    g_free (name);
+  if (info == NULL)
+    return original;
 
-  ftp_connection_close_data_connection (conn);
-  ftp_connection_receive (conn, 0);
-  if (ftp_connection_in_error (conn))
-    goto error;
+  for (i = 0; i < G_N_ELEMENTS (copy_attributes); i++)
+    {
+      GFileAttributeType type;
+      gpointer value;
 
-  return g_list_reverse (list);
+      if (!g_file_info_get_attribute_data (original,
+					   copy_attributes[i],
+					   &type,
+					   &value,
+					   NULL))
+	continue;
+      
+      g_file_info_set_attribute (info,
+	                         copy_attributes[i],
+				 type,
+				 value);
+    }
+  g_object_unref (original);
 
-error2:
-  ftp_connection_close_data_connection (conn);
-  ftp_connection_receive (conn, 0);
-error:
-  ftp_connection_close_data_connection (conn);
-  g_list_foreach (list, (GFunc) g_free, NULL);
-  g_list_free (list);
-  return NULL;
+  return info;
 }
 
 static void
@@ -1798,104 +1810,118 @@
 {
   GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend);
   FtpConnection *conn;
-  GList *walk, *list;
-  FtpFile *file = NULL;
-  struct list_state state = { 0, };
+  GFileInfo *real;
+  char *symlink;
 
   conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job));
   if (conn == NULL)
     return;
 
-  file = ftp_filename_from_gvfs_path (conn, filename);
-  if (ftp_connection_try_cd (conn, file))
-    { 
-      /* file is a directory */
-      char *basename = g_path_get_basename (filename);
-
-      g_file_info_set_name (info, basename);
-      gvfs_file_info_populate_default (info, 
-				       basename,
-				       G_FILE_TYPE_DIRECTORY);
-      g_free (basename);
+  if (query_flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)
+    {
+      real = create_file_info (ftp,
+			       conn,
+			       filename,
+			       NULL);
     }
   else
     {
-      GFileInfo *real = NULL;
-
-      /* file is not a directory - maybe it doesn't even exist? */
-      list = run_list_command (conn, "LIST %s", file);
-      for (walk = list; walk; walk = walk->next)
-	{
-	  GFileInfo *cur = process_line (conn, walk->data, NULL, &state);
-	  g_free (walk->data);
-	  if (cur == NULL)
-	    continue;
-	  if (real != NULL)
-	    {
-	      g_list_foreach (walk->next, (GFunc) g_free, NULL); 
-	      g_object_unref (cur);
-	      real = NULL;
-	      break;
-	    }
-	  real = cur;
-	}
-      g_list_free (list);
-      if (real != NULL)
+      real = create_file_info (ftp,
+			       conn,
+			       filename,
+			       &symlink);
+      if (symlink)
 	{
-	  g_file_info_copy_into (real, info);
-	  g_object_unref (real);
+	  real = resolve_symlink (ftp, conn, real, symlink);
+	  g_free (symlink);
 	}
-      else if (!ftp_connection_in_error (conn))
-	g_set_error (&conn->error,
-		     G_IO_ERROR,
-		     G_IO_ERROR_NOT_FOUND,
-		     _("File doesn't exist"));
+    }
 
+  if (real)
+    {
+      g_file_info_copy_into (real, info);
+      g_object_unref (real);
     }
+  else if (!ftp_connection_in_error (conn))
+    g_set_error (&conn->error,
+		 G_IO_ERROR,
+		 G_IO_ERROR_NOT_FOUND,
+		 _("File doesn't exist"));
 
   g_vfs_backend_ftp_push_connection (ftp, conn);
-  g_free (file);
 }
 
 static void
 do_enumerate (GVfsBackend *backend,
 	      GVfsJobEnumerate *job,
-	      const char *filename,
+	      const char *dirname,
 	      GFileAttributeMatcher *matcher,
 	      GFileQueryInfoFlags query_flags)
 {
   GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend);
   FtpConnection *conn;
-  GList *walk, *list;
-  FtpFile *file = NULL;
-  struct list_state state = { 0, };
+  const GList *walk, *files;
+  FtpFile *dir;
+  gpointer iter;
+  GSList *symlink_targets = NULL;
+  GSList *symlink_fileinfos = NULL;
+  GSList *twalk, *fwalk;
+  GFileInfo *info;
 
   conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job));
   if (conn == NULL)
     return;
 
-  file = ftp_filename_from_gvfs_path (conn, filename);
-  ftp_connection_cd (conn, file);
-  list = run_list_command (conn, "LIST");
+  /* no need to check for IS_DIR, because the enumeration code will return that
+   * automatically.
+   */
 
-  for (walk = list; walk; walk = walk->next)
-    {
-      GFileInfo *info = process_line (conn, walk->data, file, &state);
-      if (info)
+  dir = ftp_filename_from_gvfs_path (conn, dirname);
+  files = enumerate_directory (ftp, conn, dir, FALSE);
+  if (files != NULL)
+    {
+      iter = ftp->dir_ops->iter_new (conn);
+      for (walk = files; walk; walk = walk->next)
+	{
+	  char *symlink;
+	  info = ftp->dir_ops->iter_process (iter,
+					     conn,
+					     dir,
+					     NULL,
+					     walk->data,
+					     query_flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS ? NULL : &symlink);
+	  if (symlink)
+	    {
+	      /* This is necessary due to our locking. 
+	       * And we must not unlock here because it might invalidate the list we iterate */
+	      symlink_targets = g_slist_prepend (symlink_targets, symlink);
+	      symlink_fileinfos = g_slist_prepend (symlink_fileinfos, info);
+	    }
+	  else if (info)
+	    {
+	      g_vfs_job_enumerate_add_info (job, info);
+	      g_object_unref (info);
+	    }
+	}
+      ftp->dir_ops->iter_free (iter);
+      g_static_rw_lock_reader_unlock (&ftp->directory_cache_lock);
+      for (twalk = symlink_targets, fwalk = symlink_fileinfos; twalk; 
+	   twalk = twalk->next, fwalk = fwalk->next)
 	{
+	  info = resolve_symlink (ftp, conn, fwalk->data, twalk->data);
+	  g_free (twalk->data);
 	  g_vfs_job_enumerate_add_info (job, info);
 	  g_object_unref (info);
 	}
-      g_free (walk->data);
-    }
-  if (list)
-    {
-      g_vfs_job_enumerate_done (G_VFS_JOB_ENUMERATE (conn->job));
-      g_list_free (list);
+      g_slist_free (symlink_targets);
+      g_slist_free (symlink_fileinfos);
+  
+      if (!ftp_connection_in_error (conn))
+	g_vfs_job_enumerate_done (G_VFS_JOB_ENUMERATE (conn->job));
     }
 
   g_vfs_backend_ftp_push_connection (ftp, conn);
-  g_free (file);
+  g_free (dir);
 }
 
 static void



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]