Index: daemon/obexftp-marshal.list
===================================================================
--- daemon/obexftp-marshal.list	(revision 2178)
+++ daemon/obexftp-marshal.list	(working copy)
@@ -1,4 +1,5 @@
 VOID:STRING
+VOID:UINT64
 VOID:STRING,STRING
 VOID:STRING,STRING,STRING
 VOID:STRING,STRING,UINT64
Index: daemon/gvfsbackendobexftp.c
===================================================================
--- daemon/gvfsbackendobexftp.c	(revision 2178)
+++ daemon/gvfsbackendobexftp.c	(working copy)
@@ -57,7 +57,8 @@
 
 #define BDADDR_LEN 17
 
-#define ASYNC_SUCCESS 1
+#define ASYNC_SUCCESS 2
+#define ASYNC_RUNNING 1
 #define ASYNC_PENDING 0
 #define ASYNC_ERROR -1
 
@@ -632,6 +633,10 @@
 
   dbus_g_proxy_add_signal(op_backend->session_proxy, "TransferStarted",
                           G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);
+  dbus_g_proxy_add_signal(op_backend->session_proxy, "TransferCompleted",
+                          G_TYPE_INVALID);
+  dbus_g_proxy_add_signal(op_backend->session_proxy, "TransferProgress",
+                          G_TYPE_UINT64, G_TYPE_INVALID);
 
   /* Now wait until the device is connected */
   count = 0;
@@ -1186,7 +1191,241 @@
   g_print ("- do_enumerate\n");
 }
 
+typedef struct {
+  GVfsBackendObexftp *op_backend;
+  GFileProgressCallback progress_callback;
+  gpointer progress_callback_data;
+  goffset total_bytes;
+} PushData;
+
 static void
+push_transfer_started_cb (DBusGProxy *proxy,
+                          const gchar *filename,
+                          const gchar *local_path,
+                          guint64 total_bytes,
+                          gpointer user_data)
+{
+  PushData *job_data = (PushData *) user_data;
+  GVfsBackendObexftp *op_backend = job_data->op_backend;
+
+  g_message ("transfer of %s to %s started", filename, local_path);
+
+  g_mutex_lock (op_backend->mutex);
+
+  op_backend->status = ASYNC_RUNNING;
+  job_data->total_bytes = (goffset) total_bytes;
+  job_data->progress_callback (0, job_data->total_bytes, job_data->progress_callback_data);
+
+  g_cond_signal (op_backend->cond);
+  g_mutex_unlock (op_backend->mutex);
+}
+
+static void
+push_transfer_completed_cb (DBusGProxy *proxy,
+                            gpointer user_data)
+{
+  PushData *job_data = (PushData *) user_data;
+  GVfsBackendObexftp *op_backend = job_data->op_backend;
+
+  g_message ("transfer completed");
+
+  g_mutex_lock (op_backend->mutex);
+
+  op_backend->status = ASYNC_SUCCESS;
+
+  g_cond_signal (op_backend->cond);
+  g_mutex_unlock (op_backend->mutex);
+}
+
+static void
+push_transfer_progress_cb (DBusGProxy *proxy,
+                           guint64 bytes_transferred,
+                           gpointer user_data)
+{
+  PushData *job_data = (PushData *) user_data;
+
+  g_message ("transfer progress");
+
+  job_data->progress_callback ((goffset) bytes_transferred, job_data->total_bytes,
+                               job_data->progress_callback_data);
+}
+
+static void
+push_data_free (PushData *job_data)
+{
+  g_object_unref (job_data->op_backend);
+  g_slice_free (PushData, job_data);
+}
+
+static void
+do_push (GVfsBackend *backend,
+         GVfsJobPush *job,
+         const char *destination,
+         const char *local_path,
+         GFileCopyFlags flags,
+         gboolean remove_source,
+         GFileProgressCallback progress_callback,
+         gpointer progress_callback_data)
+{
+  GVfsBackendObexftp *op_backend = G_VFS_BACKEND_OBEXFTP (backend);
+  GError *error = NULL;
+  char *dirname;
+  gboolean is_dir;
+  PushData *job_data;
+  int success;
+
+  g_print ("+ do_push, destination: %s, local_path: %s\n", destination, local_path);
+
+  g_mutex_lock (op_backend->mutex);
+  op_backend->doing_io = TRUE;
+
+  is_dir = g_file_test (local_path, G_FILE_TEST_IS_DIR);
+
+  if (g_vfs_job_is_cancelled (G_VFS_JOB (job)))
+    {
+      op_backend->doing_io = FALSE;
+      g_mutex_unlock (op_backend->mutex);
+
+      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+                        G_IO_ERROR_CANCELLED,
+                        _("Operation was cancelled"));
+      return;
+    }
+
+  if (is_dir)
+    {
+      op_backend->doing_io = FALSE;
+      g_mutex_unlock (op_backend->mutex);
+
+      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+                        G_IO_ERROR_IS_DIRECTORY,
+                        _("Copying directories not supported by backend"));
+      return;
+    }
+
+  dirname = g_path_get_dirname (destination);
+
+  if (_change_directory (op_backend, dirname, &error) == FALSE)
+    {
+      op_backend->doing_io = FALSE;
+      g_mutex_unlock (op_backend->mutex);
+
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_free (dirname);
+      g_error_free (error);
+      return;
+    }
+
+  g_free (dirname);
+
+  if (g_vfs_job_is_cancelled (G_VFS_JOB (job)))
+    {
+      op_backend->doing_io = FALSE;
+      g_mutex_unlock (op_backend->mutex);
+
+      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+                        G_IO_ERROR_CANCELLED,
+                        _("Operation was cancelled"));
+      return;
+    }
+
+  op_backend->status = ASYNC_PENDING;
+
+  job_data = g_slice_new0 (PushData);
+  job_data->op_backend = g_object_ref (op_backend);
+  job_data->progress_callback = progress_callback;
+  job_data->progress_callback_data = progress_callback_data;
+
+  /* connect to the transfer signals */
+  dbus_g_proxy_connect_signal (op_backend->session_proxy, "TransferStarted",
+                               G_CALLBACK (push_transfer_started_cb), job_data,
+                               NULL);
+  dbus_g_proxy_connect_signal (op_backend->session_proxy, "TransferCompleted",
+                               G_CALLBACK (push_transfer_completed_cb), job_data,
+                               NULL);
+  dbus_g_proxy_connect_signal (op_backend->session_proxy, "TransferProgress",
+                               G_CALLBACK (push_transfer_progress_cb), job_data,
+                               NULL);
+
+  if (dbus_g_proxy_call (op_backend->session_proxy, "SendFile", &error,
+                         G_TYPE_STRING, local_path, G_TYPE_INVALID,
+                         G_TYPE_INVALID) == FALSE)
+    {
+      op_backend->doing_io = FALSE;
+      g_mutex_unlock (op_backend->mutex);
+
+      dbus_g_proxy_disconnect_signal (op_backend->session_proxy, "TransferStarted", 
+                                      G_CALLBACK (push_transfer_started_cb), job_data);
+      dbus_g_proxy_disconnect_signal (op_backend->session_proxy, "TransferCompleted", 
+                                      G_CALLBACK (push_transfer_completed_cb), job_data);
+      dbus_g_proxy_disconnect_signal (op_backend->session_proxy, "TransferProgress", 
+                                      G_CALLBACK (push_transfer_progress_cb), job_data);
+      push_data_free (job_data);
+
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return;
+    }
+
+  /* wait for the TransferStarted or ErrorOccurred signal */
+  while (op_backend->status == ASYNC_PENDING)
+    g_cond_wait (op_backend->cond, op_backend->mutex);
+
+  dbus_g_proxy_disconnect_signal (op_backend->session_proxy, "TransferStarted",
+                                  G_CALLBACK (push_transfer_started_cb), job_data);
+  success = op_backend->status;
+
+  /* we either got the operation running or an error */
+  if (success == ASYNC_ERROR)
+    {
+      op_backend->doing_io = FALSE;
+      g_mutex_unlock (op_backend->mutex);
+
+      dbus_g_proxy_disconnect_signal (op_backend->session_proxy, "TransferCompleted", 
+                                      G_CALLBACK (push_transfer_completed_cb), job_data);
+      dbus_g_proxy_disconnect_signal (op_backend->session_proxy, "TransferProgress", 
+                                      G_CALLBACK (push_transfer_progress_cb), job_data);
+
+      push_data_free (job_data);
+
+      g_vfs_job_failed_from_error (G_VFS_JOB (job),
+                                   op_backend->error);
+      g_error_free (op_backend->error);
+      op_backend->error = NULL;
+      return;
+    }
+
+  while (op_backend->status == ASYNC_RUNNING)
+    g_cond_wait (op_backend->cond, op_backend->mutex);
+
+  dbus_g_proxy_disconnect_signal (op_backend->session_proxy, "TransferCompleted", 
+                                  G_CALLBACK (push_transfer_completed_cb), job_data);
+  dbus_g_proxy_disconnect_signal (op_backend->session_proxy, "TransferProgress", 
+                                  G_CALLBACK (push_transfer_progress_cb), job_data);
+  success = op_backend->status;
+
+  push_data_free (job_data);
+
+  /* same as before, either we have success or an error */
+  if (success == ASYNC_ERROR)
+    {
+      op_backend->doing_io = FALSE;
+      g_mutex_unlock (op_backend->mutex);
+
+      g_vfs_job_failed_from_error (G_VFS_JOB (job),
+                                   op_backend->error);
+      g_error_free (op_backend->error);
+      op_backend->error = NULL;
+      return;
+    }
+
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+
+  op_backend->doing_io = FALSE;
+  g_mutex_unlock (op_backend->mutex);
+}
+
+static void
 do_delete (GVfsBackend *backend,
            GVfsJobDelete *job,
            const char *filename)
@@ -1443,6 +1682,7 @@
   backend_class->enumerate = do_enumerate;
   backend_class->delete = do_delete;
   backend_class->make_directory = do_make_directory;
+  backend_class->push = do_push;
 
   /* ErrorOccurred */
   dbus_g_object_register_marshaller (obexftp_marshal_VOID__STRING_STRING,
@@ -1451,6 +1691,9 @@
   /* TransferStarted */
   dbus_g_object_register_marshaller(obexftp_marshal_VOID__STRING_STRING_UINT64,
                                     G_TYPE_NONE, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);
+  /* TransferProgress */
+  dbus_g_object_register_marshaller(obexftp_marshal_VOID__UINT64,
+                                    G_TYPE_NONE, G_TYPE_UINT64, G_TYPE_INVALID);
   /* SessionConnected */
   dbus_g_object_register_marshaller(obexftp_marshal_VOID__STRING,
                                     G_TYPE_NONE, DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID);
