gvfs r1414 - in trunk: . daemon



Author: otte
Date: Thu Feb 28 10:54:49 2008
New Revision: 1414
URL: http://svn.gnome.org/viewvc/gvfs?rev=1414&view=rev

Log:
2008-02-28  Benjamin Otte  <otte gnome org>

	* daemon/Makefile.am:
	* daemon/gvfsbackendftp.c:
	* daemon/gvfsbackendftp.h:
	drop my current FTP code and continue development in here. Should make
	testing easier for adventurous people.




Modified:
   trunk/ChangeLog
   trunk/daemon/Makefile.am
   trunk/daemon/gvfsbackendftp.c
   trunk/daemon/gvfsbackendftp.h

Modified: trunk/daemon/Makefile.am
==============================================================================
--- trunk/daemon/Makefile.am	(original)
+++ trunk/daemon/Makefile.am	Thu Feb 28 10:54:49 2008
@@ -36,14 +36,13 @@
 
 libexec_PROGRAMS=gvfsd gvfsd-ftp gvfsd-sftp gvfsd-trash gvfsd-computer gvfsd-burn gvfsd-localtest
 
-mount_in_files = ftp.mount.in sftp.mount.in trash.mount.in computer.mount.in burn.mount.in localtest.mount.in
+mount_in_files = sftp.mount.in trash.mount.in computer.mount.in burn.mount.in localtest.mount.in
 mount_DATA =  sftp.mount trash.mount computer.mount burn.mount localtest.mount
-# Disabled until the implementation is working: ftp.mount
 
-mount_in_files += http.mount.in dav.mount.in 
+mount_in_files += http.mount.in dav.mount.in ftp.mount.in 
 if HAVE_HTTP
-mount_DATA += http.mount dav.mount  
-libexec_PROGRAMS += gvfsd-http gvfsd-dav
+mount_DATA += http.mount dav.mount ftp.mount
+libexec_PROGRAMS += gvfsd-http gvfsd-dav gvfsd-ftp
 endif
 
 mount_in_files += smb.mount.in smb-browse.mount.in
@@ -223,9 +222,10 @@
 	-DBACKEND_HEADER=gvfsbackendftp.h \
 	-DDEFAULT_BACKEND_TYPE=ftp \
 	-DMAX_JOB_THREADS=1 \
+	$(HTTP_CFLAGS) \
 	-DBACKEND_TYPES='"ftp", G_VFS_TYPE_BACKEND_FTP,'
 
-gvfsd_ftp_LDADD = $(libraries)
+gvfsd_ftp_LDADD = $(libraries) $(HTTP_LIBS)
 
 gvfsd_sftp_SOURCES = \
 	sftp.h \

Modified: trunk/daemon/gvfsbackendftp.c
==============================================================================
--- trunk/daemon/gvfsbackendftp.c	(original)
+++ trunk/daemon/gvfsbackendftp.c	Thu Feb 28 10:54:49 2008
@@ -1,7 +1,7 @@
 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* GIO - GLib Input, Output and Streaming Library
  * 
- * Copyright (C) 2006-2007 Red Hat, Inc.
+ * Copyright (C) 2008 Benjamin Otte <otte gnome org>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -18,22 +18,17 @@
  * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  * Boston, MA 02111-1307, USA.
  *
- * Author: Alexander Larsson <alexl redhat com>
+ * Author: Benjmain Otte <otte gnome org>
  */
 
 
 #include <config.h>
 
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <errno.h>
-#include <unistd.h>
-#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
-
-#include <glib/gstdio.h>
 #include <glib/gi18n.h>
-#include <gio/gio.h>
+#include <libsoup/soup.h>
 
 #include "gvfsbackendftp.h"
 #include "gvfsjobopenforread.h"
@@ -49,398 +44,1216 @@
 #include "gvfsjobenumerate.h"
 #include "gvfsdaemonprotocol.h"
 
+#if 1
+#define DEBUG g_print
+#else
+#define DEBUG(...)
+#endif
+
+typedef enum {
+  FTP_FEATURE_MDTM = (1 << 0),
+  FTP_FEATURE_SIZE = (1 << 1)
+} FtpFeatures;
+
 struct _GVfsBackendFtp
 {
-  GVfsBackend parent_instance;
+  GVfsBackend		backend;
 
-  GMountSource *mount_source; /* Only used/set during mount */
-  int mount_try;
-  gboolean mount_try_again;
+  SoupAddress *		addr;
+  char *		user;
+  char *		password;
+
+  /* connection collection */
+  GQueue *		queue;
+  GMutex *		mutex;
+  GCond *		cond;
 };
 
 G_DEFINE_TYPE (GVfsBackendFtp, g_vfs_backend_ftp, G_VFS_TYPE_BACKEND)
 
-static void
-g_vfs_backend_ftp_finalize (GObject *object)
-{
-  if (G_OBJECT_CLASS (g_vfs_backend_ftp_parent_class)->finalize)
-    (*G_OBJECT_CLASS (g_vfs_backend_ftp_parent_class)->finalize) (object);
-}
+#define STATUS_GROUP(status) ((status) / 100)
 
-static void
-g_vfs_backend_ftp_init (GVfsBackendFtp *backend)
+/*** FTP CONNECTION ***/
+
+typedef struct _FtpConnection FtpConnection;
+
+struct _FtpConnection
 {
-}
+  GCancellable *	cancellable;
+
+  FtpFeatures		features;
+
+  SoupSocket *		commands;
+  gchar			read_buffer[256];
+  gsize			read_bytes;
+
+  SoupSocket *		data;
+};
 
 static void
-do_mount (GVfsBackend *backend,
-	  GVfsJobMount *job,
-	  GMountSpec *mount_spec,
-	  GMountSource *mount_source,
-	  gboolean is_automount)
+ftp_connection_free (FtpConnection *conn)
 {
-#if 0
-  GVfsBackendFtp *op_backend = G_VFS_BACKEND_FTP (backend);
-#endif
-  /* TODO */
+  if (conn->commands)
+    g_object_unref (conn->commands);
+  if (conn->data)
+    g_object_unref (conn->data);
 
-  g_vfs_job_succeeded (G_VFS_JOB (job));
+  g_slice_free (FtpConnection, conn);
 }
 
-static gboolean
-try_mount (GVfsBackend *backend,
-	   GVfsJobMount *job,
-	   GMountSpec *mount_spec,
-	   GMountSource *mount_source,
-	   gboolean is_automount)
+static void
+ftp_error_set_from_response (GError **error, guint response)
 {
-#if 0
-  GVfsBackendFtp *op_backend = G_VFS_BACKEND_FTP (backend);
-#endif
-  const char *server, *share; /* *domain, *user; */
+  /* FIXME: make error messages nicer */
+  g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+               _("Operation failed"));
+}
 
-  server = g_mount_spec_get (mount_spec, "server");
-  share = g_mount_spec_get (mount_spec, "share");
+/**
+ * ResponseFlags:
+ * RESPONSE_PASS_100: Don't treat 1XX responses, but return them
+ * RESPONSE_PASS_300: Don't treat 3XX responses, but return them
+ * RESPONSE_PASS_400: Don't treat 4XX responses, but return them
+ * RESPONSE_PASS_500: Don't treat 5XX responses, but return them
+ * RESPONSE_FAIL_200: Fail on a 2XX response
+ */
 
-  if (server == NULL || share == NULL)
+typedef enum {
+  RESPONSE_PASS_100 = (1 << 0),
+  RESPONSE_PASS_300 = (1 << 1),
+  RESPONSE_PASS_400 = (1 << 2),
+  RESPONSE_PASS_500 = (1 << 3),
+  RESPONSE_FAIL_200 = (1 << 4)
+} ResponseFlags;
+
+/**
+ * ftp_connection_receive:
+ * @conn: connection to receive from
+ * @flags: flags for handling the response
+ * @error: pointer to error message
+ *
+ * Reads a command and stores it in @conn->read_buffer. The read buffer will be
+ * null-terminated and contain @conn->read_bytes bytes. Afterwards, the response
+ * will be parsed and processed according to @flags. By default, all responses
+ * but 2xx will cause an error.
+ *
+ * Returns: 0 on error, the ftp code otherwise
+ **/
+static guint
+ftp_connection_receive (FtpConnection *conn,
+			ResponseFlags  flags,
+			GError **      error)
+{
+  SoupSocketIOStatus status;
+  gsize n_bytes;
+  gboolean got_boundary;
+  char *last_line;
+  enum {
+    FIRST_LINE,
+    MULTILINE,
+    DONE
+  } reply_state = FIRST_LINE;
+  guint response = 0;
+  gsize bytes_left;
+
+  conn->read_bytes = 0;
+  bytes_left = sizeof (conn->read_buffer) - conn->read_bytes - 1;
+  while (reply_state != DONE && bytes_left >= 6)
     {
-      g_vfs_job_failed (G_VFS_JOB (job),
-			G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
-			_("No hostname specified"));
-      return TRUE;
-    }
-#if 0
-  user = g_mount_spec_get (mount_spec, "user");
-  domain = g_mount_spec_get (mount_spec, "domain");
-#endif
+      last_line = conn->read_buffer + conn->read_bytes;
+      status = soup_socket_read_until (conn->commands,
+				       last_line,
+				       bytes_left,
+				       "\r\n",
+				       2,
+				       &n_bytes,
+				       &got_boundary,
+				       conn->cancellable,
+				       error);
+      switch (status)
+	{
+	  case SOUP_SOCKET_OK:
+	  case SOUP_SOCKET_EOF:
+	    if (got_boundary)
+	      break;
+	    g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+			 _("Invalid reply"));
+	    /* fall through */
+	  case SOUP_SOCKET_ERROR:
+	    conn->read_buffer[conn->read_bytes] = 0;
+	    return 0;
+	  case SOUP_SOCKET_WOULD_BLOCK:
+	  default:
+	    g_assert_not_reached ();
+	    break;
+	}
 
-  /* TODO */
+      bytes_left -= n_bytes;
+      conn->read_bytes += n_bytes;
+      conn->read_buffer[conn->read_bytes] = 0;
+      DEBUG ("<-- %s", last_line);
 
-#if 0  
-  op_backend->server = g_strdup (server);
-  op_backend->share = g_strdup (share);
-  op_backend->user = g_strdup (user);
-  op_backend->domain = g_strdup (domain);
-#endif
+      if (reply_state == FIRST_LINE)
+	{
+	  if (n_bytes < 4 ||
+	      last_line[0] <= '0' || last_line[0] > '5' ||
+	      last_line[1] < '0' || last_line[1] > '9' ||
+	      last_line[2] < '0' || last_line[2] > '9')
+	    {
+	      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+			   _("Invalid reply"));
+	      return 0;
+	    }
+	  response = 100 * (last_line[0] - '0') +
+		      10 * (last_line[1] - '0') +
+		 	   (last_line[2] - '0');
+	  if (last_line[3] == ' ')
+	    reply_state = DONE;
+	  else if (last_line[3] == '-')
+	    reply_state = MULTILINE;
+	  else
+	    {
+	      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+			   _("Invalid reply"));
+	      return 0;
+	    }
+	}
+      else
+	{
+	  if (n_bytes >= 4 &&
+	      memcmp (conn->read_buffer, last_line, 3) == 0 &&
+	      last_line[3] == ' ')
+	    reply_state = DONE;
+	}
+    }
 
-  return FALSE;
-}
+  if (reply_state != DONE)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+		   _("Invalid reply"));
+      return 0;
+    }
 
-static void 
-do_open_for_read (GVfsBackend *backend,
-		  GVfsJobOpenForRead *job,
-		  const char *filename)
-{
-#if 0
-  GVfsBackendFtp *op_backend = G_VFS_BACKEND_FTP (backend);
+  switch (STATUS_GROUP (response))
+    {
+      case 0:
+	return 0;
+      case 1:
+	if (flags & RESPONSE_PASS_100)
+	  break;
+	ftp_error_set_from_response (error, response);
+	return 0;
+      case 2:
+	if (flags & RESPONSE_FAIL_200)
+	  {
+	    ftp_error_set_from_response (error, response);
+	    return 0;
+	  }
+	break;
+      case 3:
+	if (flags & RESPONSE_PASS_300)
+	  break;
+	ftp_error_set_from_response (error, response);
+	return 0;
+      case 4:
+	if (flags & RESPONSE_PASS_400)
+	  break;
+	ftp_error_set_from_response (error, response);
+	return 0;
+	break;
+      case 5:
+	if (flags & RESPONSE_PASS_500)
+	  break;
+	ftp_error_set_from_response (error, response);
+	return 0;
+      default:
+	g_assert_not_reached ();
+	break;
+    }
 
-  /* TODO */
+  return response;
+}
 
-  if (file == NULL)
-    g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno);
-  else
+/**
+ * ftp_connection_send:
+ * @conn: the connection to send to
+ * @flags: #ResponseFlags to use
+ * @error: pointer to take an error
+ * @format: format string to construct command from 
+ *          (without trailing \r\n)
+ * @...: arguments to format string
+ *
+ * Takes a command, waits for an answer and parses it. Without any @flags, FTP 
+ * codes other than 2xx cause an error. The last read ftp command will be put 
+ * into @conn->read_buffer.
+ *
+ * Returns: 0 on error or the receied FTP code otherwise.
+ *     
+ **/
+static guint
+ftp_connection_send (FtpConnection *conn,
+		     ResponseFlags  flags,
+		     GError **	    error,
+		     const char *   format,
+		     ...) G_GNUC_PRINTF (4, 5);
+static guint
+ftp_connection_send (FtpConnection *conn,
+		     ResponseFlags  flags,
+		     GError **	    error,
+		     const char *   format,
+		     ...)
+{
+  va_list varargs;
+  GString *command;
+  SoupSocketIOStatus status;
+  gsize n_bytes;
+  guint response;
+
+  command = g_string_new ("");
+  va_start (varargs, format);
+  g_string_append_vprintf (command, format, varargs);
+  va_end (varargs);
+  DEBUG ("--> %s\n", command->str);
+  g_string_append (command, "\r\n");
+  status = soup_socket_write (conn->commands,
+			      command->str,
+			      command->len,
+			      &n_bytes,
+			      conn->cancellable,
+			      error);
+  switch (status)
     {
-      g_vfs_job_open_for_read_set_can_seek (job, TRUE);
-      g_vfs_job_open_for_read_set_handle (job, file);
-      g_vfs_job_succeeded (G_VFS_JOB (job));
+      case SOUP_SOCKET_OK:
+      case SOUP_SOCKET_EOF:
+	if (n_bytes == command->len)
+	  break;
+	g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+	    _("broken transmission"));
+	/* fall through */
+      case SOUP_SOCKET_ERROR:
+	g_string_free (command, TRUE);
+	return 0;
+      case SOUP_SOCKET_WOULD_BLOCK:
+      default:
+	g_assert_not_reached ();
     }
-#endif
+  g_string_free (command, TRUE);
+
+  response = ftp_connection_receive (conn, flags, error);
+  return response;
 }
 
 static void
-do_read (GVfsBackend *backend,
-	 GVfsJobRead *job,
-	 GVfsBackendHandle handle,
-	 char *buffer,
-	 gsize bytes_requested)
+ftp_connection_parse_features (FtpConnection *conn)
 {
-#if 0
-  GVfsBackendFtp *op_backend = G_VFS_BACKEND_FTP (backend);
+  struct {
+    const char *	name;		/* name of feature */
+    FtpFeatures		enable;		/* flags to enable with this feature */
+  } features[] = {
+    { "MDTM", FTP_FEATURE_MDTM },
+    { "SIZE", FTP_FEATURE_SIZE }
+  };
+  char **supported;
+  guint i, j;
 
-  /* TODO */
+  supported = g_strsplit (conn->read_buffer, "\r\n", -1);
 
-  if (res == -1)
-    g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno);
-  else
+  for (i = 1; supported[i]; i++)
     {
-      g_vfs_job_read_set_size (job, res);
-      g_vfs_job_succeeded (G_VFS_JOB (job));
-	    
+      const char *feature = supported[i];
+      if (feature[0] != ' ')
+	continue;
+      feature++;
+      for (j = 0; j < G_N_ELEMENTS (features); j++)
+	{
+	  if (g_ascii_strcasecmp (feature, features[j].name) == 0)
+	    {
+	      DEBUG ("feature %s supported\n", features[j].name);
+	      conn->features |= features[j].enable;
+	    }
+	}
     }
-#endif
 }
 
-static void
-do_seek_on_read (GVfsBackend *backend,
-		 GVfsJobSeekRead *job,
-		 GVfsBackendHandle handle,
-		 goffset    offset,
-		 GSeekType  type)
-{
-#if 0
-  GVfsBackendFtp *op_backend = G_VFS_BACKEND_FTP (backend);
-
-  switch (type)
-    {
-    case G_SEEK_SET:
-      whence = SEEK_SET;
-      break;
-    case G_SEEK_CUR:
-      whence = SEEK_CUR;
-      break;
-    case G_SEEK_END:
-      whence = SEEK_END;
-      break;
-    default:
-      g_vfs_job_failed (G_VFS_JOB (job),
-			G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
-			_("Unsupported seek type"));
-      return;
+static FtpConnection *
+ftp_connection_new (SoupAddress * addr, 
+                    GCancellable *cancellable,
+		    const char *  username,
+		    const char *  password,
+		    GError **	  error)
+{
+  FtpConnection *conn;
+  guint status;
+
+  conn = g_slice_new0 (FtpConnection);
+  conn->cancellable = cancellable;
+  conn->commands = soup_socket_new ("non-blocking", FALSE,
+                                    "remote-address", addr,
+				    NULL);
+  status = soup_socket_connect_sync (conn->commands, cancellable);
+  if (!SOUP_STATUS_IS_SUCCESSFUL (status))
+    {
+      /* FIXME: better error messages depending on status please */
+      g_set_error (error,
+		   G_IO_ERROR,
+		   G_IO_ERROR_HOST_NOT_FOUND,
+		   _("Could not connect to host"));
+      goto fail;
     }
-#endif
 
-  /* TODO */
-
-#if 0
-  if (res == (off_t)-1)
-    g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno);
-  else
+  status = ftp_connection_receive (conn, 0, error);
+  if (status == 0)
+    goto fail;
+
+  status = ftp_connection_send (conn, RESPONSE_PASS_300, NULL,
+                                "USER %s", username);
+  if (status == 0)
     {
-      g_vfs_job_seek_read_set_offset (job, res);
-      g_vfs_job_succeeded (G_VFS_JOB (job));
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
+		   _("Invalid username"));
+      goto fail;
     }
-#endif
+  else if (STATUS_GROUP (status) == 3)
+    {
+      status = ftp_connection_send (conn, RESPONSE_PASS_300, NULL,
+				    "PASS %s", password);
+      if (status == 0)
+	{
+	  g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
+		       _("Invalid password"));
+	  goto fail;
+	}
+      else if (STATUS_GROUP (status) == 3)
+	{
+	  g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+		       _("Accounts are not supported"));
+	  goto fail;
+	}
+    }
+
+  /* only binary transfers please */
+  status = ftp_connection_send (conn, 0, error, "TYPE I");
+  if (status == 0)
+    goto fail;
+
+  /* check supported features */
+  status = ftp_connection_send (conn, 0, NULL, "FEAT");
+  if (status != 0)
+    ftp_connection_parse_features (conn);
+
+
+  return conn;
+
+fail:
+  ftp_connection_free (conn);
+  return NULL;
 }
 
-static void
-do_close_read (GVfsBackend *backend,
-	       GVfsJobCloseRead *job,
-	       GVfsBackendHandle handle)
+static gboolean
+ftp_connection_ensure_data_connection (FtpConnection *conn, 
+                                       GError **      error)
 {
-#if 0
-  GVfsBackendFtp *op_backend = G_VFS_BACKEND_FTP (backend);
+  guint ip1, ip2, ip3, ip4, port1, port2;
+  SoupAddress *addr;
+  const char *s;
+  char *ip;
+  guint status;
+
+  /* only binary transfers please */
+  status = ftp_connection_send (conn, 0, error, "PASV");
+  if (status == 0)
+    return FALSE;
+
+  /* parse response and try to find the address to connect to.
+   * This code does the sameas curl.
+   */
+  for (s = conn->read_buffer; *s; s++)
+    {
+      if (sscanf (s, "%u,%u,%u,%u,%u,%u", 
+                 &ip1, &ip2, &ip3, &ip4, 
+                 &port1, &port2) == 6)
+       break;
+    }
+  if (*s == 0)
+    {
+      return FALSE;
+    }
+  ip = g_strdup_printf ("%u.%u.%u.%u", ip1, ip2, ip3, ip4);
+  addr = soup_address_new (ip, port1 << 8 | port2);
+  g_free (ip);
+
+  conn->data = soup_socket_new ("non-blocking", FALSE,
+				"remote-address", addr,
+				NULL);
+  g_object_unref (addr);
+  status = soup_socket_connect_sync (conn->data , conn->cancellable);
+  if (!SOUP_STATUS_IS_SUCCESSFUL (status))
+    {
+      /* FIXME: better error messages depending on status please */
+      g_set_error (error,
+		   G_IO_ERROR,
+		   G_IO_ERROR_HOST_NOT_FOUND,
+		   _("Could not connect to host"));
+      g_object_unref (conn->data);
+      conn->data = NULL;
+      return FALSE;
+    }
 
-  /* TODO */
+  return TRUE;
+}
 
-  if (res == -1)
-    g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno);
-#endif
+static void
+ftp_connection_close_data_connection (FtpConnection *conn)
+{
+  if (conn == NULL || conn->data == NULL)
+    return;
 
-  g_vfs_job_succeeded (G_VFS_JOB (job));
+  g_object_unref (conn->data);
+  conn->data = NULL;
 }
+/*** BACKEND ***/
 
 static void
-do_create (GVfsBackend *backend,
-           GVfsJobOpenForWrite *job,
-           const char *filename,
-           GFileCreateFlags flags)
+g_vfs_backend_ftp_push_connection (GVfsBackendFtp *ftp, FtpConnection *conn)
 {
-#if 0
-  GVfsBackendFtp *op_backend = G_VFS_BACKEND_FTP (backend);
+  /* we allow conn == NULL to ease error cases */
+  if (conn == NULL)
+    return;
 
-  /* TODO */
+  conn->cancellable = NULL;
 
-  if (file == NULL)
-    g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno);
-  else
+  g_mutex_lock (ftp->mutex);
+  if (ftp->queue)
     {
-      handle = g_new0 (FtpWriteHandle, 1);
-      handle->file = file;
-
-      g_vfs_job_open_for_write_set_can_seek (job, TRUE);
-      g_vfs_job_open_for_write_set_handle (job, handle);
-      g_vfs_job_succeeded (G_VFS_JOB (job));
+      g_queue_push_tail (ftp->queue, conn);
+      g_cond_signal (ftp->cond);
     }
-#endif
+  else
+    ftp_connection_free (conn);
+  g_mutex_unlock (ftp->mutex);
 }
 
 static void
-do_append_to (GVfsBackend *backend,
-              GVfsJobOpenForWrite *job,
-              const char *filename,
-              GFileCreateFlags flags)
+do_broadcast (GCancellable *cancellable, GCond *cond)
 {
-#if 0
-  GVfsBackendFtp *op_backend = G_VFS_BACKEND_FTP (backend);
+  g_cond_broadcast (cond);
+}
 
-  /* TODO */
+static FtpConnection *
+g_vfs_backend_ftp_pop_connection (GVfsBackendFtp *ftp, 
+                                  GCancellable *  cancellable,
+				  GError **       error)
+{
+  FtpConnection *conn;
 
-  if (file == NULL)
-    g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno);
-  else
+  g_mutex_lock (ftp->mutex);
+  conn = ftp->queue ? g_queue_pop_head (ftp->queue) : NULL;
+  if (conn == NULL && ftp->queue != NULL)
     {
-      handle = g_new0 (FtpWriteHandle, 1);
-      handle->file = file;
-
-      initial_offset = op_backend->ftp_context->lseek (op_backend->ftp_context, file,
-						       0, SEEK_CUR);
-      if (initial_offset == (off_t) -1)
-	g_vfs_job_open_for_write_set_can_seek (job, FALSE);
-      else
+      guint id = g_signal_connect (cancellable, 
+				   "cancelled", 
+				   G_CALLBACK (do_broadcast),
+				   ftp->cond);
+      while (conn == NULL && ftp->queue == NULL && !g_cancellable_is_cancelled (cancellable))
 	{
-	  g_vfs_job_open_for_write_set_initial_offset (job, initial_offset);
-	  g_vfs_job_open_for_write_set_can_seek (job, TRUE);
+	  g_cond_wait (ftp->cond, ftp->mutex);
+	  conn = g_queue_pop_head (ftp->queue);
 	}
-      g_vfs_job_open_for_write_set_handle (job, handle);
-      g_vfs_job_succeeded (G_VFS_JOB (job));
+      g_signal_handler_disconnect (cancellable, id);
     }
-#endif
+  g_mutex_unlock (ftp->mutex);
+
+  if (conn == NULL)
+    {
+      /* FIXME: need different error on force-unmount? */
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED,
+	           _("Operation cancelled"));
+    }
+  else
+    conn->cancellable = cancellable;
+
+  return conn;
 }
 
 static void
-do_replace (GVfsBackend *backend,
-            GVfsJobOpenForWrite *job,
-            const char *filename,
-            const char *etag,
-            gboolean make_backup,
-            GFileCreateFlags flags)
+g_vfs_backend_ftp_finalize (GObject *object)
 {
-#if 0
-  GVfsBackendFtp *op_backend = G_VFS_BACKEND_FTP (backend);
-#endif
-  /* TODO */
+  GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (object);
+
+  if (ftp->addr)
+    g_object_unref (ftp->addr);
+
+  /* has been cleared on unmount */
+  g_assert (ftp->queue == NULL);
+  g_cond_free (ftp->cond);
+  g_mutex_free (ftp->mutex);
+
+  g_free (ftp->user);
+  g_free (ftp->password);
+
+  if (G_OBJECT_CLASS (g_vfs_backend_ftp_parent_class)->finalize)
+    (*G_OBJECT_CLASS (g_vfs_backend_ftp_parent_class)->finalize) (object);
 }
 
 static void
-do_write (GVfsBackend *backend,
-	  GVfsJobWrite *job,
-	  GVfsBackendHandle _handle,
-	  char *buffer,
-	  gsize buffer_size)
+g_vfs_backend_ftp_init (GVfsBackendFtp *ftp)
+{
+  ftp->mutex = g_mutex_new ();
+  ftp->cond = g_cond_new ();
+}
+
+static void
+do_mount (GVfsBackend *backend,
+	  GVfsJobMount *job,
+	  GMountSpec *mount_spec,
+	  GMountSource *mount_source,
+	  gboolean is_automount)
 {
-#if 0
-  GVfsBackendFtp *op_backend = G_VFS_BACKEND_FTP (backend);
+  GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend);
+  FtpConnection *conn;
+  char *prompt;
+  char *username;
+  char *password;
+  gboolean aborted, handled;
+  GError *error = NULL;
+
+  /* translators: %s here is the display name of the share */
+  prompt = g_strdup_printf (_("Enter passsword for %s"),
+                            g_vfs_backend_get_display_name (backend));
+
+  handled = g_mount_source_ask_password (
+         mount_source,
+         prompt,
+         ftp->user ? ftp->user : "anonymous",
+         NULL,
+         G_ASK_PASSWORD_NEED_USERNAME |
+         G_ASK_PASSWORD_NEED_PASSWORD |
+         G_ASK_PASSWORD_ANONYMOUS_SUPPORTED,
+	 &aborted,
+	 &password,
+	 &username,
+	 NULL,
+	 NULL);
+  if (handled && !aborted)
+    {
+      g_free (ftp->user);
+      g_free (ftp->password);
+      ftp->user = username;
+      ftp->password = password;
+    }
+  else if (handled)
+    { 
+      g_free (username);
+      g_free (password);
+    }
+
+  if (ftp->user == NULL)
+    ftp->user = g_strdup ("anonymous");
 
-  /* TODO */
+  g_free (prompt);
 
-  if (res == -1)
-    g_vfs_job_failed_from_errno (G_VFS_JOB (job), errno);
+  conn = ftp_connection_new (ftp->addr,
+			     G_VFS_JOB (job)->cancellable,
+			     ftp->user,
+			     ftp->password,
+			     &error);
+  if (conn == NULL)
+    {
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+    }
   else
     {
-      g_vfs_job_write_set_written_size (job, res);
+      ftp->queue = g_queue_new ();
+      g_vfs_backend_ftp_push_connection (ftp, conn);
       g_vfs_job_succeeded (G_VFS_JOB (job));
     }
-#endif
 }
 
-static void
-do_seek_on_write (GVfsBackend *backend,
-		  GVfsJobSeekWrite *job,
-		  GVfsBackendHandle _handle,
-		  goffset    offset,
-		  GSeekType  type)
+static gboolean
+try_mount (GVfsBackend *backend,
+	  GVfsJobMount *job,
+	  GMountSpec *mount_spec,
+	  GMountSource *mount_source,
+	  gboolean is_automount)
 {
-#if 0
-  GVfsBackendFtp *op_backend = G_VFS_BACKEND_FTP (backend);
-#endif
-  /* TODO */
+  GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend);
+  const char *host, *port_str;
+  guint port;
+  char *name;
+
+  host = g_mount_spec_get (mount_spec, "host");
+  if (host == NULL)
+    {
+      g_vfs_job_failed (G_VFS_JOB (job),
+                       G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                       _("No hostname specified"));
+      return TRUE;
+    }
+  port_str = g_mount_spec_get (mount_spec, "port");
+  if (port_str == NULL)
+    port = 21;
+  else
+    {
+      /* FIXME: error handling? */
+      port = strtoul (port_str, NULL, 10);
+    }
+
+  ftp->addr = soup_address_new (host, port);
+  ftp->user = g_strdup (g_mount_spec_get (mount_spec, "user"));
+
+  if (port_str == NULL)
+    /* translators: this is the default display name for FTP shares.
+     * %s is the hostname. */
+    name = g_strdup_printf (_("FTP on %s"), host);
+  else
+    /* translators: this is the display name for FTP shares on a non-default
+     * port. %s is hostname, %u the used port. */
+    name = g_strdup_printf (_("FTP on %s:%u"), host, port);
+
+  g_vfs_backend_set_display_name (backend, name);
+  g_free (name);
+  g_vfs_backend_set_mount_spec (backend, mount_spec);
+  g_vfs_backend_set_icon_name (backend, "folder-remote");
+
+  return FALSE;
 }
 
 static void
-do_close_write (GVfsBackend *backend,
-		GVfsJobCloseWrite *job,
-		GVfsBackendHandle _handle)
+do_unmount (GVfsBackend *   backend,
+	    GVfsJobUnmount *job)
 {
-#if 0
-  GVfsBackendFtp *op_backend = G_VFS_BACKEND_FTP (backend);
-#endif
-  /* TODO */
+  GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend);
+  FtpConnection *conn;
+
+  g_mutex_lock (ftp->mutex);
+  while ((conn = g_queue_pop_head (ftp->queue)))
+    {
+      /* FIXME: properly quit */
+      ftp_connection_free (conn);
+    }
+  g_queue_free (ftp->queue);
+  ftp->queue = NULL;
+  g_cond_broadcast (ftp->cond);
+  g_mutex_unlock (ftp->mutex);
+  g_vfs_job_succeeded (G_VFS_JOB (job));
 }
 
 static void
-do_query_info (GVfsBackend *backend,
-               GVfsJobQueryInfo *job,
-               const char *filename,
-               GFileQueryInfoFlags flags,
-               GFileInfo *info,
-               GFileAttributeMatcher *attribute_matcher)
+do_open_for_read (GVfsBackend *backend,
+		  GVfsJobOpenForRead *job,
+		  const char *filename)
 {
-#if 0
-  GVfsBackendFtp *op_backend = G_VFS_BACKEND_FTP (backend);
-#endif
-  /* TODO */
+  GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend);
+  GError *error = NULL;
+  FtpConnection *conn;
+  guint status;
+
+  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;
+
+  status = ftp_connection_send (conn,
+				RESPONSE_PASS_100 | RESPONSE_FAIL_200,
+				&error,
+                                "RETR %s", filename);
+  if (status == 0)
+    goto error;
+
+  /* don't push the connection back, it's our handle now */
+  conn->cancellable = NULL;
+  g_vfs_job_open_for_read_set_handle (job, conn);
+  g_vfs_job_open_for_read_set_can_seek (job, FALSE);
+  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_query_fs_info (GVfsBackend *backend,
-                  GVfsJobQueryFsInfo *job,
-                  const char *filename,
-                  GFileInfo *info,
-                  GFileAttributeMatcher *attribute_matcher)
+do_close_read (GVfsBackend *     backend,
+	       GVfsJobCloseRead *job,
+	       GVfsBackendHandle handle)
 {
-  /* TODO */
+  GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend);
+  GError *error = NULL;
+  FtpConnection *conn = handle;
+  guint response;
+
+  conn->cancellable = G_VFS_JOB (job)->cancellable;
+  ftp_connection_close_data_connection (conn);
+  response = ftp_connection_receive (conn, 0, &error); 
+  if (response == 0)
+    {
+      g_vfs_backend_ftp_push_connection (ftp, conn);
+      if (response != 0)
+	ftp_error_set_from_response (&error, response);
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return;
+    }
+
+  g_vfs_backend_ftp_push_connection (ftp, conn);
+  g_vfs_job_succeeded (G_VFS_JOB (job));
 }
 
-static gboolean
-try_query_settable_attributes (GVfsBackend *backend,
-			       GVfsJobQueryAttributes *job,
-			       const char *filename)
+static void
+do_read (GVfsBackend *     backend,
+	 GVfsJobRead *     job,
+	 GVfsBackendHandle handle,
+	 char *            buffer,
+	 gsize             bytes_requested)
 {
-  /* TODO */
+  GError *error = NULL;
+  FtpConnection *conn = handle;
+  SoupSocketIOStatus status;
+  gsize n_bytes;
+
+  status = soup_socket_read (conn->data,
+			     buffer,
+			     bytes_requested,
+			     &n_bytes,
+			     G_VFS_JOB (job)->cancellable,
+			     &error);
+  switch (status)
+    {
+      case SOUP_SOCKET_EOF:
+      case SOUP_SOCKET_OK:
+	g_vfs_job_read_set_size (job, n_bytes);
+	g_vfs_job_succeeded (G_VFS_JOB (job));
+	return;
+      case SOUP_SOCKET_ERROR:
+	break;
+      case SOUP_SOCKET_WOULD_BLOCK:
+      default:
+	g_assert_not_reached ();
+	break;
+    }
 
-  return TRUE;
+  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 *attribute_matcher,
-              GFileQueryInfoFlags flags)
-{
-#if 0
-  GVfsBackendFtp *op_backend = G_VFS_BACKEND_FTP (backend);
-#endif
-  /* TODO */
+do_start_write (GVfsBackendFtp *ftp,
+		FtpConnection *conn,
+		GVfsJobOpenForWrite *job,
+		const char *filename,
+		GFileCreateFlags flags)
+{
+  GError *error = NULL;
+  guint status;
+
+  /* FIXME: can we honour the flags? */
+  if (!ftp_connection_ensure_data_connection (conn, &error))
+    goto error;
+
+  status = ftp_connection_send (conn,
+				RESPONSE_PASS_100 | RESPONSE_FAIL_200,
+				&error,
+                                "STOR %s", filename);
+  if (status == 0)
+    goto error;
+
+  /* don't push the connection back, it's our handle now */
+  conn->cancellable = NULL;
+  g_vfs_job_open_for_write_set_handle (job, conn);
+  g_vfs_job_open_for_write_set_can_seek (job, FALSE);
+  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_set_display_name (GVfsBackend *backend,
-                     GVfsJobSetDisplayName *job,
-                     const char *filename,
-                     const char *display_name)
-{
-#if 0
-  GVfsBackendFtp *op_backend = G_VFS_BACKEND_FTP (backend);
-#endif
-  /* TODO */
+do_create (GVfsBackend *backend,
+	   GVfsJobOpenForWrite *job,
+	   const char *filename,
+	   GFileCreateFlags flags)
+{
+  GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend);
+  FtpConnection *conn;
+  GError *error = NULL;
+
+  conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)->cancellable, &error);
+  if (conn == NULL)
+    goto error;
+
+  do_start_write (ftp, conn, job, filename, flags);
+  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_delete (GVfsBackend *backend,
-	   GVfsJobDelete *job,
-	   const char *filename)
+do_replace (GVfsBackend *backend,
+	    GVfsJobOpenForWrite *job,
+	    const char *filename,
+	    const char *etag,
+	    gboolean make_backup,
+	    GFileCreateFlags flags)
+{
+  GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend);
+  FtpConnection *conn;
+  GError *error = NULL;
+
+  if (make_backup)
+    {
+      /* FIXME: implement! */
+      g_vfs_job_failed (G_VFS_JOB (job),
+			G_IO_ERROR,
+			G_IO_ERROR_NOT_SUPPORTED,
+			_("backups not supported yet"));
+      return;
+    }
+
+  conn = g_vfs_backend_ftp_pop_connection (ftp, G_VFS_JOB (job)->cancellable, &error);
+  if (conn == NULL)
+    goto error;
+
+  do_start_write (ftp, conn, job, filename, flags);
+  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_close_write (GVfsBackend *backend,
+	        GVfsJobCloseWrite *job,
+	        GVfsBackendHandle handle)
 {
-#if 0
-  GVfsBackendFtp *op_backend = G_VFS_BACKEND_FTP (backend);
-#endif
-  /* TODO */
+  GVfsBackendFtp *ftp = G_VFS_BACKEND_FTP (backend);
+  GError *error = NULL;
+  FtpConnection *conn = handle;
+  guint response;
+
+  conn->cancellable = G_VFS_JOB (job)->cancellable;
+  ftp_connection_close_data_connection (conn);
+  response = ftp_connection_receive (conn, 0, &error); 
+  if (response == 0)
+    {
+      g_vfs_backend_ftp_push_connection (ftp, conn);
+      if (response != 0)
+	ftp_error_set_from_response (&error, response);
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return;
+    }
+
+  g_vfs_backend_ftp_push_connection (ftp, conn);
+  g_vfs_job_succeeded (G_VFS_JOB (job));
 }
 
 static void
-do_make_directory (GVfsBackend *backend,
-		   GVfsJobMakeDirectory *job,
-		   const char *filename)
+do_write (GVfsBackend *backend,
+	  GVfsJobWrite *job,
+	  GVfsBackendHandle handle,
+	  char *buffer,
+	  gsize buffer_size)
 {
-#if 0
-  GVfsBackendFtp *op_backend = G_VFS_BACKEND_FTP (backend);
-#endif
-  /* TODO */
+  GError *error = NULL;
+  FtpConnection *conn = handle;
+  SoupSocketIOStatus status;
+  gsize n_bytes;
+
+  status = soup_socket_write (conn->data,
+			      buffer,
+			      buffer_size,
+			      &n_bytes,
+			      G_VFS_JOB (job)->cancellable,
+			      &error);
+  switch (status)
+    {
+      case SOUP_SOCKET_EOF:
+      case SOUP_SOCKET_OK:
+	g_vfs_job_write_set_written_size (job, n_bytes);
+	g_vfs_job_succeeded (G_VFS_JOB (job));
+	return;
+      case SOUP_SOCKET_ERROR:
+	break;
+      case SOUP_SOCKET_WOULD_BLOCK:
+      default:
+	g_assert_not_reached ();
+	break;
+    }
+
+  g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+  g_error_free (error);
+}
+
+typedef enum {
+  FILE_INFO_DISPLAY_NAME = (1 << 0),
+  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_DISPLAY_NAME))
+    flags |= FILE_INFO_DISPLAY_NAME;
+
+  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
-do_move (GVfsBackend *backend,
-	 GVfsJobMove *job,
-	 const char *source,
-	 const char *destination,
-	 GFileCopyFlags flags,
-	 GFileProgressCallback progress_callback,
-	 gpointer progress_callback_data)
+file_info_query (FtpConnection *conn,
+		 const char *filename,
+		 GFileInfo *     info,
+		 FileInfoFlags  flags)
 {
-#if 0
-  GVfsBackendFtp *op_backend = G_VFS_BACKEND_FTP (backend);
-#endif
-  /* TODO */
+  guint response;
+
+  DEBUG ("%p query %s\n", info, filename);
+  if (flags & FILE_INFO_DISPLAY_NAME)
+    {
+      char *display_name = g_filename_display_basename (filename);
+
+      if (strstr (display_name, "\357\277\275") != NULL)
+        {
+          char *p = display_name;
+          display_name = g_strconcat (display_name, _(" (invalid encoding)"), NULL);
+          g_free (p);
+        }
+
+      g_file_info_set_display_name (info, display_name);
+      g_free (display_name);
+    }
+
+  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);
+	}
+    }
+
+  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)
+	g_file_info_set_file_type (info, G_FILE_TYPE_REGULAR);
+      else
+	g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
+    }
+}
+
+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);
+  DEBUG ("%p query %s\n", 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)
+{
+  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;
+
+  response = ftp_connection_send (conn, 
+				  RESPONSE_PASS_100 | RESPONSE_FAIL_200,
+				  &error,
+				  "NLST %s", filename);
+  if (response == 0)
+    goto error;
+
+  size = 128;
+  bytes_read = 0;
+  name = g_malloc (size);
+
+  do
+    {
+      if (bytes_read + 3 >= size)
+	{
+	  if (size >= 16384)
+	    {
+	      g_set_error (&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,
+				       "\r\n",
+				       2,
+				       &n_bytes,
+				       &got_boundary,
+				       conn->cancellable,
+				       &error);
+
+      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 - 2] = 0;
+		DEBUG ("file: %s\n", name);
+		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;
+	}
+    }
+  while (status == SOUP_SOCKET_OK);
+
+  if (bytes_read)
+    {
+      name[bytes_read] = 0;
+      DEBUG ("file: %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);
+
+  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;
+
+error2:
+  ftp_connection_close_data_connection (conn);
+  ftp_connection_receive (conn, 0, NULL);
+error:
+  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);
 }
 
 static void
@@ -453,22 +1266,14 @@
 
   backend_class->mount = do_mount;
   backend_class->try_mount = try_mount;
+  backend_class->unmount = do_unmount;
   backend_class->open_for_read = do_open_for_read;
-  backend_class->read = do_read;
-  backend_class->seek_on_read = do_seek_on_read;
   backend_class->close_read = do_close_read;
+  backend_class->read = do_read;
   backend_class->create = do_create;
-  backend_class->append_to = do_append_to;
   backend_class->replace = do_replace;
-  backend_class->write = do_write;
-  backend_class->seek_on_write = do_seek_on_write;
   backend_class->close_write = do_close_write;
+  backend_class->write = do_write;
   backend_class->query_info = do_query_info;
-  backend_class->query_fs_info = do_query_fs_info;
   backend_class->enumerate = do_enumerate;
-  backend_class->set_display_name = do_set_display_name;
-  backend_class->delete = do_delete;
-  backend_class->make_directory = do_make_directory;
-  backend_class->move = do_move;
-  backend_class->try_query_settable_attributes = try_query_settable_attributes;
 }

Modified: trunk/daemon/gvfsbackendftp.h
==============================================================================
--- trunk/daemon/gvfsbackendftp.h	(original)
+++ trunk/daemon/gvfsbackendftp.h	Thu Feb 28 10:54:49 2008
@@ -1,6 +1,6 @@
 /* GIO - GLib Input, Output and Streaming Library
  * 
- * Copyright (C) 2006-2007 Red Hat, Inc.
+ * Copyright (C) 2008 Benjamin Otte <otte gnome org>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -17,7 +17,7 @@
  * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  * Boston, MA 02111-1307, USA.
  *
- * Author: Alexander Larsson <alexl redhat com>
+ * Author: Benjamin Otte <otte gnome org>
  */
 
 #ifndef __G_VFS_BACKEND_FTP_H__



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