public inbox for linux-bluetooth@vger.kernel.org
 help / color / mirror / Atom feed
* [Bluez-devel] [PATCH] Allow multiple files to be sent
@ 2008-01-17 10:43 Bastien Nocera
  2008-01-18 13:43 ` Bastien Nocera
  0 siblings, 1 reply; 16+ messages in thread
From: Bastien Nocera @ 2008-01-17 10:43 UTC (permalink / raw)
  To: BlueZ development

[-- Attachment #1: Type: text/plain, Size: 248 bytes --]

Heya,

Another for bluetooth-sento feature parity with gnome-obex-send.

This one adds support for sending multiple files, change the
command-line option to match those of gnome-obex-send, as well as add a
TODO list at the top of the file.

Cheers

[-- Attachment #2: bluez-gnome-sendto-multiple-files.patch --]
[-- Type: text/x-patch, Size: 8221 bytes --]

Index: main.c
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/main.c,v
retrieving revision 1.11
diff -u -p -r1.11 main.c
--- main.c	22 Dec 2007 00:09:17 -0000	1.11
+++ main.c	17 Jan 2008 10:43:01 -0000
@@ -21,6 +21,15 @@
  *
  */
 
+/* TODO
+ * - Fix passing URIs and relative paths as options
+ * - Special nice error for Palm Pilots
+ * - Support cancelling a transfer from the phone
+ * - Show the remote device name if possible
+ * - Truncate long filenames nicely for remote devices
+ * - Close button should be "Cancel" during operations
+ */
+
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
@@ -42,24 +51,40 @@ static GtkWidget *label_filename;
 static GtkWidget *label_status;
 static GtkWidget *progress;
 
-static gint filesize = -1;
+static guint64 filesize = -1;
+static guint current_file = 0;
 
-static gchar *open_file_dialog(void)
+static gchar **open_file_dialog(void)
 {
 	GtkWidget *dialog;
-	gchar *filename = NULL;
+	GSList *list, *l;
+	gchar **filenames = NULL;
 
 	dialog = gtk_file_chooser_dialog_new(_("Select File"), NULL,
 				GTK_FILE_CHOOSER_ACTION_OPEN,
 				GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
 				GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
+	gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
 
-	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
-		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+		GPtrArray *array;
+
+		list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
+		if (list == NULL) {
+			gtk_widget_destroy(dialog);
+			return NULL;
+		}
+		array = g_ptr_array_new();
+		for (l = list; l != NULL; l = l->next)
+			g_ptr_array_add(array, l->data);
+		g_slist_free(list);
+
+		filenames = (char **) array;
+	}
 
 	gtk_widget_destroy(dialog);
 
-	return filename;
+	return filenames;
 }
 
 static void selected_device_changed(BluetoothDeviceSelection *selector,
@@ -114,11 +139,32 @@ static void response_callback(GtkWidget 
 	gtk_main_quit();
 }
 
+static void set_filename(GtkWidget *label, const gchar *filename)
+{
+	gchar *display, *text;
+
+	if (g_str_has_prefix(filename, "file:///") != FALSE) {
+		gchar *local;
+
+		local = g_filename_from_uri(filename, NULL, NULL);
+		display = g_filename_display_basename(local);
+		g_free (local);
+	} else if (filename[0] == '/') {
+		display = g_filename_display_basename(filename);
+	} else {
+		display = g_filename_display_name(filename);
+	}
+
+	text = g_strdup_printf("<b>%s</b>", display);
+	g_free(display);
+	gtk_label_set_markup(GTK_LABEL(label), text);
+	g_free(text);
+}
+
 static void create_window(const gchar *filename)
 {
 	GtkWidget *vbox;
 	GtkWidget *label;
-	gchar *text;
 
 	dialog = gtk_dialog_new_with_buttons(_("File Transfer"), NULL,
 				GTK_DIALOG_NO_SEPARATOR,
@@ -147,9 +193,7 @@ static void create_window(const gchar *f
 
 	label_status = label;
 
-	text = g_strdup_printf("<b>%s</b>", filename);
-	gtk_label_set_markup(GTK_LABEL(label_filename), text);
-	g_free(text);
+	set_filename(label_filename, filename);
 
 	gtk_label_set_markup(GTK_LABEL(label_status), _("Connecting..."));
 
@@ -181,7 +225,7 @@ static void transfer_progress(DBusGProxy
 	gchar *text;
 	gdouble fraction;
 
-	text = g_strdup_printf(_("Transfered %d of %d bytes"), bytes, filesize);
+	text = g_strdup_printf(_("Transfered %d of %"G_GUINT64_FORMAT" bytes"), bytes, filesize);
 	gtk_label_set_markup(GTK_LABEL(label_status), text);
 	g_free(text);
 
@@ -191,30 +235,51 @@ static void transfer_progress(DBusGProxy
 
 static void transfer_completed(DBusGProxy *proxy, gpointer user_data)
 {
+	gchar **filenames = user_data;
+
 	gtk_label_set_markup(GTK_LABEL(label_status), _("Completed"));
 
 	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 1.0);
 
-	dbus_g_proxy_call(proxy, "Disconnect", NULL, G_TYPE_INVALID,
-							G_TYPE_INVALID);
+	current_file++;
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
+	/* Any more files to process? */
+	if (filenames[current_file] != NULL) {
+		GError *error = NULL;
+
+		//FIXME process filename to a proper local path
+		set_filename(label_filename, filenames[current_file]);
+		dbus_g_proxy_call(proxy, "SendFile", &error,
+					G_TYPE_STRING, filenames[current_file], G_TYPE_INVALID,
+								G_TYPE_INVALID);
+
+		if (error != NULL) {
+			g_printerr("Sending of file %s failed: %s\n", filenames[current_file],
+							error->message);
+			g_error_free(error);
+			gtk_main_quit();
+		}
+	} else {
+		dbus_g_proxy_call(proxy, "Disconnect", NULL, G_TYPE_INVALID,
+						G_TYPE_INVALID);
+		gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
 						GTK_RESPONSE_CLOSE, TRUE);
+	}
 }
 
 static void session_connected(DBusGProxy *proxy, gpointer user_data)
 {
-	gchar *filename = user_data;
+	gchar **filenames = user_data;
 	GError *error = NULL;
 
 	gtk_label_set_markup(GTK_LABEL(label_status), _("Connected"));
 
 	dbus_g_proxy_call(proxy, "SendFile", &error,
-				G_TYPE_STRING, filename, G_TYPE_INVALID,
+				G_TYPE_STRING, filenames[current_file], G_TYPE_INVALID,
 							G_TYPE_INVALID);
 
 	if (error != NULL) {
-		g_printerr("Sending of file %s failed: %s\n", filename,
+		g_printerr("Sending of file %s failed: %s\n", filenames[current_file],
 							error->message);
 		g_error_free(error);
 		gtk_main_quit();
@@ -261,7 +326,7 @@ static void create_notify(DBusGProxy *pr
 				G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
 
 	dbus_g_proxy_connect_signal(proxy, "TransferStarted",
-				G_CALLBACK(transfer_started), NULL, NULL);
+				G_CALLBACK(transfer_started), user_data, NULL);
 
 	dbus_g_proxy_add_signal(proxy, "TransferProgress",
 						G_TYPE_UINT, G_TYPE_INVALID);
@@ -272,17 +337,20 @@ static void create_notify(DBusGProxy *pr
 	dbus_g_proxy_add_signal(proxy, "TransferCompleted", G_TYPE_INVALID);
 
 	dbus_g_proxy_connect_signal(proxy, "TransferCompleted",
-				G_CALLBACK(transfer_completed), NULL, NULL);
+				G_CALLBACK(transfer_completed), user_data, NULL);
 
 	dbus_g_proxy_call(proxy, "Connect", NULL, G_TYPE_INVALID,
 							G_TYPE_INVALID);
 }
 
 static gchar *option_device = NULL;
+static gchar **filenames = NULL;
 
 static GOptionEntry options[] = {
-	{ "device", 0, 0, G_OPTION_ARG_STRING, &option_device,
-				N_("Remote device to use"), "ADDRESS" },
+	{ "dest", 'd', 0, G_OPTION_ARG_STRING, &option_device,
+		N_("Remote device to use"), "ADDRESS" },
+	{ G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames,
+		N_("Files to send"), NULL},
 	{ NULL },
 };
 
@@ -290,7 +358,7 @@ int main(int argc, char *argv[])
 {
 	DBusGProxy *proxy;
 	GError *error;
-	gchar *filename, *address;
+	gchar *address;
 
 	bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
 	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
@@ -298,7 +366,7 @@ int main(int argc, char *argv[])
 
 	error = NULL;
 
-	if (gtk_init_with_args(&argc, &argv, "[FILE...]",
+	if (gtk_init_with_args(&argc, &argv, "[FILENAMES...]",
 				options, GETTEXT_PACKAGE, &error) == FALSE) {
 		if (error != NULL) {
 			g_printerr("%s\n", error->message);
@@ -311,17 +379,15 @@ int main(int argc, char *argv[])
 
 	gtk_window_set_default_icon_name("stock_bluetooth");
 
-	if (argc < 2) {
-		filename = open_file_dialog();
-		if (filename == NULL)
+	if (filenames == NULL) {
+		filenames = open_file_dialog();
+		if (filenames == NULL)
 			gtk_exit(1);
-	} else
-		filename = g_strdup(argv[1]);
+	}
 
 	if (option_device == NULL) {
 		address = browse_device_dialog();
 		if (address == NULL) {
-			g_free(filename);
 			gtk_exit(1);
 		}
 	} else
@@ -345,22 +411,22 @@ int main(int argc, char *argv[])
 		gtk_exit(1);
 	}
 
-	create_window(filename);
+	create_window(filenames[0]);
 
 	proxy = dbus_g_proxy_new_for_name(conn, "org.openobex",
 				"/org/openobex", "org.openobex.Manager");
 
 	dbus_g_proxy_begin_call(proxy, "CreateBluetoothSession",
-				create_notify, filename, NULL,
+				create_notify, filenames, NULL,
 				G_TYPE_STRING, address, G_TYPE_STRING, "opp",
-				G_TYPE_BOOLEAN, FALSE, G_TYPE_INVALID);
+				G_TYPE_INVALID);
 
 	gtk_main();
 
 	dbus_g_connection_unref(conn);
 
 	g_free(address);
-	g_free(filename);
+	g_strfreev(filenames);
 
 	return 0;
 }

[-- Attachment #3: Type: text/plain, Size: 228 bytes --]

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/

[-- Attachment #4: Type: text/plain, Size: 164 bytes --]

_______________________________________________
Bluez-devel mailing list
Bluez-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bluez-devel

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Bluez-devel] [PATCH] Allow multiple files to be sent
  2008-01-17 10:43 [Bluez-devel] [PATCH] Allow multiple files to be sent Bastien Nocera
@ 2008-01-18 13:43 ` Bastien Nocera
  2008-02-01 13:14   ` Bastien Nocera
  0 siblings, 1 reply; 16+ messages in thread
From: Bastien Nocera @ 2008-01-18 13:43 UTC (permalink / raw)
  To: BlueZ development

[-- Attachment #1: Type: text/plain, Size: 544 bytes --]

Hello again,

On Thu, 2008-01-17 at 10:43 +0000, Bastien Nocera wrote: 
> Heya,
> 
> Another for bluetooth-sento feature parity with gnome-obex-send.

Another patch, based on Tadas' gnome-obex-send port.

- Fix to run with the latest obex-data-server (in 2 places)
- Add an appropriate error when the target device is a Palm
- Accept relative local paths and URIs as well as absolute filenames as
arguments (needed for ease of use and nautilus-sendto, respectively)
- Gather the name of the Bluetooth device from hcid to show in the UI

Cheers

[-- Attachment #2: bluez-gnome-new-sendto-1.patch --]
[-- Type: text/x-patch, Size: 37182 bytes --]

Index: Makefile.am
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/Makefile.am,v
retrieving revision 1.7
diff -u -p -r1.7 Makefile.am
--- Makefile.am	29 Aug 2007 10:05:28 -0000	1.7
+++ Makefile.am	18 Jan 2008 13:41:57 -0000
@@ -1,5 +1,5 @@
 
-noinst_PROGRAMS = bluetooth-sendto
+bin_PROGRAMS = bluetooth-sendto
 
 bluetooth_sendto_SOURCES = main.c
 
@@ -8,7 +8,7 @@ bluetooth_sendto_LDADD = $(top_builddir)
 
 AM_CFLAGS = @DBUS_CFLAGS@ @GTK_CFLAGS@
 
-BUILT_SOURCES = marshal.h marshal.c
+BUILT_SOURCES = sendto-marshal.h sendto-marshal.c
 
 nodist_bluetooth_sendto_SOURCES = $(BUILT_SOURCES)
 
@@ -22,8 +22,8 @@ EXTRA_DIST = $(noinst_MANS) marshal.list
 
 MAINTAINERCLEANFILES = Makefile.in
 
-marshal.h: marshal.list
-	$(GLIB_GENMARSHAL) --prefix=marshal $< --header > $@
+sendto-marshal.h: marshal.list
+	$(GLIB_GENMARSHAL) --prefix=sendto_marshal $< --header > $@
 
-marshal.c: marshal.list
-	$(GLIB_GENMARSHAL) --prefix=marshal $< --body > $@
+sendto-marshal.c: marshal.list
+	$(GLIB_GENMARSHAL) --prefix=sendto_marshal $< --body > $@
Index: main.c
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/main.c,v
retrieving revision 1.11
diff -u -p -r1.11 main.c
--- main.c	22 Dec 2007 00:09:17 -0000	1.11
+++ main.c	18 Jan 2008 13:41:58 -0000
@@ -1,366 +1,856 @@
 /*
+ * bluetooth-sendto
+ * 
+ * Copyright (c) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ * Copyright (c) 2007 Bastien Nocera <hadess@hadess.net>
  *
- *  BlueZ - Bluetooth protocol stack for Linux
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
  *
- *  Copyright (C) 2005-2007  Marcel Holtmann <marcel@holtmann.org>
- *
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
  *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
 
-#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
 
+#include <dbus/dbus-glib.h>
+#include <glib.h>
 #include <glib/gi18n.h>
-
+#include <glib/gstdio.h>
 #include <gtk/gtk.h>
 
 #include "bluetooth-device-selection.h"
+#include "dbus-glue.h"
+#include "sendto-marshal.h"
 
-#include "marshal.h"
+typedef struct _appinfo {
+	/* DBus */
+	DBusGConnection *connection;
+	DBusGProxy *manager_proxy;
+	DBusGProxy *session_proxy;
+	/* GTK */
+	GtkWidget *main_dialog;
+	GtkWidget *from_label;
+	GtkWidget *operation_label;
+	GtkWidget *progress_bar;
+	/* sending related */
+	gsize file_length;
+	guint file_count;
+	guint current_file;
+	GSList *file_list;
+	guint byte_count;
+	guint bytes_sent;
+	gint64 first_transfer_time;
+	gint64 last_update_time;
+} BluetoothSendto;
+
+typedef enum {
+	BUTTONS_OK,
+	BUTTONS_RETRY_CANCEL,
+	BUTTONS_RETRY_SKIP_CANCEL
+} ButtonSet;
+
+static gchar **files_to_send = NULL;
+static gchar *bdaddrstr = NULL;
+static gchar *device_name = NULL;
+
+#define DIALOG_RESPONSE_SKIP 1
+#define DIALOG_RESPONSE_RETRY 2
+
+static gboolean send_one (BluetoothSendto *app);
+
+static void cancel_clicked_cb (GtkWidget *button, BluetoothSendto *app);
+static void session_created_cb (DBusGProxy *proxy, DBusGProxyCall *call,
+				BluetoothSendto *app);
+static void selected_device_changed_cb (BluetoothDeviceSelection *selector,
+					gchar *address, gpointer user_data);
+
+static const GOptionEntry options[] = {
+	{"dest", 'd', 0, G_OPTION_ARG_STRING, &bdaddrstr, 
+		"Bluetooth address of destination device", "BDADDR"},
+	{G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &files_to_send},
+	{NULL}
+};
 
-static DBusGConnection *conn = NULL;
+static gint64
+get_system_time (void)
+{
+	struct timeval tmp;
 
-static GtkWidget *dialog;
-static GtkWidget *label_filename;
-static GtkWidget *label_status;
-static GtkWidget *progress;
+	gettimeofday (&tmp, NULL);
+	return (gint64)tmp.tv_usec + (gint64)tmp.tv_sec * G_GINT64_CONSTANT (1000000);
+}
 
-static gint filesize = -1;
+static gboolean
+is_palm_device (const gchar *bdaddr)
+{
+	return (g_str_has_prefix (bdaddr, "00:04:6B")
+		|| g_str_has_prefix (bdaddr, "00:07:E0")
+		|| g_str_has_prefix (bdaddr, "00:0E:20"));
+}
 
-static gchar *open_file_dialog(void)
+/* Return the local absolute filename for the file, and its size
+ * if the file exists and is stat()'able */
+static gchar *
+normalise_filename (const gchar *filename, gint64 *size)
 {
-	GtkWidget *dialog;
-	gchar *filename = NULL;
+	char *ret;
+	struct stat file_stat;
 
-	dialog = gtk_file_chooser_dialog_new(_("Select File"), NULL,
-				GTK_FILE_CHOOSER_ACTION_OPEN,
-				GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
-				GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
+	g_return_val_if_fail (filename != NULL, NULL);
+	g_return_val_if_fail (size != NULL, NULL);
 
-	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
-		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+	if (g_str_has_prefix (filename, "file://") != FALSE) {
+		ret = g_filename_from_uri (filename, NULL, NULL);
+		if (ret == NULL)
+			return NULL;
+	} else if (filename[0] == '/') {
+		ret = g_strdup (filename);
+	} else {
+		gchar *curdir;
 
-	gtk_widget_destroy(dialog);
+		curdir = g_get_current_dir ();
+		ret = g_build_filename (curdir, filename, NULL);
+		g_free (curdir);
+	}
 
-	return filename;
-}
+	if (g_file_test (ret, G_FILE_TEST_IS_REGULAR) == FALSE) {
+		g_free (ret);
+		return NULL;
+	}
 
-static void selected_device_changed(BluetoothDeviceSelection *selector,
-					gchar *address, gpointer user_data)
-{
-	GtkWidget *dialog = user_data;
+	if (g_stat (ret, &file_stat) == 0) {
+		*size = file_stat.st_size;
+	} else {
+		g_free (ret);
+		return NULL;
+	}
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-				GTK_RESPONSE_ACCEPT, address != NULL);
+	return ret;
 }
 
-static gchar *browse_device_dialog(void)
+static gchar *
+get_device_name (BluetoothSendto *app)
 {
-	GtkWidget *dialog;
-	GtkWidget *selector;
-	gchar *address = NULL;
+	DBusGConnection *connection;
+	DBusGProxy *manager;
+	gchar *name, **adapters;
+	guint i;
 
-	dialog = gtk_dialog_new_with_buttons(_("Select Device"),
-				NULL, GTK_DIALOG_NO_SEPARATOR,
-				GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
-				GTK_STOCK_CONNECT, GTK_RESPONSE_ACCEPT, NULL);
+	name = NULL;
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_ACCEPT, FALSE);
+	connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, NULL);
+	if (connection == NULL)
+		return NULL;
 
-	gtk_window_set_default_size(GTK_WINDOW(dialog), 450, 300);
+	manager = dbus_g_proxy_new_for_name (connection, "org.bluez",
+					    "/org/bluez", "org.bluez.Manager");
+	if (manager == NULL) {
+		dbus_g_connection_unref (connection);
+		return NULL;
+	}
 
-	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
+	GError *error = NULL;
+	if (manager_list_adapters (manager, &adapters, &error) == FALSE) {
+		g_object_unref (manager);
+		dbus_g_connection_unref (connection);
+		return NULL;
+	}
 
-	selector = bluetooth_device_selection_new(_("Select destination device"));
+	for (i = 0; adapters[i] != NULL; i++) {
+		DBusGProxy *adapter;
 
-	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), selector);
+		adapter = dbus_g_proxy_new_for_name (connection, "org.bluez",
+						    adapters[i], "org.bluez.Adapter");
+		if (dbus_g_proxy_call (adapter, "GetRemoteName", NULL,
+				   G_TYPE_STRING, bdaddrstr, G_TYPE_INVALID,
+				   G_TYPE_STRING, &name, G_TYPE_INVALID) != FALSE) {
+			if (name != NULL && name[0] != '\0') {
+				g_object_unref (adapter);
+				break;
+			}
+		}
+		g_object_unref (adapter);
+	}
 
-	g_signal_connect(selector, "selected-device-changed",
-				G_CALLBACK(selected_device_changed), dialog);
+	g_object_unref (manager);
+	dbus_g_connection_unref (connection);
 
-	gtk_widget_show_all(selector);
+	return name;
+}
 
-	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
-		g_object_get(selector, "device-selected", &address, NULL);
+static gint
+show_error_dialog (GtkWindow *parent, ButtonSet buttons,
+		   const gchar *primary_text, const gchar *secondary_text)
+{
+	GtkWidget *dialog;
+	gchar *primary_text_markup;
+	gint ret;
 
-	gtk_widget_destroy(dialog);
+	primary_text_markup = g_strdup_printf ("<span weight=\"bold\" size=\"larger\">%s</span>", 
+					       primary_text);
+	dialog = gtk_message_dialog_new_with_markup (parent, 0, GTK_MESSAGE_ERROR, 
+						     buttons==BUTTONS_OK? GTK_BUTTONS_OK: GTK_BUTTONS_CANCEL, 
+						     primary_text_markup);
+	gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+						  secondary_text);
+	if (buttons == BUTTONS_RETRY_SKIP_CANCEL) {
+		gtk_dialog_add_button (GTK_DIALOG (dialog), _("Skip"),
+				       DIALOG_RESPONSE_SKIP);
+	}
+	if (buttons != BUTTONS_OK) {
+		gtk_dialog_add_button (GTK_DIALOG (dialog), _("Retry"),
+				       DIALOG_RESPONSE_RETRY);
+	}
+	ret = gtk_dialog_run (GTK_DIALOG (dialog));
 
-	return address;
+	gtk_widget_destroy (dialog);
+	g_free (primary_text_markup);
+	return ret;
+}
+
+static gint
+show_connection_error_dialog (GtkWindow *parent, const gchar *bdaddr,
+			      const gchar *primary_text)
+{
+	if (is_palm_device (bdaddrstr) == FALSE) {
+		return show_error_dialog (parent,
+					  BUTTONS_RETRY_CANCEL,
+					  primary_text,
+					  _("Make sure that remote device is on and that it accepts Bluetooth connections."));
+	} else {
+		return show_error_dialog (parent,
+					  BUTTONS_RETRY_CANCEL,
+					  primary_text,
+					  _("Make sure \"Beam receive\" is enabled in the Power preferences, and Bluetooth is enabled, on your Palm device."));
+	}
 }
 
-static void response_callback(GtkWidget *dialog,
-					gint response, gpointer user_data)
-{
-	gtk_widget_destroy(dialog);
-
-	gtk_main_quit();
+static gint
+handle_file_sending_error (const gchar *error_text,
+			   const gchar *error_secondary_text, BluetoothSendto *app)
+{
+	ButtonSet buttons;
+	gint response_id;
+
+	if (app->file_count > 1 && app->current_file < app->file_count-1)
+		buttons = BUTTONS_RETRY_SKIP_CANCEL;
+	else
+		buttons = BUTTONS_RETRY_CANCEL;
+
+	response_id = show_error_dialog (GTK_WINDOW (app->main_dialog), buttons, 
+					 error_text, error_secondary_text);
+
+	switch (response_id) {
+	case DIALOG_RESPONSE_SKIP:
+		app->current_file++;
+		app->bytes_sent += app->file_length;
+	case DIALOG_RESPONSE_RETRY:
+		return TRUE;
+	case GTK_RESPONSE_CANCEL:
+	default:
+		gtk_main_quit ();
+		break;
+	}
+	return response_id;
 }
 
-static void create_window(const gchar *filename)
+static gchar *
+browse_device_dialog (void)
 {
-	GtkWidget *vbox;
-	GtkWidget *label;
-	gchar *text;
-
-	dialog = gtk_dialog_new_with_buttons(_("File Transfer"), NULL,
-				GTK_DIALOG_NO_SEPARATOR,
-				GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
-
-	gtk_window_set_default_size(GTK_WINDOW(dialog), 400, -1);
+	GtkWidget *dialog;
+	GtkWidget *selector;
+	gchar *address = NULL;
 
-	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
+	dialog = gtk_dialog_new_with_buttons (_("Select Device"),
+					      NULL, GTK_DIALOG_NO_SEPARATOR,
+					      GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+					      GTK_STOCK_CONNECT, GTK_RESPONSE_ACCEPT, NULL);
 
-	vbox = gtk_vbox_new(FALSE, 6);
-	gtk_container_set_border_width(GTK_CONTAINER(vbox), 12);
-	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox);
+	gtk_window_set_icon_name (GTK_WINDOW (dialog), "stock_bluetooth");
 
-	label = gtk_label_new(NULL);
-	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+	gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+					   GTK_RESPONSE_ACCEPT, FALSE);
 
-	label_filename = label;
+	gtk_window_set_default_size (GTK_WINDOW (dialog), 450, 300);
 
-	progress = gtk_progress_bar_new();
-	gtk_box_pack_start(GTK_BOX(vbox), progress, FALSE, FALSE, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (dialog), 6);
 
-	label = gtk_label_new(NULL);
-	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 6);
+	selector = bluetooth_device_selection_new (_("Select destination device"));
+	bluetooth_device_selection_start_discovery (
+	    BLUETOOTH_DEVICE_SELECTION (selector));
 
-	label_status = label;
+	gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), selector);
 
-	text = g_strdup_printf("<b>%s</b>", filename);
-	gtk_label_set_markup(GTK_LABEL(label_filename), text);
-	g_free(text);
+	g_signal_connect (selector, "selected-device-changed",
+			  G_CALLBACK (selected_device_changed_cb), dialog);
 
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Connecting..."));
+	gtk_widget_show_all (selector);
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, FALSE);
+	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
+		g_object_get (selector, "device-selected", &address, NULL);
 
-	g_signal_connect(G_OBJECT(dialog), "response",
-				G_CALLBACK(response_callback), NULL);
+	gtk_widget_destroy (dialog);
 
-	gtk_widget_show_all(dialog);
+	return address;
 }
 
-static void transfer_started(DBusGProxy *proxy, gchar *a, gchar *b,
-						gint size, gpointer user_data)
+static void
+ui_init (BluetoothSendto *app)
 {
+	GtkWidget *vbox, *vbox_parent, *table;
+	GtkWidget *label1, *label2, *label3, *target_label, *from_label, *operation_label;
+	GtkWidget *progress_bar, *button;
 	gchar *text;
 
-	filesize = size;
+	/* initialize UI */
+
+	device_name = get_device_name (app);
 
-	text = g_strdup_printf(_("Starting transfer of %d bytes"), size);
-	gtk_label_set_markup(GTK_LABEL(label_status), text);
-	g_free(text);
+	/* main dialog */
+	app->main_dialog = gtk_dialog_new ();
+	gtk_dialog_set_has_separator (GTK_DIALOG (app->main_dialog), FALSE);
+	gtk_window_set_title (GTK_WINDOW (app->main_dialog),
+			      _("Bluetooth file transfer"));
+	gtk_window_set_icon_name (GTK_WINDOW (app->main_dialog), "stock_bluetooth");
+	gtk_window_set_type_hint (GTK_WINDOW (app->main_dialog),
+				  GDK_WINDOW_TYPE_HINT_NORMAL);
+	gtk_window_set_position (GTK_WINDOW (app->main_dialog), GTK_WIN_POS_CENTER);
+	gtk_window_set_resizable (GTK_WINDOW (app->main_dialog), FALSE);
+	gtk_widget_set (app->main_dialog, "width-request", 400, NULL);
+	button = gtk_dialog_add_button (GTK_DIALOG (app->main_dialog),
+					GTK_STOCK_CANCEL,
+					GTK_RESPONSE_CANCEL);
+	g_signal_connect (G_OBJECT (button), "clicked",
+			  G_CALLBACK (cancel_clicked_cb), app);
+	gtk_container_set_border_width (GTK_CONTAINER (app->main_dialog), 6);
+
+	/* table elements */
+	from_label = gtk_label_new (NULL);
+	gtk_misc_set_alignment (GTK_MISC (from_label), 0, 0.5);
+	gtk_label_set_ellipsize (GTK_LABEL (from_label), PANGO_ELLIPSIZE_MIDDLE);
+	target_label = gtk_label_new (NULL);
+	gtk_misc_set_alignment (GTK_MISC (target_label), 0, 0.5);
+	gtk_label_set_ellipsize (GTK_LABEL (target_label), PANGO_ELLIPSIZE_END);
+	gtk_label_set_text (GTK_LABEL (target_label), device_name ? device_name : bdaddrstr);
+	label2 = gtk_label_new (NULL);
+	text = g_markup_printf_escaped ("<b>%s</b>", _("From:"));
+	gtk_label_set_markup (GTK_LABEL (label2), text);
+	g_free (text);
+	label3 = gtk_label_new (NULL);
+	gtk_misc_set_alignment (GTK_MISC (label3), 1, 0.5);
+	text = g_markup_printf_escaped ("<b>%s</b>", _("To:"));
+	gtk_label_set_markup (GTK_LABEL (label3), text);
+	g_free (text);
+
+	/* table */
+	table = gtk_table_new (2, 2, FALSE);
+	gtk_table_set_col_spacings (GTK_TABLE (table), 4);
+	gtk_table_set_row_spacings (GTK_TABLE (table), 4);
+	gtk_table_attach_defaults (GTK_TABLE (table), from_label, 1, 2, 0, 1);
+	gtk_table_attach_defaults (GTK_TABLE (table), target_label, 1, 2, 1, 2);
+	gtk_table_attach (GTK_TABLE (table), label2, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+	gtk_table_attach (GTK_TABLE (table), label3, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+
+	/* vbox elements */
+	label1 = gtk_label_new (NULL);
+	gtk_misc_set_alignment (GTK_MISC (label1), 0, 0.5);
+	text = g_markup_printf_escaped ("<span weight=\"bold\" size=\"larger\">%s</span>",
+					_("Sending files via bluetooth"));
+	gtk_label_set_markup (GTK_LABEL (label1), text);
+	g_free (text);
+	progress_bar = gtk_progress_bar_new ();
+	gtk_progress_bar_set_ellipsize (GTK_PROGRESS_BAR (progress_bar),
+					PANGO_ELLIPSIZE_END);
+	gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress_bar),
+				   _("Connecting..."));
+	operation_label = gtk_label_new (NULL);
+	gtk_misc_set_alignment (GTK_MISC (operation_label), 0, 0.5);
+	gtk_label_set_ellipsize (GTK_LABEL (operation_label), PANGO_ELLIPSIZE_END);
+
+	/* vbox */
+	vbox = gtk_vbox_new (FALSE, 0);
+	gtk_box_set_spacing (GTK_BOX (vbox), 6);
+	gtk_container_set_border_width (GTK_CONTAINER (vbox), 6);
+	gtk_box_pack_start_defaults (GTK_BOX (vbox), label1);
+	gtk_box_pack_start (GTK_BOX (vbox), table, TRUE, TRUE, 9);
+	gtk_box_pack_start_defaults (GTK_BOX (vbox), progress_bar);
+	gtk_box_pack_start (GTK_BOX (vbox), operation_label, TRUE, TRUE, 2);
+	vbox_parent = gtk_bin_get_child (GTK_BIN (app->main_dialog));
+	gtk_box_pack_start_defaults (GTK_BOX (vbox_parent), vbox);
+
+	/* Set widget variables in app */
+	app->from_label = from_label;
+	app->operation_label = operation_label;
+	app->progress_bar = progress_bar;
+
+	/* show it */
+	gtk_widget_show_all (app->main_dialog);
+}
+
+static gboolean
+send_one (BluetoothSendto *app)
+{
+	const gchar *fname;
+	gint ret;
+	gchar *bname, *dirname;
+	GError *err = NULL;
+	gchar *operation_text, *operation_markup, *progressbar_text;
+
+	if ((fname = g_slist_nth_data (app->file_list, app->current_file))) {
+		/* there's a file to send */
+		dbus_g_proxy_call (app->session_proxy, "SendFile", &err,
+				   G_TYPE_STRING, fname, G_TYPE_INVALID, G_TYPE_INVALID);
+
+		if (err != NULL) {
+			ret = handle_file_sending_error (_("Unable to read file"),
+							 err->message, app);
+			if (ret != GTK_RESPONSE_CANCEL) {
+				g_idle_add ((GSourceFunc) send_one, app);
+			}
+			g_error_free (err);
+			return FALSE;
+		}
+
+		if (app->first_transfer_time == 0) {
+			app->first_transfer_time = get_system_time();
+		}
+		bname = g_path_get_basename (fname);
+		dirname = g_path_get_dirname (fname);
 
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0.0);
+		operation_text = g_strdup_printf (_("Sending %s"), bname);
+		operation_markup = g_markup_printf_escaped ("<i>%s</i>", operation_text);
+		progressbar_text = g_strdup_printf (_("Sending file: %d of %d"),
+						    app->current_file+1,
+						    app->file_count);
+		gtk_label_set_text (GTK_LABEL (app->from_label), dirname);
+		gtk_label_set_markup (GTK_LABEL (app->operation_label), operation_markup);
+		gtk_progress_bar_set_text (GTK_PROGRESS_BAR (app->progress_bar),
+					   progressbar_text);
+
+		g_free (bname);
+		g_free (dirname);
+		g_free (operation_text);
+		g_free (operation_markup);
+		g_free (progressbar_text);
+	} else {
+		/* nothing left to send, time to quit */
+		gtk_main_quit ();
+	}
+	return FALSE;
 }
 
-static void transfer_progress(DBusGProxy *proxy, guint bytes, gpointer user_data)
+static void
+cancel_clicked_cb (GtkWidget *button, BluetoothSendto *app)
 {
-	gchar *text;
-	gdouble fraction;
+	gtk_main_quit ();
+}
 
-	text = g_strdup_printf(_("Transfered %d of %d bytes"), bytes, filesize);
-	gtk_label_set_markup(GTK_LABEL(label_status), text);
-	g_free(text);
+static void
+session_connected_cb (DBusGProxy *proxy, BluetoothSendto *app)
+{
+	/* OBEX connect has succeeded, so start sending */
+	g_idle_add ((GSourceFunc) send_one, app);
+}
 
-	fraction = (gdouble) bytes / (gdouble) filesize;
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), fraction);
+static void
+transfer_started_cb (DBusGProxy *proxy, const gchar *filename,
+		     const gchar *local_path, gint byte_count, BluetoothSendto *app)
+{
+	app->file_length = byte_count;
 }
 
-static void transfer_completed(DBusGProxy *proxy, gpointer user_data)
+static void
+transfer_cancelled_cb (DBusGProxy *proxy, BluetoothSendto *app)
 {
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Completed"));
+	gint ret;
+	gchar *error_text;
 
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 1.0);
+	error_text = g_strdup_printf (_("An error occured while sending file '%s'"),
+				      (gchar *)g_slist_nth_data (app->file_list, app->current_file));
+	ret = handle_file_sending_error (error_text,
+					 _("The remote device cancelled the transfer"), app);
 
-	dbus_g_proxy_call(proxy, "Disconnect", NULL, G_TYPE_INVALID,
-							G_TYPE_INVALID);
+	if (ret != GTK_RESPONSE_CANCEL)
+		g_idle_add ((GSourceFunc) send_one, app);
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, TRUE);
+	g_free (error_text);
 }
 
-static void session_connected(DBusGProxy *proxy, gpointer user_data)
+static void
+error_occurred_cb (DBusGProxy *proxy, const gchar *error_name,	
+		   const gchar *error_message, BluetoothSendto *app)
 {
-	gchar *filename = user_data;
-	GError *error = NULL;
+	gint ret;
+	gboolean link_error = FALSE;
+	gchar *error_text;
+	const gchar *reason;
 
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Connected"));
+	g_message ("ErrorOccurred");
+	g_message ("Error name: %s", error_name);
+	g_message ("Error message: %s", error_message);
 
-	dbus_g_proxy_call(proxy, "SendFile", &error,
-				G_TYPE_STRING, filename, G_TYPE_INVALID,
-							G_TYPE_INVALID);
-
-	if (error != NULL) {
-		g_printerr("Sending of file %s failed: %s\n", filename,
-							error->message);
-		g_error_free(error);
-		gtk_main_quit();
+	error_text = g_strdup_printf (_("An error occured while sending file '%s'"),
+				      (gchar *)g_slist_nth_data(app->file_list, app->current_file));
+	if (strcmp (error_name, "org.openobex.Error.LinkError") == 0) {
+		/* Connection to remote device was lost */
+		g_object_unref (G_OBJECT (app->session_proxy));
+		reason = _("Connection to remote device was lost");
+		link_error = TRUE;
+	} else if (strcmp (error_name, "org.openobex.Error.Forbidden") == 0) {
+		reason = _("Remote device rejected file");
+	} else {
+		reason = _("Unknown error");
+	}
+	ret = handle_file_sending_error (error_text, reason, app);
+
+	if (ret != GTK_RESPONSE_CANCEL) {
+		if (link_error) {
+			/* Need to reestablish connection */
+			dbus_g_proxy_begin_call (app->manager_proxy, "CreateBluetoothSession",
+						 (DBusGProxyCallNotify) session_created_cb, app, NULL,
+						 G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+						 G_TYPE_INVALID);
+		} else {
+			g_idle_add ((GSourceFunc) send_one, app);
+		}
+	}
+
+
+	g_free (error_text);
+}
+
+static void
+transfer_progress_cb (DBusGProxy *proxy, gint bytes_transferred, BluetoothSendto *app)
+{
+	gdouble frac;
+	guint actual_bytes_sent;
+	gint elapsed_time;
+	gint transfer_rate;
+	gint time_remaining;
+	gint64 current_time;
+	gchar *str, *str2;
+	gchar *progressbar_text;
+
+	/* update progress bar fraction */
+	actual_bytes_sent = app->bytes_sent + bytes_transferred;
+	frac = (gdouble)actual_bytes_sent / app->byte_count;
+	gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (app->progress_bar), frac);
+
+	/* update progress bar text (time remaining)
+	 * (only update once in a second) */
+	current_time = get_system_time();
+	elapsed_time = (current_time - app->first_transfer_time) / 1000000;
+
+	if (app->last_update_time != 0) {
+		if (current_time < app->last_update_time + 1000000)
+			return;
+		else
+			app->last_update_time = current_time;
+	} else {
+		app->last_update_time = current_time;
 	}
+
+	if (elapsed_time == 0)
+		return;
+
+	transfer_rate = actual_bytes_sent / elapsed_time;
+	if (transfer_rate == 0)
+		return;
+
+	time_remaining = (app->byte_count - actual_bytes_sent) / transfer_rate;
+
+	//FIXME write this is human
+	if (time_remaining >= 3600) {
+		str = g_strdup_printf (_("(%d:%02d:%d Remaining)"), 
+				       time_remaining / 3600,
+				       (time_remaining % 3600) / 60,
+				       (time_remaining % 3600) % 60);
+	} else {
+		str = g_strdup_printf (_("(%d:%02d Remaining)"), 
+				       time_remaining / 60,
+				       time_remaining % 60);
+	}
+
+	/* Translators:
+	 * Sending file 1 of 3 */
+	str2 = g_strdup_printf (_("Sending file %d of %d"),
+				app->current_file+1,
+				app->file_count);
+	progressbar_text = g_strconcat (str2, " ", str, NULL);
+	gtk_progress_bar_set_text (GTK_PROGRESS_BAR (app->progress_bar),
+				   progressbar_text);
+	g_free(str);
+	g_free(str2);
+	g_free(progressbar_text);
+}
+
+static void
+transfer_completed_cb (DBusGProxy *proxy, BluetoothSendto *app)
+{
+	app->current_file++;
+	app->bytes_sent += app->file_length;
+
+	g_idle_add ((GSourceFunc) send_one, app);
 }
 
-static void create_notify(DBusGProxy *proxy,
-				DBusGProxyCall *call, void *user_data)
+static void session_created_cb (DBusGProxy *proxy, DBusGProxyCall *call,
+				BluetoothSendto *app)
 {
 	GError *error = NULL;
 	const gchar *path = NULL;
 
-	if (dbus_g_proxy_end_call(proxy, call, &error,
-			G_TYPE_STRING, &path, G_TYPE_INVALID) == FALSE) {
-		gchar *text, *message;
-
-		if (error != NULL) {
-			message = g_strdup(error->message);
-			g_error_free(error);
-		} else
-			message = g_strdup(_("An unknown error occured"));
-
-		text = g_strdup_printf("<span foreground=\"red\">%s</span>",
-								message);
-		gtk_label_set_markup(GTK_LABEL(label_status), text);
-		g_free(text);
-
-		g_free(message);
+	if (dbus_g_proxy_end_call (proxy, call, &error,
+				   G_TYPE_STRING, &path, G_TYPE_INVALID) == FALSE) {
+		const gchar *error_name = NULL;
+		gint response_id;
+
+		if (error != NULL && error->code == DBUS_GERROR_REMOTE_EXCEPTION)
+			error_name = dbus_g_error_get_name (error);
+		if (error_name && strcmp (error_name, "org.openobex.Error.ConnectionAttemptFailed") == 0) {
+			response_id = show_connection_error_dialog (GTK_WINDOW(app->main_dialog), bdaddrstr, error->message);
+			if (response_id == DIALOG_RESPONSE_RETRY) {
+				/* Try to create Session again */
+				dbus_g_proxy_begin_call (app->manager_proxy,
+							 "CreateBluetoothSession",
+							 (DBusGProxyCallNotify) session_created_cb, app, NULL,
+							 G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+							 G_TYPE_BOOLEAN, FALSE, G_TYPE_INVALID);
+				goto out;
+			}
+		} else {
+			show_error_dialog (GTK_WINDOW (app->main_dialog), BUTTONS_OK,
+					   error->message, "");
+		}
 
-		gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, TRUE);
-		return;
+		/* Failed, quit main loop */
+		gtk_main_quit();
+		goto out;
 	}
 
-	proxy = dbus_g_proxy_new_for_name(conn, "org.openobex",
-						path, "org.openobex.Session");
+	app->session_proxy = dbus_g_proxy_new_for_name (app->connection,
+							"org.openobex",
+							path,
+							"org.openobex.Session");
+
+	dbus_g_proxy_add_signal (app->session_proxy, "Connected",
+				 G_TYPE_INVALID);
+
+	dbus_g_proxy_connect_signal (app->session_proxy, "Connected",
+				     G_CALLBACK (session_connected_cb), app, NULL);
+
+	dbus_g_proxy_add_signal (app->session_proxy, "TransferStarted",
+				 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+
+	dbus_g_proxy_connect_signal (app->session_proxy, "TransferStarted",
+				     G_CALLBACK (transfer_started_cb), app, NULL);
+
+	dbus_g_proxy_add_signal (app->session_proxy, "Cancelled",
+				 G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "Connected", G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal (app->session_proxy, "Cancelled",
+				     G_CALLBACK (transfer_cancelled_cb), app, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "Connected",
-				G_CALLBACK(session_connected), user_data, NULL);
+	dbus_g_proxy_add_signal (app->session_proxy, "ErrorOccurred",
+				 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "TransferStarted", G_TYPE_STRING,
-				G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal (app->session_proxy, "ErrorOccurred",
+				     G_CALLBACK (error_occurred_cb), app, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferStarted",
-				G_CALLBACK(transfer_started), NULL, NULL);
+	dbus_g_proxy_add_signal (app->session_proxy, "TransferProgress",
+				 G_TYPE_UINT, G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "TransferProgress",
-						G_TYPE_UINT, G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal (app->session_proxy, "TransferProgress",
+				     G_CALLBACK (transfer_progress_cb), app, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferProgress",
-				G_CALLBACK(transfer_progress), NULL, NULL);
+	dbus_g_proxy_add_signal (app->session_proxy, "TransferCompleted", 
+				 G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "TransferCompleted", G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal (app->session_proxy, "TransferCompleted",
+				     G_CALLBACK (transfer_completed_cb), app, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferCompleted",
-				G_CALLBACK(transfer_completed), NULL, NULL);
+	dbus_g_proxy_call (app->session_proxy, "Connect", &error, G_TYPE_INVALID,
+			   G_TYPE_INVALID);
 
-	dbus_g_proxy_call(proxy, "Connect", NULL, G_TYPE_INVALID,
-							G_TYPE_INVALID);
+out:
+	if (error)
+		g_error_free (error);
 }
 
-static gchar *option_device = NULL;
+static void
+selected_device_changed_cb (BluetoothDeviceSelection *selector,
+			    gchar *address, gpointer user_data)
+{
+	GtkWidget *dialog = user_data;
 
-static GOptionEntry options[] = {
-	{ "device", 0, 0, G_OPTION_ARG_STRING, &option_device,
-				N_("Remote device to use"), "ADDRESS" },
-	{ NULL },
-};
+	gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+					   GTK_RESPONSE_ACCEPT, address != NULL);
+}
 
-int main(int argc, char *argv[])
+static void
+free_mem (BluetoothSendto *app)
 {
-	DBusGProxy *proxy;
-	GError *error;
-	gchar *filename, *address;
-
-	bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
-	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
-	textdomain(GETTEXT_PACKAGE);
-
-	error = NULL;
-
-	if (gtk_init_with_args(&argc, &argv, "[FILE...]",
-				options, GETTEXT_PACKAGE, &error) == FALSE) {
-		if (error != NULL) {
-			g_printerr("%s\n", error->message);
-			g_error_free(error);
-		} else
-			g_printerr("An unknown error occurred\n");
-
-		gtk_exit(1);
-	}
-
-	gtk_window_set_default_icon_name("stock_bluetooth");
-
-	if (argc < 2) {
-		filename = open_file_dialog();
-		if (filename == NULL)
-			gtk_exit(1);
-	} else
-		filename = g_strdup(argv[1]);
-
-	if (option_device == NULL) {
-		address = browse_device_dialog();
-		if (address == NULL) {
-			g_free(filename);
-			gtk_exit(1);
-		}
-	} else
-		address = g_strdup(option_device);
+	if (files_to_send)
+		g_strfreev (files_to_send);
+	g_free (bdaddrstr);
+
+	if (app) {
+		if (G_IS_OBJECT (app->manager_proxy))
+			g_object_unref (G_OBJECT (app->manager_proxy));
+		if (G_IS_OBJECT (app->session_proxy))
+			g_object_unref (G_OBJECT (app->session_proxy));
+		g_slist_free (app->file_list);
+		g_free (app);
+	}
+}
 
-	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING_INT,
-				G_TYPE_NONE, G_TYPE_STRING,
-				G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+int
+main (int argc, char *argv[])
+{
+	GOptionContext *option_context;
+	GError *err = NULL;
+	guint i;
+	BluetoothSendto *app;
+
+	app = g_new0 (BluetoothSendto, 1);
+
+	/* initialize gettext */
+	bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+	textdomain (GETTEXT_PACKAGE);
+
+	/* parse command line arguments */
+	option_context = g_option_context_new ("<file list>");
+	g_option_context_add_main_entries (option_context, options, GETTEXT_PACKAGE);
+	g_option_context_add_group (option_context, gtk_get_option_group (TRUE));
+	g_option_context_parse (option_context, &argc, &argv, &err);
+	g_option_context_free (option_context);
+	if (err) {
+		printf("Usage:\n");
+		printf("  %s [--dest=BDADDR] <file list>\n", g_get_prgname ());
+		g_error_free (err);
+		free_mem (app);
+		return 0;
+	}
 
-	error = NULL;
+	gtk_init(&argc, &argv);
 
-	conn = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
-	if (conn == NULL) {
-		if (error != NULL) {
-			g_printerr("Connecting to session bus failed: %s\n",
-							error->message);
-			g_error_free(error);
-		} else
-			g_print("An unknown error occured\n");
+	/* get a list of files to send from command-line */
+	if (files_to_send)
+		app->file_count = g_strv_length (files_to_send);
+	else
+		app->file_count = 0;
+	for (i = 0; i < app->file_count; i++) {
+		app->file_list = g_slist_append (app->file_list,
+						 g_strdup (*(files_to_send + i)));
+	}
+	/* show file chooser if no files were specified */
+	if (app->file_count == 0) {
+		GtkWidget *dialog;
+
+		dialog = gtk_file_chooser_dialog_new (_("Choose files to send"), NULL,
+						      GTK_FILE_CHOOSER_ACTION_OPEN,
+						      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+						      _("Send"), GTK_RESPONSE_ACCEPT,
+						      NULL);
+		gtk_window_set_icon_name (GTK_WINDOW (dialog), "stock_bluetooth");
+		gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dialog), TRUE);
+
+		if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
+			app->file_list = gtk_file_chooser_get_filenames (GTK_FILE_CHOOSER (dialog));
+			app->file_count = g_slist_length (app->file_list);
+		}
+		gtk_widget_destroy (dialog);
+	}
+	/* Check if there are some files to send */
+	if (app->file_count == 0) {
+		free_mem (app);
+		return 0;
+	}
+	/* Check for non regular and non existand files and
+	 * determine total bytes to send */
+	for (i = 0; i < app->file_count; i++) {
+		GSList *item;
+		gchar *path, *filename;
+		gint64 size;
+
+		item = g_slist_nth (app->file_list, i);
+		path = (gchar *) item->data;
+		filename = normalise_filename (path, &size);
+		if (filename == NULL) {
+			g_free (path);
+			app->file_list = g_slist_remove_link (app->file_list, item);
+			continue;
+		}
+		g_free (item->data);
+		item->data = filename;
+		app->byte_count += size;
+	}
 
-		gtk_exit(1);
+	/* determine Bluetooth device to send to */
+	if (!bdaddrstr) {
+		bdaddrstr = browse_device_dialog();
+		if (!bdaddrstr) {
+			free_mem (app);
+			return 0;
+		}
 	}
 
-	create_window(filename);
+	dbus_g_object_register_marshaller (sendto_marshal_VOID__STRING_STRING_INT,
+					   G_TYPE_NONE, G_TYPE_STRING,
+					   G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+	dbus_g_object_register_marshaller (sendto_marshal_VOID__STRING_STRING,
+					   G_TYPE_NONE, G_TYPE_STRING,
+					   G_TYPE_STRING, G_TYPE_INVALID);
+
+	/* init DBus connection */
+	app->connection = dbus_g_bus_get (DBUS_BUS_SESSION, &err);
+	if (app->connection == NULL) {
+		g_printerr ("Connecting to session bus failed: %s\n",
+			    err->message);
+		g_error_free (err);
+		free_mem (app);
+		return 1;
+	}
 
-	proxy = dbus_g_proxy_new_for_name(conn, "org.openobex",
-				"/org/openobex", "org.openobex.Manager");
+	/* init UI */
+	ui_init (app);
 
-	dbus_g_proxy_begin_call(proxy, "CreateBluetoothSession",
-				create_notify, filename, NULL,
-				G_TYPE_STRING, address, G_TYPE_STRING, "opp",
-				G_TYPE_BOOLEAN, FALSE, G_TYPE_INVALID);
+	/* init DBus proxy, create Session */
+	app->manager_proxy = dbus_g_proxy_new_for_name (app->connection,
+							"org.openobex",
+							"/org/openobex",
+							"org.openobex.Manager");
+
+	dbus_g_proxy_begin_call (app->manager_proxy, "CreateBluetoothSession",
+				 (DBusGProxyCallNotify) session_created_cb, app, NULL,
+				 G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+				 G_TYPE_INVALID);
 
-	gtk_main();
+	/* Go into main loop */
+	gtk_main ();
 
-	dbus_g_connection_unref(conn);
+	gtk_widget_destroy (app->main_dialog);
 
-	g_free(address);
-	g_free(filename);
+	/* done sending, free memory */
+	free_mem (app);
 
 	return 0;
 }
+
Index: marshal.list
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/marshal.list,v
retrieving revision 1.1
diff -u -p -r1.1 marshal.list
--- marshal.list	18 Aug 2007 20:57:50 -0000	1.1
+++ marshal.list	18 Jan 2008 13:41:58 -0000
@@ -1 +1,2 @@
 VOID:STRING,STRING,INT
+VOID:STRING,STRING

[-- Attachment #3: Type: text/plain, Size: 228 bytes --]

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/

[-- Attachment #4: Type: text/plain, Size: 164 bytes --]

_______________________________________________
Bluez-devel mailing list
Bluez-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bluez-devel

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Bluez-devel] [PATCH] Allow multiple files to be sent
  2008-01-18 13:43 ` Bastien Nocera
@ 2008-02-01 13:14   ` Bastien Nocera
  2008-02-01 16:08     ` Bastien Nocera
  0 siblings, 1 reply; 16+ messages in thread
From: Bastien Nocera @ 2008-02-01 13:14 UTC (permalink / raw)
  To: BlueZ development

[-- Attachment #1: Type: text/plain, Size: 700 bytes --]


On Fri, 2008-01-18 at 13:43 +0000, Bastien Nocera wrote:
> Hello again,
> 
> On Thu, 2008-01-17 at 10:43 +0000, Bastien Nocera wrote: 
> > Heya,
> > 
> > Another for bluetooth-sento feature parity with gnome-obex-send.
> 
> Another patch, based on Tadas' gnome-obex-send port.
> 
> - Fix to run with the latest obex-data-server (in 2 places)
> - Add an appropriate error when the target device is a Palm
> - Accept relative local paths and URIs as well as absolute filenames as
> arguments (needed for ease of use and nautilus-sendto, respectively)
> - Gather the name of the Bluetooth device from hcid to show in the UI

Updated patch to use the marshal file in common/ instead of our own.

Cheers

[-- Attachment #2: bluez-gnome-new-sendto-2.patch --]
[-- Type: text/x-patch, Size: 36973 bytes --]

Index: common/marshal.list
===================================================================
RCS file: /cvsroot/bluez/gnome/common/marshal.list,v
retrieving revision 1.1
diff -u -p -r1.1 marshal.list
--- common/marshal.list	24 Nov 2006 17:50:03 -0000	1.1
+++ common/marshal.list	1 Feb 2008 13:13:15 -0000
@@ -1,2 +1,3 @@
 VOID:STRING,UINT,INT
 VOID:STRING,STRING
+VOID:STRING,STRING,INT
Index: sendto/Makefile.am
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/Makefile.am,v
retrieving revision 1.7
diff -u -p -r1.7 Makefile.am
--- sendto/Makefile.am	29 Aug 2007 10:05:28 -0000	1.7
+++ sendto/Makefile.am	1 Feb 2008 13:13:15 -0000
@@ -8,8 +8,6 @@ bluetooth_sendto_LDADD = $(top_builddir)
 
 AM_CFLAGS = @DBUS_CFLAGS@ @GTK_CFLAGS@
 
-BUILT_SOURCES = marshal.h marshal.c
-
 nodist_bluetooth_sendto_SOURCES = $(BUILT_SOURCES)
 
 INCLUDES = -I$(top_srcdir)/common
@@ -18,12 +16,7 @@ CLEANFILES = $(BUILT_SOURCES)
 
 noinst_MANS = bluetooth-sendto.1
 
-EXTRA_DIST = $(noinst_MANS) marshal.list
+EXTRA_DIST = $(noinst_MANS)
 
 MAINTAINERCLEANFILES = Makefile.in
 
-marshal.h: marshal.list
-	$(GLIB_GENMARSHAL) --prefix=marshal $< --header > $@
-
-marshal.c: marshal.list
-	$(GLIB_GENMARSHAL) --prefix=marshal $< --body > $@
Index: sendto/main.c
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/main.c,v
retrieving revision 1.12
diff -u -p -r1.12 main.c
--- sendto/main.c	16 Jan 2008 19:13:39 -0000	1.12
+++ sendto/main.c	1 Feb 2008 13:13:15 -0000
@@ -1,366 +1,856 @@
 /*
+ * bluetooth-sendto
+ * 
+ * Copyright (c) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ * Copyright (c) 2007 Bastien Nocera <hadess@hadess.net>
  *
- *  BlueZ - Bluetooth protocol stack for Linux
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
  *
- *  Copyright (C) 2005-2007  Marcel Holtmann <marcel@holtmann.org>
- *
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
  *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
 
-#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
 
+#include <dbus/dbus-glib.h>
+#include <glib.h>
 #include <glib/gi18n.h>
-
+#include <glib/gstdio.h>
 #include <gtk/gtk.h>
 
 #include "bluetooth-device-selection.h"
-
+#include "dbus-glue.h"
 #include "marshal.h"
 
-static DBusGConnection *conn = NULL;
+typedef struct _appinfo {
+	/* DBus */
+	DBusGConnection *connection;
+	DBusGProxy *manager_proxy;
+	DBusGProxy *session_proxy;
+	/* GTK */
+	GtkWidget *main_dialog;
+	GtkWidget *from_label;
+	GtkWidget *operation_label;
+	GtkWidget *progress_bar;
+	/* sending related */
+	gsize file_length;
+	guint file_count;
+	guint current_file;
+	GSList *file_list;
+	guint byte_count;
+	guint bytes_sent;
+	gint64 first_transfer_time;
+	gint64 last_update_time;
+} BluetoothSendto;
+
+typedef enum {
+	BUTTONS_OK,
+	BUTTONS_RETRY_CANCEL,
+	BUTTONS_RETRY_SKIP_CANCEL
+} ButtonSet;
+
+static gchar **files_to_send = NULL;
+static gchar *bdaddrstr = NULL;
+static gchar *device_name = NULL;
+
+#define DIALOG_RESPONSE_SKIP 1
+#define DIALOG_RESPONSE_RETRY 2
+
+static gboolean send_one (BluetoothSendto *app);
+
+static void cancel_clicked_cb (GtkWidget *button, BluetoothSendto *app);
+static void session_created_cb (DBusGProxy *proxy, DBusGProxyCall *call,
+				BluetoothSendto *app);
+static void selected_device_changed_cb (BluetoothDeviceSelection *selector,
+					gchar *address, gpointer user_data);
+
+static const GOptionEntry options[] = {
+	{"dest", 'd', 0, G_OPTION_ARG_STRING, &bdaddrstr, 
+		"Bluetooth address of destination device", "BDADDR"},
+	{G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &files_to_send},
+	{NULL}
+};
 
-static GtkWidget *dialog;
-static GtkWidget *label_filename;
-static GtkWidget *label_status;
-static GtkWidget *progress;
+static gint64
+get_system_time (void)
+{
+	struct timeval tmp;
 
-static gint filesize = -1;
+	gettimeofday (&tmp, NULL);
+	return (gint64)tmp.tv_usec + (gint64)tmp.tv_sec * G_GINT64_CONSTANT (1000000);
+}
 
-static gchar *open_file_dialog(void)
+static gboolean
+is_palm_device (const gchar *bdaddr)
 {
-	GtkWidget *dialog;
-	gchar *filename = NULL;
+	return (g_str_has_prefix (bdaddr, "00:04:6B")
+		|| g_str_has_prefix (bdaddr, "00:07:E0")
+		|| g_str_has_prefix (bdaddr, "00:0E:20"));
+}
 
-	dialog = gtk_file_chooser_dialog_new(_("Select File"), NULL,
-				GTK_FILE_CHOOSER_ACTION_OPEN,
-				GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
-				GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
+/* Return the local absolute filename for the file, and its size
+ * if the file exists and is stat()'able */
+static gchar *
+normalise_filename (const gchar *filename, gint64 *size)
+{
+	char *ret;
+	struct stat file_stat;
 
-	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
-		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+	g_return_val_if_fail (filename != NULL, NULL);
+	g_return_val_if_fail (size != NULL, NULL);
 
-	gtk_widget_destroy(dialog);
+	if (g_str_has_prefix (filename, "file://") != FALSE) {
+		ret = g_filename_from_uri (filename, NULL, NULL);
+		if (ret == NULL)
+			return NULL;
+	} else if (filename[0] == '/') {
+		ret = g_strdup (filename);
+	} else {
+		gchar *curdir;
 
-	return filename;
-}
+		curdir = g_get_current_dir ();
+		ret = g_build_filename (curdir, filename, NULL);
+		g_free (curdir);
+	}
 
-static void selected_device_changed(BluetoothDeviceSelection *selector,
-					gchar *address, gpointer user_data)
-{
-	GtkWidget *dialog = user_data;
+	if (g_file_test (ret, G_FILE_TEST_IS_REGULAR) == FALSE) {
+		g_free (ret);
+		return NULL;
+	}
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-				GTK_RESPONSE_ACCEPT, address != NULL);
+	if (g_stat (ret, &file_stat) == 0) {
+		*size = file_stat.st_size;
+	} else {
+		g_free (ret);
+		return NULL;
+	}
+
+	return ret;
 }
 
-static gchar *browse_device_dialog(void)
+static gchar *
+get_device_name (BluetoothSendto *app)
 {
-	GtkWidget *dialog;
-	GtkWidget *selector;
-	gchar *address = NULL;
+	DBusGConnection *connection;
+	DBusGProxy *manager;
+	gchar *name, **adapters;
+	guint i;
 
-	dialog = gtk_dialog_new_with_buttons(_("Select Device"),
-				NULL, GTK_DIALOG_NO_SEPARATOR,
-				GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
-				GTK_STOCK_CONNECT, GTK_RESPONSE_ACCEPT, NULL);
+	name = NULL;
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_ACCEPT, FALSE);
+	connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, NULL);
+	if (connection == NULL)
+		return NULL;
 
-	gtk_window_set_default_size(GTK_WINDOW(dialog), 450, 300);
+	manager = dbus_g_proxy_new_for_name (connection, "org.bluez",
+					    "/org/bluez", "org.bluez.Manager");
+	if (manager == NULL) {
+		dbus_g_connection_unref (connection);
+		return NULL;
+	}
 
-	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
+	GError *error = NULL;
+	if (manager_list_adapters (manager, &adapters, &error) == FALSE) {
+		g_object_unref (manager);
+		dbus_g_connection_unref (connection);
+		return NULL;
+	}
 
-	selector = bluetooth_device_selection_new(_("Select destination device"));
+	for (i = 0; adapters[i] != NULL; i++) {
+		DBusGProxy *adapter;
 
-	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), selector);
+		adapter = dbus_g_proxy_new_for_name (connection, "org.bluez",
+						    adapters[i], "org.bluez.Adapter");
+		if (dbus_g_proxy_call (adapter, "GetRemoteName", NULL,
+				   G_TYPE_STRING, bdaddrstr, G_TYPE_INVALID,
+				   G_TYPE_STRING, &name, G_TYPE_INVALID) != FALSE) {
+			if (name != NULL && name[0] != '\0') {
+				g_object_unref (adapter);
+				break;
+			}
+		}
+		g_object_unref (adapter);
+	}
 
-	g_signal_connect(selector, "selected-device-changed",
-				G_CALLBACK(selected_device_changed), dialog);
+	g_object_unref (manager);
+	dbus_g_connection_unref (connection);
 
-	gtk_widget_show_all(selector);
+	return name;
+}
 
-	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
-		g_object_get(selector, "device-selected", &address, NULL);
+static gint
+show_error_dialog (GtkWindow *parent, ButtonSet buttons,
+		   const gchar *primary_text, const gchar *secondary_text)
+{
+	GtkWidget *dialog;
+	gchar *primary_text_markup;
+	gint ret;
 
-	gtk_widget_destroy(dialog);
+	primary_text_markup = g_strdup_printf ("<span weight=\"bold\" size=\"larger\">%s</span>", 
+					       primary_text);
+	dialog = gtk_message_dialog_new_with_markup (parent, 0, GTK_MESSAGE_ERROR, 
+						     buttons==BUTTONS_OK? GTK_BUTTONS_OK: GTK_BUTTONS_CANCEL, 
+						     primary_text_markup);
+	gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+						  secondary_text);
+	if (buttons == BUTTONS_RETRY_SKIP_CANCEL) {
+		gtk_dialog_add_button (GTK_DIALOG (dialog), _("Skip"),
+				       DIALOG_RESPONSE_SKIP);
+	}
+	if (buttons != BUTTONS_OK) {
+		gtk_dialog_add_button (GTK_DIALOG (dialog), _("Retry"),
+				       DIALOG_RESPONSE_RETRY);
+	}
+	ret = gtk_dialog_run (GTK_DIALOG (dialog));
 
-	return address;
+	gtk_widget_destroy (dialog);
+	g_free (primary_text_markup);
+	return ret;
+}
+
+static gint
+show_connection_error_dialog (GtkWindow *parent, const gchar *bdaddr,
+			      const gchar *primary_text)
+{
+	if (is_palm_device (bdaddrstr) == FALSE) {
+		return show_error_dialog (parent,
+					  BUTTONS_RETRY_CANCEL,
+					  primary_text,
+					  _("Make sure that remote device is on and that it accepts Bluetooth connections."));
+	} else {
+		return show_error_dialog (parent,
+					  BUTTONS_RETRY_CANCEL,
+					  primary_text,
+					  _("Make sure \"Beam receive\" is enabled in the Power preferences, and Bluetooth is enabled, on your Palm device."));
+	}
 }
 
-static void response_callback(GtkWidget *dialog,
-					gint response, gpointer user_data)
-{
-	gtk_widget_destroy(dialog);
-
-	gtk_main_quit();
+static gint
+handle_file_sending_error (const gchar *error_text,
+			   const gchar *error_secondary_text, BluetoothSendto *app)
+{
+	ButtonSet buttons;
+	gint response_id;
+
+	if (app->file_count > 1 && app->current_file < app->file_count-1)
+		buttons = BUTTONS_RETRY_SKIP_CANCEL;
+	else
+		buttons = BUTTONS_RETRY_CANCEL;
+
+	response_id = show_error_dialog (GTK_WINDOW (app->main_dialog), buttons, 
+					 error_text, error_secondary_text);
+
+	switch (response_id) {
+	case DIALOG_RESPONSE_SKIP:
+		app->current_file++;
+		app->bytes_sent += app->file_length;
+	case DIALOG_RESPONSE_RETRY:
+		return TRUE;
+	case GTK_RESPONSE_CANCEL:
+	default:
+		gtk_main_quit ();
+		break;
+	}
+	return response_id;
 }
 
-static void create_window(const gchar *filename)
+static gchar *
+browse_device_dialog (void)
 {
-	GtkWidget *vbox;
-	GtkWidget *label;
-	gchar *text;
-
-	dialog = gtk_dialog_new_with_buttons(_("File Transfer"), NULL,
-				GTK_DIALOG_NO_SEPARATOR,
-				GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
-
-	gtk_window_set_default_size(GTK_WINDOW(dialog), 400, -1);
+	GtkWidget *dialog;
+	GtkWidget *selector;
+	gchar *address = NULL;
 
-	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
+	dialog = gtk_dialog_new_with_buttons (_("Select Device"),
+					      NULL, GTK_DIALOG_NO_SEPARATOR,
+					      GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+					      GTK_STOCK_CONNECT, GTK_RESPONSE_ACCEPT, NULL);
 
-	vbox = gtk_vbox_new(FALSE, 6);
-	gtk_container_set_border_width(GTK_CONTAINER(vbox), 12);
-	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox);
+	gtk_window_set_icon_name (GTK_WINDOW (dialog), "stock_bluetooth");
 
-	label = gtk_label_new(NULL);
-	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+	gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+					   GTK_RESPONSE_ACCEPT, FALSE);
 
-	label_filename = label;
+	gtk_window_set_default_size (GTK_WINDOW (dialog), 450, 300);
 
-	progress = gtk_progress_bar_new();
-	gtk_box_pack_start(GTK_BOX(vbox), progress, FALSE, FALSE, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (dialog), 6);
 
-	label = gtk_label_new(NULL);
-	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 6);
+	selector = bluetooth_device_selection_new (_("Select destination device"));
+	bluetooth_device_selection_start_discovery (
+	    BLUETOOTH_DEVICE_SELECTION (selector));
 
-	label_status = label;
+	gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), selector);
 
-	text = g_strdup_printf("<b>%s</b>", filename);
-	gtk_label_set_markup(GTK_LABEL(label_filename), text);
-	g_free(text);
+	g_signal_connect (selector, "selected-device-changed",
+			  G_CALLBACK (selected_device_changed_cb), dialog);
 
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Connecting..."));
+	gtk_widget_show_all (selector);
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, FALSE);
+	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
+		g_object_get (selector, "device-selected", &address, NULL);
 
-	g_signal_connect(G_OBJECT(dialog), "response",
-				G_CALLBACK(response_callback), NULL);
+	gtk_widget_destroy (dialog);
 
-	gtk_widget_show_all(dialog);
+	return address;
 }
 
-static void transfer_started(DBusGProxy *proxy, gchar *a, gchar *b,
-						gint size, gpointer user_data)
+static void
+ui_init (BluetoothSendto *app)
 {
+	GtkWidget *vbox, *vbox_parent, *table;
+	GtkWidget *label1, *label2, *label3, *target_label, *from_label, *operation_label;
+	GtkWidget *progress_bar, *button;
 	gchar *text;
 
-	filesize = size;
+	/* initialize UI */
 
-	text = g_strdup_printf(_("Starting transfer of %d bytes"), size);
-	gtk_label_set_markup(GTK_LABEL(label_status), text);
-	g_free(text);
+	device_name = get_device_name (app);
 
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0.0);
+	/* main dialog */
+	app->main_dialog = gtk_dialog_new ();
+	gtk_dialog_set_has_separator (GTK_DIALOG (app->main_dialog), FALSE);
+	gtk_window_set_title (GTK_WINDOW (app->main_dialog),
+			      _("Bluetooth file transfer"));
+	gtk_window_set_icon_name (GTK_WINDOW (app->main_dialog), "stock_bluetooth");
+	gtk_window_set_type_hint (GTK_WINDOW (app->main_dialog),
+				  GDK_WINDOW_TYPE_HINT_NORMAL);
+	gtk_window_set_position (GTK_WINDOW (app->main_dialog), GTK_WIN_POS_CENTER);
+	gtk_window_set_resizable (GTK_WINDOW (app->main_dialog), FALSE);
+	gtk_widget_set (app->main_dialog, "width-request", 400, NULL);
+	button = gtk_dialog_add_button (GTK_DIALOG (app->main_dialog),
+					GTK_STOCK_CANCEL,
+					GTK_RESPONSE_CANCEL);
+	g_signal_connect (G_OBJECT (button), "clicked",
+			  G_CALLBACK (cancel_clicked_cb), app);
+	gtk_container_set_border_width (GTK_CONTAINER (app->main_dialog), 6);
+
+	/* table elements */
+	from_label = gtk_label_new (NULL);
+	gtk_misc_set_alignment (GTK_MISC (from_label), 0, 0.5);
+	gtk_label_set_ellipsize (GTK_LABEL (from_label), PANGO_ELLIPSIZE_MIDDLE);
+	target_label = gtk_label_new (NULL);
+	gtk_misc_set_alignment (GTK_MISC (target_label), 0, 0.5);
+	gtk_label_set_ellipsize (GTK_LABEL (target_label), PANGO_ELLIPSIZE_END);
+	gtk_label_set_text (GTK_LABEL (target_label), device_name ? device_name : bdaddrstr);
+	label2 = gtk_label_new (NULL);
+	text = g_markup_printf_escaped ("<b>%s</b>", _("From:"));
+	gtk_label_set_markup (GTK_LABEL (label2), text);
+	g_free (text);
+	label3 = gtk_label_new (NULL);
+	gtk_misc_set_alignment (GTK_MISC (label3), 1, 0.5);
+	text = g_markup_printf_escaped ("<b>%s</b>", _("To:"));
+	gtk_label_set_markup (GTK_LABEL (label3), text);
+	g_free (text);
+
+	/* table */
+	table = gtk_table_new (2, 2, FALSE);
+	gtk_table_set_col_spacings (GTK_TABLE (table), 4);
+	gtk_table_set_row_spacings (GTK_TABLE (table), 4);
+	gtk_table_attach_defaults (GTK_TABLE (table), from_label, 1, 2, 0, 1);
+	gtk_table_attach_defaults (GTK_TABLE (table), target_label, 1, 2, 1, 2);
+	gtk_table_attach (GTK_TABLE (table), label2, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+	gtk_table_attach (GTK_TABLE (table), label3, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+
+	/* vbox elements */
+	label1 = gtk_label_new (NULL);
+	gtk_misc_set_alignment (GTK_MISC (label1), 0, 0.5);
+	text = g_markup_printf_escaped ("<span weight=\"bold\" size=\"larger\">%s</span>",
+					_("Sending files via bluetooth"));
+	gtk_label_set_markup (GTK_LABEL (label1), text);
+	g_free (text);
+	progress_bar = gtk_progress_bar_new ();
+	gtk_progress_bar_set_ellipsize (GTK_PROGRESS_BAR (progress_bar),
+					PANGO_ELLIPSIZE_END);
+	gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress_bar),
+				   _("Connecting..."));
+	operation_label = gtk_label_new (NULL);
+	gtk_misc_set_alignment (GTK_MISC (operation_label), 0, 0.5);
+	gtk_label_set_ellipsize (GTK_LABEL (operation_label), PANGO_ELLIPSIZE_END);
+
+	/* vbox */
+	vbox = gtk_vbox_new (FALSE, 0);
+	gtk_box_set_spacing (GTK_BOX (vbox), 6);
+	gtk_container_set_border_width (GTK_CONTAINER (vbox), 6);
+	gtk_box_pack_start_defaults (GTK_BOX (vbox), label1);
+	gtk_box_pack_start (GTK_BOX (vbox), table, TRUE, TRUE, 9);
+	gtk_box_pack_start_defaults (GTK_BOX (vbox), progress_bar);
+	gtk_box_pack_start (GTK_BOX (vbox), operation_label, TRUE, TRUE, 2);
+	vbox_parent = gtk_bin_get_child (GTK_BIN (app->main_dialog));
+	gtk_box_pack_start_defaults (GTK_BOX (vbox_parent), vbox);
+
+	/* Set widget variables in app */
+	app->from_label = from_label;
+	app->operation_label = operation_label;
+	app->progress_bar = progress_bar;
+
+	/* show it */
+	gtk_widget_show_all (app->main_dialog);
+}
+
+static gboolean
+send_one (BluetoothSendto *app)
+{
+	const gchar *fname;
+	gint ret;
+	gchar *bname, *dirname;
+	GError *err = NULL;
+	gchar *operation_text, *operation_markup, *progressbar_text;
+
+	if ((fname = g_slist_nth_data (app->file_list, app->current_file))) {
+		/* there's a file to send */
+		dbus_g_proxy_call (app->session_proxy, "SendFile", &err,
+				   G_TYPE_STRING, fname, G_TYPE_INVALID, G_TYPE_INVALID);
+
+		if (err != NULL) {
+			ret = handle_file_sending_error (_("Unable to read file"),
+							 err->message, app);
+			if (ret != GTK_RESPONSE_CANCEL) {
+				g_idle_add ((GSourceFunc) send_one, app);
+			}
+			g_error_free (err);
+			return FALSE;
+		}
+
+		if (app->first_transfer_time == 0) {
+			app->first_transfer_time = get_system_time();
+		}
+		bname = g_path_get_basename (fname);
+		dirname = g_path_get_dirname (fname);
+
+		operation_text = g_strdup_printf (_("Sending %s"), bname);
+		operation_markup = g_markup_printf_escaped ("<i>%s</i>", operation_text);
+		progressbar_text = g_strdup_printf (_("Sending file: %d of %d"),
+						    app->current_file+1,
+						    app->file_count);
+		gtk_label_set_text (GTK_LABEL (app->from_label), dirname);
+		gtk_label_set_markup (GTK_LABEL (app->operation_label), operation_markup);
+		gtk_progress_bar_set_text (GTK_PROGRESS_BAR (app->progress_bar),
+					   progressbar_text);
+
+		g_free (bname);
+		g_free (dirname);
+		g_free (operation_text);
+		g_free (operation_markup);
+		g_free (progressbar_text);
+	} else {
+		/* nothing left to send, time to quit */
+		gtk_main_quit ();
+	}
+	return FALSE;
 }
 
-static void transfer_progress(DBusGProxy *proxy, guint bytes, gpointer user_data)
+static void
+cancel_clicked_cb (GtkWidget *button, BluetoothSendto *app)
 {
-	gchar *text;
-	gdouble fraction;
+	gtk_main_quit ();
+}
 
-	text = g_strdup_printf(_("Transfered %d of %d bytes"), bytes, filesize);
-	gtk_label_set_markup(GTK_LABEL(label_status), text);
-	g_free(text);
+static void
+session_connected_cb (DBusGProxy *proxy, BluetoothSendto *app)
+{
+	/* OBEX connect has succeeded, so start sending */
+	g_idle_add ((GSourceFunc) send_one, app);
+}
 
-	fraction = (gdouble) bytes / (gdouble) filesize;
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), fraction);
+static void
+transfer_started_cb (DBusGProxy *proxy, const gchar *filename,
+		     const gchar *local_path, gint byte_count, BluetoothSendto *app)
+{
+	app->file_length = byte_count;
 }
 
-static void transfer_completed(DBusGProxy *proxy, gpointer user_data)
+static void
+transfer_cancelled_cb (DBusGProxy *proxy, BluetoothSendto *app)
 {
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Completed"));
+	gint ret;
+	gchar *error_text;
 
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 1.0);
+	error_text = g_strdup_printf (_("An error occured while sending file '%s'"),
+				      (gchar *)g_slist_nth_data (app->file_list, app->current_file));
+	ret = handle_file_sending_error (error_text,
+					 _("The remote device cancelled the transfer"), app);
 
-	dbus_g_proxy_call(proxy, "Disconnect", NULL, G_TYPE_INVALID,
-							G_TYPE_INVALID);
+	if (ret != GTK_RESPONSE_CANCEL)
+		g_idle_add ((GSourceFunc) send_one, app);
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, TRUE);
+	g_free (error_text);
 }
 
-static void session_connected(DBusGProxy *proxy, gpointer user_data)
+static void
+error_occurred_cb (DBusGProxy *proxy, const gchar *error_name,	
+		   const gchar *error_message, BluetoothSendto *app)
 {
-	gchar *filename = user_data;
-	GError *error = NULL;
+	gint ret;
+	gboolean link_error = FALSE;
+	gchar *error_text;
+	const gchar *reason;
 
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Connected"));
+	g_message ("ErrorOccurred");
+	g_message ("Error name: %s", error_name);
+	g_message ("Error message: %s", error_message);
 
-	dbus_g_proxy_call(proxy, "SendFile", &error,
-				G_TYPE_STRING, filename, G_TYPE_INVALID,
-							G_TYPE_INVALID);
-
-	if (error != NULL) {
-		g_printerr("Sending of file %s failed: %s\n", filename,
-							error->message);
-		g_error_free(error);
-		gtk_main_quit();
+	error_text = g_strdup_printf (_("An error occured while sending file '%s'"),
+				      (gchar *)g_slist_nth_data(app->file_list, app->current_file));
+	if (strcmp (error_name, "org.openobex.Error.LinkError") == 0) {
+		/* Connection to remote device was lost */
+		g_object_unref (G_OBJECT (app->session_proxy));
+		reason = _("Connection to remote device was lost");
+		link_error = TRUE;
+	} else if (strcmp (error_name, "org.openobex.Error.Forbidden") == 0) {
+		reason = _("Remote device rejected file");
+	} else {
+		reason = _("Unknown error");
+	}
+	ret = handle_file_sending_error (error_text, reason, app);
+
+	if (ret != GTK_RESPONSE_CANCEL) {
+		if (link_error) {
+			/* Need to reestablish connection */
+			dbus_g_proxy_begin_call (app->manager_proxy, "CreateBluetoothSession",
+						 (DBusGProxyCallNotify) session_created_cb, app, NULL,
+						 G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+						 G_TYPE_INVALID);
+		} else {
+			g_idle_add ((GSourceFunc) send_one, app);
+		}
+	}
+
+
+	g_free (error_text);
+}
+
+static void
+transfer_progress_cb (DBusGProxy *proxy, gint bytes_transferred, BluetoothSendto *app)
+{
+	gdouble frac;
+	guint actual_bytes_sent;
+	gint elapsed_time;
+	gint transfer_rate;
+	gint time_remaining;
+	gint64 current_time;
+	gchar *str, *str2;
+	gchar *progressbar_text;
+
+	/* update progress bar fraction */
+	actual_bytes_sent = app->bytes_sent + bytes_transferred;
+	frac = (gdouble)actual_bytes_sent / app->byte_count;
+	gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (app->progress_bar), frac);
+
+	/* update progress bar text (time remaining)
+	 * (only update once in a second) */
+	current_time = get_system_time();
+	elapsed_time = (current_time - app->first_transfer_time) / 1000000;
+
+	if (app->last_update_time != 0) {
+		if (current_time < app->last_update_time + 1000000)
+			return;
+		else
+			app->last_update_time = current_time;
+	} else {
+		app->last_update_time = current_time;
 	}
+
+	if (elapsed_time == 0)
+		return;
+
+	transfer_rate = actual_bytes_sent / elapsed_time;
+	if (transfer_rate == 0)
+		return;
+
+	time_remaining = (app->byte_count - actual_bytes_sent) / transfer_rate;
+
+	//FIXME write this is human
+	if (time_remaining >= 3600) {
+		str = g_strdup_printf (_("(%d:%02d:%d Remaining)"), 
+				       time_remaining / 3600,
+				       (time_remaining % 3600) / 60,
+				       (time_remaining % 3600) % 60);
+	} else {
+		str = g_strdup_printf (_("(%d:%02d Remaining)"), 
+				       time_remaining / 60,
+				       time_remaining % 60);
+	}
+
+	/* Translators:
+	 * Sending file 1 of 3 */
+	str2 = g_strdup_printf (_("Sending file %d of %d"),
+				app->current_file+1,
+				app->file_count);
+	progressbar_text = g_strconcat (str2, " ", str, NULL);
+	gtk_progress_bar_set_text (GTK_PROGRESS_BAR (app->progress_bar),
+				   progressbar_text);
+	g_free(str);
+	g_free(str2);
+	g_free(progressbar_text);
+}
+
+static void
+transfer_completed_cb (DBusGProxy *proxy, BluetoothSendto *app)
+{
+	app->current_file++;
+	app->bytes_sent += app->file_length;
+
+	g_idle_add ((GSourceFunc) send_one, app);
 }
 
-static void create_notify(DBusGProxy *proxy,
-				DBusGProxyCall *call, void *user_data)
+static void session_created_cb (DBusGProxy *proxy, DBusGProxyCall *call,
+				BluetoothSendto *app)
 {
 	GError *error = NULL;
 	const gchar *path = NULL;
 
-	if (dbus_g_proxy_end_call(proxy, call, &error,
-			G_TYPE_STRING, &path, G_TYPE_INVALID) == FALSE) {
-		gchar *text, *message;
-
-		if (error != NULL) {
-			message = g_strdup(error->message);
-			g_error_free(error);
-		} else
-			message = g_strdup(_("An unknown error occured"));
-
-		text = g_strdup_printf("<span foreground=\"red\">%s</span>",
-								message);
-		gtk_label_set_markup(GTK_LABEL(label_status), text);
-		g_free(text);
-
-		g_free(message);
+	if (dbus_g_proxy_end_call (proxy, call, &error,
+				   G_TYPE_STRING, &path, G_TYPE_INVALID) == FALSE) {
+		const gchar *error_name = NULL;
+		gint response_id;
+
+		if (error != NULL && error->code == DBUS_GERROR_REMOTE_EXCEPTION)
+			error_name = dbus_g_error_get_name (error);
+		if (error_name && strcmp (error_name, "org.openobex.Error.ConnectionAttemptFailed") == 0) {
+			response_id = show_connection_error_dialog (GTK_WINDOW(app->main_dialog), bdaddrstr, error->message);
+			if (response_id == DIALOG_RESPONSE_RETRY) {
+				/* Try to create Session again */
+				dbus_g_proxy_begin_call (app->manager_proxy,
+							 "CreateBluetoothSession",
+							 (DBusGProxyCallNotify) session_created_cb, app, NULL,
+							 G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+							 G_TYPE_BOOLEAN, FALSE, G_TYPE_INVALID);
+				goto out;
+			}
+		} else {
+			show_error_dialog (GTK_WINDOW (app->main_dialog), BUTTONS_OK,
+					   error->message, "");
+		}
 
-		gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, TRUE);
-		return;
+		/* Failed, quit main loop */
+		gtk_main_quit();
+		goto out;
 	}
 
-	proxy = dbus_g_proxy_new_for_name(conn, "org.openobex",
-						path, "org.openobex.Session");
+	app->session_proxy = dbus_g_proxy_new_for_name (app->connection,
+							"org.openobex",
+							path,
+							"org.openobex.Session");
+
+	dbus_g_proxy_add_signal (app->session_proxy, "Connected",
+				 G_TYPE_INVALID);
+
+	dbus_g_proxy_connect_signal (app->session_proxy, "Connected",
+				     G_CALLBACK (session_connected_cb), app, NULL);
+
+	dbus_g_proxy_add_signal (app->session_proxy, "TransferStarted",
+				 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+
+	dbus_g_proxy_connect_signal (app->session_proxy, "TransferStarted",
+				     G_CALLBACK (transfer_started_cb), app, NULL);
 
-	dbus_g_proxy_add_signal(proxy, "Connected", G_TYPE_INVALID);
+	dbus_g_proxy_add_signal (app->session_proxy, "Cancelled",
+				 G_TYPE_INVALID);
 
-	dbus_g_proxy_connect_signal(proxy, "Connected",
-				G_CALLBACK(session_connected), user_data, NULL);
+	dbus_g_proxy_connect_signal (app->session_proxy, "Cancelled",
+				     G_CALLBACK (transfer_cancelled_cb), app, NULL);
 
-	dbus_g_proxy_add_signal(proxy, "TransferStarted", G_TYPE_STRING,
-				G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+	dbus_g_proxy_add_signal (app->session_proxy, "ErrorOccurred",
+				 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferStarted",
-				G_CALLBACK(transfer_started), NULL, NULL);
+	dbus_g_proxy_connect_signal (app->session_proxy, "ErrorOccurred",
+				     G_CALLBACK (error_occurred_cb), app, NULL);
 
-	dbus_g_proxy_add_signal(proxy, "TransferProgress",
-						G_TYPE_UINT, G_TYPE_INVALID);
+	dbus_g_proxy_add_signal (app->session_proxy, "TransferProgress",
+				 G_TYPE_UINT, G_TYPE_INVALID);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferProgress",
-				G_CALLBACK(transfer_progress), NULL, NULL);
+	dbus_g_proxy_connect_signal (app->session_proxy, "TransferProgress",
+				     G_CALLBACK (transfer_progress_cb), app, NULL);
 
-	dbus_g_proxy_add_signal(proxy, "TransferCompleted", G_TYPE_INVALID);
+	dbus_g_proxy_add_signal (app->session_proxy, "TransferCompleted", 
+				 G_TYPE_INVALID);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferCompleted",
-				G_CALLBACK(transfer_completed), NULL, NULL);
+	dbus_g_proxy_connect_signal (app->session_proxy, "TransferCompleted",
+				     G_CALLBACK (transfer_completed_cb), app, NULL);
 
-	dbus_g_proxy_call(proxy, "Connect", NULL, G_TYPE_INVALID,
-							G_TYPE_INVALID);
+	dbus_g_proxy_call (app->session_proxy, "Connect", &error, G_TYPE_INVALID,
+			   G_TYPE_INVALID);
+
+out:
+	if (error)
+		g_error_free (error);
 }
 
-static gchar *option_device = NULL;
+static void
+selected_device_changed_cb (BluetoothDeviceSelection *selector,
+			    gchar *address, gpointer user_data)
+{
+	GtkWidget *dialog = user_data;
 
-static GOptionEntry options[] = {
-	{ "device", 0, 0, G_OPTION_ARG_STRING, &option_device,
-				N_("Remote device to use"), "ADDRESS" },
-	{ NULL },
-};
+	gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
+					   GTK_RESPONSE_ACCEPT, address != NULL);
+}
 
-int main(int argc, char *argv[])
+static void
+free_mem (BluetoothSendto *app)
 {
-	DBusGProxy *proxy;
-	GError *error;
-	gchar *filename, *address;
-
-	bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
-	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
-	textdomain(GETTEXT_PACKAGE);
-
-	error = NULL;
-
-	if (gtk_init_with_args(&argc, &argv, "[FILE...]",
-				options, GETTEXT_PACKAGE, &error) == FALSE) {
-		if (error != NULL) {
-			g_printerr("%s\n", error->message);
-			g_error_free(error);
-		} else
-			g_printerr("An unknown error occurred\n");
-
-		gtk_exit(1);
-	}
-
-	gtk_window_set_default_icon_name("stock_bluetooth");
-
-	if (argc < 2) {
-		filename = open_file_dialog();
-		if (filename == NULL)
-			gtk_exit(1);
-	} else
-		filename = g_strdup(argv[1]);
-
-	if (option_device == NULL) {
-		address = browse_device_dialog();
-		if (address == NULL) {
-			g_free(filename);
-			gtk_exit(1);
-		}
-	} else
-		address = g_strdup(option_device);
+	if (files_to_send)
+		g_strfreev (files_to_send);
+	g_free (bdaddrstr);
+
+	if (app) {
+		if (G_IS_OBJECT (app->manager_proxy))
+			g_object_unref (G_OBJECT (app->manager_proxy));
+		if (G_IS_OBJECT (app->session_proxy))
+			g_object_unref (G_OBJECT (app->session_proxy));
+		g_slist_free (app->file_list);
+		g_free (app);
+	}
+}
 
-	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING_INT,
-				G_TYPE_NONE, G_TYPE_STRING,
-				G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+int
+main (int argc, char *argv[])
+{
+	GOptionContext *option_context;
+	GError *err = NULL;
+	guint i;
+	BluetoothSendto *app;
+
+	app = g_new0 (BluetoothSendto, 1);
+
+	/* initialize gettext */
+	bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+	textdomain (GETTEXT_PACKAGE);
+
+	/* parse command line arguments */
+	option_context = g_option_context_new ("<file list>");
+	g_option_context_add_main_entries (option_context, options, GETTEXT_PACKAGE);
+	g_option_context_add_group (option_context, gtk_get_option_group (TRUE));
+	g_option_context_parse (option_context, &argc, &argv, &err);
+	g_option_context_free (option_context);
+	if (err) {
+		printf("Usage:\n");
+		printf("  %s [--dest=BDADDR] <file list>\n", g_get_prgname ());
+		g_error_free (err);
+		free_mem (app);
+		return 0;
+	}
 
-	error = NULL;
+	gtk_init(&argc, &argv);
 
-	conn = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
-	if (conn == NULL) {
-		if (error != NULL) {
-			g_printerr("Connecting to session bus failed: %s\n",
-							error->message);
-			g_error_free(error);
-		} else
-			g_print("An unknown error occured\n");
+	/* get a list of files to send from command-line */
+	if (files_to_send)
+		app->file_count = g_strv_length (files_to_send);
+	else
+		app->file_count = 0;
+	for (i = 0; i < app->file_count; i++) {
+		app->file_list = g_slist_append (app->file_list,
+						 g_strdup (*(files_to_send + i)));
+	}
+	/* show file chooser if no files were specified */
+	if (app->file_count == 0) {
+		GtkWidget *dialog;
+
+		dialog = gtk_file_chooser_dialog_new (_("Choose files to send"), NULL,
+						      GTK_FILE_CHOOSER_ACTION_OPEN,
+						      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+						      _("Send"), GTK_RESPONSE_ACCEPT,
+						      NULL);
+		gtk_window_set_icon_name (GTK_WINDOW (dialog), "stock_bluetooth");
+		gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dialog), TRUE);
+
+		if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
+			app->file_list = gtk_file_chooser_get_filenames (GTK_FILE_CHOOSER (dialog));
+			app->file_count = g_slist_length (app->file_list);
+		}
+		gtk_widget_destroy (dialog);
+	}
+	/* Check if there are some files to send */
+	if (app->file_count == 0) {
+		free_mem (app);
+		return 0;
+	}
+	/* Check for non regular and non existand files and
+	 * determine total bytes to send */
+	for (i = 0; i < app->file_count; i++) {
+		GSList *item;
+		gchar *path, *filename;
+		gint64 size;
+
+		item = g_slist_nth (app->file_list, i);
+		path = (gchar *) item->data;
+		filename = normalise_filename (path, &size);
+		if (filename == NULL) {
+			g_free (path);
+			app->file_list = g_slist_remove_link (app->file_list, item);
+			continue;
+		}
+		g_free (item->data);
+		item->data = filename;
+		app->byte_count += size;
+	}
 
-		gtk_exit(1);
+	/* determine Bluetooth device to send to */
+	if (!bdaddrstr) {
+		bdaddrstr = browse_device_dialog();
+		if (!bdaddrstr) {
+			free_mem (app);
+			return 0;
+		}
 	}
 
-	create_window(filename);
+	dbus_g_object_register_marshaller (marshal_VOID__STRING_STRING_INT,
+					   G_TYPE_NONE, G_TYPE_STRING,
+					   G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+	dbus_g_object_register_marshaller (marshal_VOID__STRING_STRING,
+					   G_TYPE_NONE, G_TYPE_STRING,
+					   G_TYPE_STRING, G_TYPE_INVALID);
+
+	/* init DBus connection */
+	app->connection = dbus_g_bus_get (DBUS_BUS_SESSION, &err);
+	if (app->connection == NULL) {
+		g_printerr ("Connecting to session bus failed: %s\n",
+			    err->message);
+		g_error_free (err);
+		free_mem (app);
+		return 1;
+	}
 
-	proxy = dbus_g_proxy_new_for_name(conn, "org.openobex",
-				"/org/openobex", "org.openobex.Manager");
+	/* init UI */
+	ui_init (app);
 
-	dbus_g_proxy_begin_call(proxy, "CreateBluetoothSession",
-				create_notify, filename, NULL,
-				G_TYPE_STRING, address, G_TYPE_STRING, "opp",
-				G_TYPE_INVALID);
+	/* init DBus proxy, create Session */
+	app->manager_proxy = dbus_g_proxy_new_for_name (app->connection,
+							"org.openobex",
+							"/org/openobex",
+							"org.openobex.Manager");
+
+	dbus_g_proxy_begin_call (app->manager_proxy, "CreateBluetoothSession",
+				 (DBusGProxyCallNotify) session_created_cb, app, NULL,
+				 G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+				 G_TYPE_INVALID);
 
-	gtk_main();
+	/* Go into main loop */
+	gtk_main ();
 
-	dbus_g_connection_unref(conn);
+	gtk_widget_destroy (app->main_dialog);
 
-	g_free(address);
-	g_free(filename);
+	/* done sending, free memory */
+	free_mem (app);
 
 	return 0;
 }
+

[-- Attachment #3: Type: text/plain, Size: 228 bytes --]

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/

[-- Attachment #4: Type: text/plain, Size: 164 bytes --]

_______________________________________________
Bluez-devel mailing list
Bluez-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bluez-devel

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Bluez-devel] [PATCH] Allow multiple files to be sent
  2008-02-01 13:14   ` Bastien Nocera
@ 2008-02-01 16:08     ` Bastien Nocera
  2008-02-01 16:20       ` Bastien Nocera
  0 siblings, 1 reply; 16+ messages in thread
From: Bastien Nocera @ 2008-02-01 16:08 UTC (permalink / raw)
  To: BlueZ development

[-- Attachment #1: Type: text/plain, Size: 873 bytes --]


On Fri, 2008-02-01 at 13:14 +0000, Bastien Nocera wrote:
> On Fri, 2008-01-18 at 13:43 +0000, Bastien Nocera wrote:
> > Hello again,
> > 
> > On Thu, 2008-01-17 at 10:43 +0000, Bastien Nocera wrote: 
> > > Heya,
> > > 
> > > Another for bluetooth-sento feature parity with gnome-obex-send.
> > 
> > Another patch, based on Tadas' gnome-obex-send port.
> > 
> > - Fix to run with the latest obex-data-server (in 2 places)
> > - Add an appropriate error when the target device is a Palm
> > - Accept relative local paths and URIs as well as absolute filenames as
> > arguments (needed for ease of use and nautilus-sendto, respectively)
> > - Gather the name of the Bluetooth device from hcid to show in the UI
> 
> Updated patch to use the marshal file in common/ instead of our own.

Updated patch:
- follow coding style
- move the helper dialogues in another file

Cheers

[-- Attachment #2: bluez-gnome-new-sendto-3.patch --]
[-- Type: text/x-patch, Size: 36941 bytes --]

Index: Makefile.am
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/Makefile.am,v
retrieving revision 1.8
diff -u -p -r1.8 Makefile.am
--- Makefile.am	1 Feb 2008 14:03:47 -0000	1.8
+++ Makefile.am	1 Feb 2008 16:04:22 -0000
@@ -1,7 +1,7 @@
 
 noinst_PROGRAMS = bluetooth-sendto
 
-bluetooth_sendto_SOURCES = main.c
+bluetooth_sendto_SOURCES = main.c utils.c utils.h
 
 bluetooth_sendto_LDADD = $(top_builddir)/common/libcommon.a \
 					@GTK_LIBS@ @DBUS_LIBS@
@@ -15,3 +15,4 @@ noinst_MANS = bluetooth-sendto.1
 EXTRA_DIST = $(noinst_MANS)
 
 MAINTAINERCLEANFILES = Makefile.in
+
Index: main.c
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/main.c,v
retrieving revision 1.12
diff -u -p -r1.12 main.c
--- main.c	16 Jan 2008 19:13:39 -0000	1.12
+++ main.c	1 Feb 2008 16:04:22 -0000
@@ -1,9 +1,11 @@
 /*
+ * bluetooth-sendto
  *
  *  BlueZ - Bluetooth protocol stack for Linux
  *
  *  Copyright (C) 2005-2007  Marcel Holtmann <marcel@holtmann.org>
- *
+ *  Copyright (C) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ *  Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -25,342 +27,765 @@
 #include <config.h>
 #endif
 
-#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
 
+#include <dbus/dbus-glib.h>
+#include <glib.h>
 #include <glib/gi18n.h>
-
+#include <glib/gstdio.h>
 #include <gtk/gtk.h>
 
-#include "bluetooth-device-selection.h"
-
+#include "dbus-glue.h"
 #include "marshal.h"
+#include "utils.h"
 
-static DBusGConnection *conn = NULL;
+typedef struct _appinfo {
+	/* DBus */
+	DBusGConnection *connection;
+	DBusGProxy *manager_proxy;
+	DBusGProxy *session_proxy;
+	/* GTK */
+	GtkWidget *main_dialog;
+	GtkWidget *from_label;
+	GtkWidget *operation_label;
+	GtkWidget *progress_bar;
+	/* sending related */
+	gsize file_length;
+	guint file_count;
+	guint current_file;
+	GSList *file_list;
+	guint byte_count;
+	guint bytes_sent;
+	gint64 first_transfer_time;
+	gint64 last_update_time;
+} BluetoothSendto;
+
+typedef enum {
+	BUTTONS_OK,
+	BUTTONS_RETRY_CANCEL,
+	BUTTONS_RETRY_SKIP_CANCEL
+} ButtonSet;
+
+static gchar **files_to_send = NULL;
+static gchar *bdaddrstr = NULL;
+static gchar *device_name = NULL;
+
+#define DIALOG_RESPONSE_SKIP 1
+#define DIALOG_RESPONSE_RETRY 2
+
+static void error_occurred_cb(DBusGProxy *proxy, const gchar *error_name,
+			      const gchar *error_message, BluetoothSendto *app);
+
+static const GOptionEntry options[] = {
+	{"dest", 'd', 0, G_OPTION_ARG_STRING, &bdaddrstr, 
+		"Bluetooth address of destination device", "BDADDR"},
+	{G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &files_to_send},
+	{NULL}
+};
 
-static GtkWidget *dialog;
-static GtkWidget *label_filename;
-static GtkWidget *label_status;
-static GtkWidget *progress;
+static gint64
+get_system_time(void)
+{
+	struct timeval tmp;
 
-static gint filesize = -1;
+	gettimeofday(&tmp, NULL);
+	return (gint64)tmp.tv_usec + (gint64)tmp.tv_sec * G_GINT64_CONSTANT(1000000);
+}
 
-static gchar *open_file_dialog(void)
+static gboolean
+is_palm_device(const gchar *bdaddr)
 {
-	GtkWidget *dialog;
-	gchar *filename = NULL;
+	return (g_str_has_prefix(bdaddr, "00:04:6B")
+		|| g_str_has_prefix(bdaddr, "00:07:E0")
+		|| g_str_has_prefix(bdaddr, "00:0E:20"));
+}
 
-	dialog = gtk_file_chooser_dialog_new(_("Select File"), NULL,
-				GTK_FILE_CHOOSER_ACTION_OPEN,
-				GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
-				GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
+/* Return the local absolute filename for the file, and its size
+ * if the file exists and is stat()'able */
+static gchar *
+normalise_filename(const gchar *filename, gint64 *size)
+{
+	char *ret;
+	struct stat file_stat;
 
-	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
-		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+	g_return_val_if_fail(filename != NULL, NULL);
+	g_return_val_if_fail(size != NULL, NULL);
 
-	gtk_widget_destroy(dialog);
+	if (g_str_has_prefix(filename, "file://")) {
+		ret = g_filename_from_uri(filename, NULL, NULL);
+		if (ret == NULL)
+			return NULL;
+	} else if (filename[0] == '/') {
+		ret = g_strdup(filename);
+	} else {
+		gchar *curdir;
 
-	return filename;
-}
+		curdir = g_get_current_dir();
+		ret = g_build_filename(curdir, filename, NULL);
+		g_free(curdir);
+	}
 
-static void selected_device_changed(BluetoothDeviceSelection *selector,
-					gchar *address, gpointer user_data)
-{
-	GtkWidget *dialog = user_data;
+	if (!g_file_test(ret, G_FILE_TEST_IS_REGULAR)) {
+		g_free(ret);
+		return NULL;
+	}
+
+	if (!g_stat(ret, &file_stat)) {
+		*size = file_stat.st_size;
+	} else {
+		g_free(ret);
+		return NULL;
+	}
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-				GTK_RESPONSE_ACCEPT, address != NULL);
+	return ret;
 }
 
-static gchar *browse_device_dialog(void)
+static gchar *
+get_device_name(BluetoothSendto *app)
 {
-	GtkWidget *dialog;
-	GtkWidget *selector;
-	gchar *address = NULL;
+	DBusGConnection *connection;
+	DBusGProxy *manager;
+	GError *error = NULL;
+	gchar *name, **adapters;
+	guint i;
 
-	dialog = gtk_dialog_new_with_buttons(_("Select Device"),
-				NULL, GTK_DIALOG_NO_SEPARATOR,
-				GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
-				GTK_STOCK_CONNECT, GTK_RESPONSE_ACCEPT, NULL);
+	name = NULL;
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_ACCEPT, FALSE);
+	connection = dbus_g_bus_get(DBUS_BUS_SYSTEM, NULL);
+	if (connection == NULL)
+		return NULL;
+
+	manager = dbus_g_proxy_new_for_name(connection, "org.bluez",
+					    "/org/bluez", "org.bluez.Manager");
+	if (!manager) {
+		dbus_g_connection_unref(connection);
+		return NULL;
+	}
 
-	gtk_window_set_default_size(GTK_WINDOW(dialog), 450, 300);
+	if (!manager_list_adapters(manager, &adapters, &error)) {
+		g_object_unref(manager);
+		dbus_g_connection_unref(connection);
+		return NULL;
+	}
 
-	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
+	for (i = 0; adapters[i] != NULL; i++) {
+		DBusGProxy *adapter;
 
-	selector = bluetooth_device_selection_new(_("Select destination device"));
+		adapter = dbus_g_proxy_new_for_name(connection, "org.bluez",
+						    adapters[i], "org.bluez.Adapter");
+		if (dbus_g_proxy_call(adapter, "GetRemoteName", NULL,
+				      G_TYPE_STRING, bdaddrstr, G_TYPE_INVALID,
+				      G_TYPE_STRING, &name, G_TYPE_INVALID)) {
+			if (name != NULL && name[0] != '\0') {
+				g_object_unref(adapter);
+				break;
+			}
+		}
+		g_object_unref(adapter);
+	}
 
-	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), selector);
+	g_object_unref(manager);
+	dbus_g_connection_unref(connection);
 
-	g_signal_connect(selector, "selected-device-changed",
-				G_CALLBACK(selected_device_changed), dialog);
+	return name;
+}
 
-	gtk_widget_show_all(selector);
+static gint
+show_error_dialog(GtkWindow *parent, ButtonSet buttons,
+		  const gchar *primary_text, const gchar *secondary_text)
+{
+	GtkWidget *dialog;
+	gchar *primary_text_markup;
+	gint ret;
 
-	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
-		g_object_get(selector, "device-selected", &address, NULL);
+	primary_text_markup = g_strdup_printf("<span weight=\"bold\" size=\"larger\">%s</span>", 
+					      primary_text);
+	dialog = gtk_message_dialog_new_with_markup(parent, 0, GTK_MESSAGE_ERROR, 
+						    buttons==BUTTONS_OK? GTK_BUTTONS_OK: GTK_BUTTONS_CANCEL, 
+						    primary_text_markup);
+	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
+						 secondary_text);
+	if (buttons == BUTTONS_RETRY_SKIP_CANCEL) {
+		gtk_dialog_add_button(GTK_DIALOG(dialog), _("Skip"),
+				      DIALOG_RESPONSE_SKIP);
+	}
+	if (buttons != BUTTONS_OK) {
+		gtk_dialog_add_button(GTK_DIALOG(dialog), _("Retry"),
+				      DIALOG_RESPONSE_RETRY);
+	}
+	ret = gtk_dialog_run(GTK_DIALOG(dialog));
 
 	gtk_widget_destroy(dialog);
+	g_free(primary_text_markup);
+	return ret;
+}
 
-	return address;
+static gint
+show_connection_error_dialog(GtkWindow *parent, const gchar *bdaddr,
+			     const gchar *primary_text)
+{
+	if (!is_palm_device(bdaddrstr)) {
+		return show_error_dialog(parent,
+					 BUTTONS_RETRY_CANCEL,
+					 primary_text,
+					 _("Make sure that remote device is on and that it accepts Bluetooth connections."));
+	} else {
+		return show_error_dialog(parent,
+					 BUTTONS_RETRY_CANCEL,
+					 primary_text,
+					 _("Make sure \"Beam receive\" is enabled in the Power preferences, and Bluetooth is enabled, on your Palm device."));
+	}
 }
 
-static void response_callback(GtkWidget *dialog,
-					gint response, gpointer user_data)
-{
-	gtk_widget_destroy(dialog);
+static gint
+handle_file_sending_error(const gchar *error_text,
+			  const gchar *error_secondary_text, BluetoothSendto *app)
+{
+	ButtonSet buttons;
+	gint response_id;
+
+	if (app->file_count > 1 && app->current_file < app->file_count - 1)
+		buttons = BUTTONS_RETRY_SKIP_CANCEL;
+	else
+		buttons = BUTTONS_RETRY_CANCEL;
+
+	response_id = show_error_dialog(GTK_WINDOW(app->main_dialog), buttons, 
+					error_text, error_secondary_text);
+
+	switch (response_id) {
+	case DIALOG_RESPONSE_SKIP:
+		app->current_file++;
+		app->bytes_sent += app->file_length;
+	case DIALOG_RESPONSE_RETRY:
+		return TRUE;
+	case GTK_RESPONSE_CANCEL:
+	default:
+		gtk_main_quit();
+		break;
+	}
+	return response_id;
+}
 
+static void
+cancel_clicked_cb(GtkWidget *button, BluetoothSendto *app)
+{
 	gtk_main_quit();
 }
 
-static void create_window(const gchar *filename)
+static void
+ui_init(BluetoothSendto *app)
 {
-	GtkWidget *vbox;
-	GtkWidget *label;
+	GtkWidget *vbox, *vbox_parent, *table;
+	GtkWidget *label1, *label2, *label3, *target_label, *from_label, *operation_label;
+	GtkWidget *progress_bar, *button;
 	gchar *text;
 
-	dialog = gtk_dialog_new_with_buttons(_("File Transfer"), NULL,
-				GTK_DIALOG_NO_SEPARATOR,
-				GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
-
-	gtk_window_set_default_size(GTK_WINDOW(dialog), 400, -1);
-
-	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
-
-	vbox = gtk_vbox_new(FALSE, 6);
-	gtk_container_set_border_width(GTK_CONTAINER(vbox), 12);
-	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox);
-
-	label = gtk_label_new(NULL);
-	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+	/* initialize UI */
 
-	label_filename = label;
+	device_name = get_device_name(app);
 
-	progress = gtk_progress_bar_new();
-	gtk_box_pack_start(GTK_BOX(vbox), progress, FALSE, FALSE, 0);
-
-	label = gtk_label_new(NULL);
-	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 6);
-
-	label_status = label;
+	/* main dialog */
+	app->main_dialog = gtk_dialog_new();
+	gtk_dialog_set_has_separator(GTK_DIALOG(app->main_dialog), FALSE);
+	gtk_window_set_title(GTK_WINDOW(app->main_dialog),
+			     _("Bluetooth file transfer"));
+	gtk_window_set_icon_name(GTK_WINDOW(app->main_dialog), "stock_bluetooth");
+	gtk_window_set_type_hint(GTK_WINDOW(app->main_dialog),
+				 GDK_WINDOW_TYPE_HINT_NORMAL);
+	gtk_window_set_position(GTK_WINDOW(app->main_dialog), GTK_WIN_POS_CENTER);
+	gtk_window_set_resizable(GTK_WINDOW(app->main_dialog), FALSE);
+	gtk_widget_set(app->main_dialog, "width-request", 400, NULL);
+	button = gtk_dialog_add_button(GTK_DIALOG(app->main_dialog),
+				       GTK_STOCK_CANCEL,
+				       GTK_RESPONSE_CANCEL);
+	g_signal_connect(G_OBJECT(button), "clicked",
+			 G_CALLBACK(cancel_clicked_cb), app);
+	gtk_container_set_border_width(GTK_CONTAINER(app->main_dialog), 6);
+
+	/* table elements */
+	from_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(from_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(from_label), PANGO_ELLIPSIZE_MIDDLE);
+	target_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(target_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(target_label), PANGO_ELLIPSIZE_END);
+	gtk_label_set_text(GTK_LABEL(target_label), device_name ? device_name : bdaddrstr);
+	label2 = gtk_label_new(NULL);
+	text = g_markup_printf_escaped("<b>%s</b>", _("From:"));
+	gtk_label_set_markup(GTK_LABEL(label2), text);
+	g_free(text);
+	label3 = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(label3), 1, 0.5);
+	text = g_markup_printf_escaped("<b>%s</b>", _("To:"));
+	gtk_label_set_markup(GTK_LABEL(label3), text);
+	g_free(text);
 
-	text = g_strdup_printf("<b>%s</b>", filename);
-	gtk_label_set_markup(GTK_LABEL(label_filename), text);
+	/* table */
+	table = gtk_table_new(2, 2, FALSE);
+	gtk_table_set_col_spacings(GTK_TABLE(table), 4);
+	gtk_table_set_row_spacings(GTK_TABLE(table), 4);
+	gtk_table_attach_defaults(GTK_TABLE(table), from_label, 1, 2, 0, 1);
+	gtk_table_attach_defaults(GTK_TABLE(table), target_label, 1, 2, 1, 2);
+	gtk_table_attach(GTK_TABLE(table), label2, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), label3, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+
+	/* vbox elements */
+	label1 = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(label1), 0, 0.5);
+	text = g_markup_printf_escaped("<span weight=\"bold\" size=\"larger\">%s</span>",
+				       _("Sending files via bluetooth"));
+	gtk_label_set_markup(GTK_LABEL(label1), text);
 	g_free(text);
+	progress_bar = gtk_progress_bar_new();
+	gtk_progress_bar_set_ellipsize(GTK_PROGRESS_BAR(progress_bar),
+				       PANGO_ELLIPSIZE_END);
+	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
+				  _("Connecting..."));
+	operation_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(operation_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(operation_label), PANGO_ELLIPSIZE_END);
+
+	/* vbox */
+	vbox = gtk_vbox_new(FALSE, 0);
+	gtk_box_set_spacing(GTK_BOX(vbox), 6);
+	gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
+	gtk_box_pack_start_defaults(GTK_BOX(vbox), label1);
+	gtk_box_pack_start(GTK_BOX(vbox), table, TRUE, TRUE, 9);
+	gtk_box_pack_start_defaults(GTK_BOX(vbox), progress_bar);
+	gtk_box_pack_start(GTK_BOX(vbox), operation_label, TRUE, TRUE, 2);
+	vbox_parent = gtk_bin_get_child(GTK_BIN(app->main_dialog));
+	gtk_box_pack_start_defaults(GTK_BOX(vbox_parent), vbox);
+
+	/* Set widget variables in app */
+	app->from_label = from_label;
+	app->operation_label = operation_label;
+	app->progress_bar = progress_bar;
+
+	/* show it */
+	gtk_widget_show_all(app->main_dialog);
+}
+
+static gboolean
+send_one(BluetoothSendto *app)
+{
+	const gchar *fname;
+	gint ret;
+	gchar *bname, *dirname;
+	GError *err = NULL;
+	gchar *operation_text, *operation_markup, *progressbar_text;
+
+	if ((fname = g_slist_nth_data(app->file_list, app->current_file))) {
+		/* there's a file to send */
+		dbus_g_proxy_call(app->session_proxy, "SendFile", &err,
+				  G_TYPE_STRING, fname, G_TYPE_INVALID, G_TYPE_INVALID);
+
+		if (err != NULL) {
+			ret = handle_file_sending_error(_("Unable to read file"),
+							err->message, app);
+			if (ret != GTK_RESPONSE_CANCEL) {
+				g_idle_add((GSourceFunc) send_one, app);
+			}
+			g_error_free(err);
+			return FALSE;
+		}
 
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Connecting..."));
+		if (!app->first_transfer_time) {
+			app->first_transfer_time = get_system_time();
+		}
+		bname = g_path_get_basename(fname);
+		dirname = g_path_get_dirname(fname);
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, FALSE);
+		operation_text = g_strdup_printf(_("Sending %s"), bname);
+		operation_markup = g_markup_printf_escaped("<i>%s</i>", operation_text);
+		progressbar_text = g_strdup_printf(_("Sending file: %d of %d"),
+						   app->current_file+1,
+						   app->file_count);
+		gtk_label_set_text(GTK_LABEL(app->from_label), dirname);
+		gtk_label_set_markup(GTK_LABEL(app->operation_label), operation_markup);
+		gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app->progress_bar),
+					  progressbar_text);
+
+		g_free(bname);
+		g_free(dirname);
+		g_free(operation_text);
+		g_free(operation_markup);
+		g_free(progressbar_text);
+	} else {
+		/* nothing left to send, time to quit */
+		gtk_main_quit();
+	}
+	return FALSE;
+}
 
-	g_signal_connect(G_OBJECT(dialog), "response",
-				G_CALLBACK(response_callback), NULL);
+static void
+session_connected_cb(DBusGProxy *proxy, BluetoothSendto *app)
+{
+	/* OBEX connect has succeeded, so start sending */
+	g_idle_add((GSourceFunc) send_one, app);
+}
 
-	gtk_widget_show_all(dialog);
+static void
+transfer_started_cb(DBusGProxy *proxy, const gchar *filename,
+		    const gchar *local_path, gint byte_count, BluetoothSendto *app)
+{
+	app->file_length = byte_count;
 }
 
-static void transfer_started(DBusGProxy *proxy, gchar *a, gchar *b,
-						gint size, gpointer user_data)
+static void
+transfer_cancelled_cb(DBusGProxy *proxy, BluetoothSendto *app)
 {
-	gchar *text;
+	gint ret;
+	gchar *error_text;
 
-	filesize = size;
+	error_text = g_strdup_printf(_("An error occured while sending file '%s'"),
+				     (gchar *)g_slist_nth_data(app->file_list, app->current_file));
+	ret = handle_file_sending_error(error_text,
+					_("The remote device cancelled the transfer"), app);
 
-	text = g_strdup_printf(_("Starting transfer of %d bytes"), size);
-	gtk_label_set_markup(GTK_LABEL(label_status), text);
-	g_free(text);
+	if (ret != GTK_RESPONSE_CANCEL)
+		g_idle_add((GSourceFunc) send_one, app);
 
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0.0);
+	g_free(error_text);
 }
 
-static void transfer_progress(DBusGProxy *proxy, guint bytes, gpointer user_data)
+static void
+transfer_progress_cb(DBusGProxy *proxy, gint bytes_transferred, BluetoothSendto *app)
 {
-	gchar *text;
-	gdouble fraction;
+	gdouble frac;
+	guint actual_bytes_sent;
+	gint elapsed_time;
+	gint transfer_rate;
+	gint time_remaining;
+	gint64 current_time;
+	gchar *str, *str2;
+	gchar *progressbar_text;
 
-	text = g_strdup_printf(_("Transfered %d of %d bytes"), bytes, filesize);
-	gtk_label_set_markup(GTK_LABEL(label_status), text);
-	g_free(text);
+	/* update progress bar fraction */
+	actual_bytes_sent = app->bytes_sent + bytes_transferred;
+	frac = (gdouble)actual_bytes_sent / app->byte_count;
+	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(app->progress_bar), frac);
 
-	fraction = (gdouble) bytes / (gdouble) filesize;
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), fraction);
-}
+	/* update progress bar text (time remaining)
+	 * (only update once in a second) */
+	current_time = get_system_time();
+	elapsed_time = (current_time - app->first_transfer_time) / 1000000;
 
-static void transfer_completed(DBusGProxy *proxy, gpointer user_data)
-{
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Completed"));
+	if (app->last_update_time) {
+		if (current_time < app->last_update_time + 1000000)
+			return;
+		else
+			app->last_update_time = current_time;
+	} else {
+		app->last_update_time = current_time;
+	}
+
+	if (!elapsed_time)
+		return;
+
+	transfer_rate = actual_bytes_sent / elapsed_time;
+	if (transfer_rate == 0)
+		return;
 
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 1.0);
+	time_remaining = (app->byte_count - actual_bytes_sent) / transfer_rate;
 
-	dbus_g_proxy_call(proxy, "Disconnect", NULL, G_TYPE_INVALID,
-							G_TYPE_INVALID);
+	if (time_remaining >= 3600) {
+		str = g_strdup_printf(_("(%d:%02d:%d Remaining)"), 
+				      time_remaining / 3600,
+				      (time_remaining % 3600) / 60,
+				      (time_remaining % 3600) % 60);
+	} else {
+		str = g_strdup_printf(_("(%d:%02d Remaining)"), 
+				      time_remaining / 60,
+				      time_remaining % 60);
+	}
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, TRUE);
+	/* Translators:
+	 * Sending file 1 of 3 */
+	str2 = g_strdup_printf(_("Sending file %d of %d"),
+			       app->current_file + 1,
+			       app->file_count);
+	progressbar_text = g_strconcat(str2, " ", str, NULL);
+	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app->progress_bar),
+				  progressbar_text);
+	g_free(str);
+	g_free(str2);
+	g_free(progressbar_text);
+}
+
+static void
+transfer_completed_cb(DBusGProxy *proxy, BluetoothSendto *app)
+{
+	app->current_file++;
+	app->bytes_sent += app->file_length;
+
+	g_idle_add((GSourceFunc) send_one, app);
 }
 
-static void session_connected(DBusGProxy *proxy, gpointer user_data)
+static void session_created_cb(DBusGProxy *proxy, DBusGProxyCall *call,
+			       BluetoothSendto *app)
 {
-	gchar *filename = user_data;
 	GError *error = NULL;
+	const gchar *path = NULL;
 
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Connected"));
+	if (!dbus_g_proxy_end_call(proxy, call, &error,
+				  G_TYPE_STRING, &path, G_TYPE_INVALID)) {
+		const gchar *error_name = NULL;
+		gint response_id;
+
+		if (error && error->code == DBUS_GERROR_REMOTE_EXCEPTION)
+			error_name = dbus_g_error_get_name(error);
+		if (error_name && !strcmp(error_name, "org.openobex.Error.ConnectionAttemptFailed")) {
+			response_id = show_connection_error_dialog(GTK_WINDOW(app->main_dialog), bdaddrstr, error->message);
+			if (response_id == DIALOG_RESPONSE_RETRY) {
+				/* Try to create Session again */
+				dbus_g_proxy_begin_call(app->manager_proxy,
+							"CreateBluetoothSession",
+							(DBusGProxyCallNotify) session_created_cb, app, NULL,
+							G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+							G_TYPE_BOOLEAN, FALSE, G_TYPE_INVALID);
+				goto out;
+			}
+		} else {
+			show_error_dialog(GTK_WINDOW(app->main_dialog), BUTTONS_OK,
+					  error->message, "");
+		}
 
-	dbus_g_proxy_call(proxy, "SendFile", &error,
-				G_TYPE_STRING, filename, G_TYPE_INVALID,
-							G_TYPE_INVALID);
-
-	if (error != NULL) {
-		g_printerr("Sending of file %s failed: %s\n", filename,
-							error->message);
-		g_error_free(error);
+		/* Failed, quit main loop */
 		gtk_main_quit();
+		goto out;
 	}
-}
 
-static void create_notify(DBusGProxy *proxy,
-				DBusGProxyCall *call, void *user_data)
-{
-	GError *error = NULL;
-	const gchar *path = NULL;
+	app->session_proxy = dbus_g_proxy_new_for_name(app->connection,
+						       "org.openobex",
+						       path,
+						       "org.openobex.Session");
 
-	if (dbus_g_proxy_end_call(proxy, call, &error,
-			G_TYPE_STRING, &path, G_TYPE_INVALID) == FALSE) {
-		gchar *text, *message;
-
-		if (error != NULL) {
-			message = g_strdup(error->message);
-			g_error_free(error);
-		} else
-			message = g_strdup(_("An unknown error occured"));
-
-		text = g_strdup_printf("<span foreground=\"red\">%s</span>",
-								message);
-		gtk_label_set_markup(GTK_LABEL(label_status), text);
-		g_free(text);
+	dbus_g_proxy_add_signal(app->session_proxy, "Connected",
+				G_TYPE_INVALID);
 
-		g_free(message);
+	dbus_g_proxy_connect_signal(app->session_proxy, "Connected",
+				    G_CALLBACK(session_connected_cb), app, NULL);
 
-		gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, TRUE);
-		return;
-	}
+	dbus_g_proxy_add_signal(app->session_proxy, "TransferStarted",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+
+	dbus_g_proxy_connect_signal(app->session_proxy, "TransferStarted",
+				    G_CALLBACK(transfer_started_cb), app, NULL);
 
-	proxy = dbus_g_proxy_new_for_name(conn, "org.openobex",
-						path, "org.openobex.Session");
+	dbus_g_proxy_add_signal(app->session_proxy, "Cancelled",
+				G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "Connected", G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(app->session_proxy, "Cancelled",
+				    G_CALLBACK(transfer_cancelled_cb), app, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "Connected",
-				G_CALLBACK(session_connected), user_data, NULL);
+	dbus_g_proxy_add_signal(app->session_proxy, "ErrorOccurred",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "TransferStarted", G_TYPE_STRING,
-				G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(app->session_proxy, "ErrorOccurred",
+				    G_CALLBACK(error_occurred_cb), app, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferStarted",
-				G_CALLBACK(transfer_started), NULL, NULL);
+	dbus_g_proxy_add_signal(app->session_proxy, "TransferProgress",
+				G_TYPE_UINT, G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "TransferProgress",
-						G_TYPE_UINT, G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(app->session_proxy, "TransferProgress",
+				    G_CALLBACK(transfer_progress_cb), app, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferProgress",
-				G_CALLBACK(transfer_progress), NULL, NULL);
+	dbus_g_proxy_add_signal(app->session_proxy, "TransferCompleted", 
+				G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "TransferCompleted", G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(app->session_proxy, "TransferCompleted",
+				    G_CALLBACK(transfer_completed_cb), app, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferCompleted",
-				G_CALLBACK(transfer_completed), NULL, NULL);
+	dbus_g_proxy_call(app->session_proxy, "Connect", &error, G_TYPE_INVALID,
+			  G_TYPE_INVALID);
 
-	dbus_g_proxy_call(proxy, "Connect", NULL, G_TYPE_INVALID,
-							G_TYPE_INVALID);
+out:
+	if (error)
+		g_error_free(error);
 }
 
-static gchar *option_device = NULL;
+static void
+error_occurred_cb(DBusGProxy *proxy, const gchar *error_name,	
+		  const gchar *error_message, BluetoothSendto *app)
+{
+	gint ret;
+	gboolean link_error = FALSE;
+	gchar *error_text;
+	const gchar *reason;
+
+	g_message("ErrorOccurred");
+	g_message("Error name: %s", error_name);
+	g_message("Error message: %s", error_message);
+
+	error_text = g_strdup_printf(_("An error occured while sending file '%s'"),
+				     (gchar *)g_slist_nth_data(app->file_list, app->current_file));
+	if (!strcmp(error_name, "org.openobex.Error.LinkError")) {
+		/* Connection to remote device was lost */
+		g_object_unref(G_OBJECT(app->session_proxy));
+		reason = _("Connection to remote device was lost");
+		link_error = TRUE;
+	} else if (!strcmp(error_name, "org.openobex.Error.Forbidden")) {
+		reason = _("Remote device rejected file");
+	} else {
+		reason = _("Unknown error");
+	}
+	ret = handle_file_sending_error(error_text, reason, app);
 
-static GOptionEntry options[] = {
-	{ "device", 0, 0, G_OPTION_ARG_STRING, &option_device,
-				N_("Remote device to use"), "ADDRESS" },
-	{ NULL },
-};
+	if (ret != GTK_RESPONSE_CANCEL) {
+		if (link_error) {
+			/* Need to reestablish connection */
+			dbus_g_proxy_begin_call(app->manager_proxy, "CreateBluetoothSession",
+						(DBusGProxyCallNotify) session_created_cb, app, NULL,
+						G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+						G_TYPE_INVALID);
+		} else {
+			g_idle_add((GSourceFunc) send_one, app);
+		}
+	}
+
+
+	g_free(error_text);
+}
 
-int main(int argc, char *argv[])
+static void
+free_mem(BluetoothSendto *app)
 {
-	DBusGProxy *proxy;
-	GError *error;
-	gchar *filename, *address;
+	if (files_to_send)
+		g_strfreev(files_to_send);
+	g_free(bdaddrstr);
+
+	if (app) {
+		if (G_IS_OBJECT(app->manager_proxy))
+			g_object_unref(G_OBJECT(app->manager_proxy));
+		if (G_IS_OBJECT(app->session_proxy))
+			g_object_unref (G_OBJECT(app->session_proxy));
+		g_slist_free(app->file_list);
+		g_free(app);
+	}
+}
+
+int
+main(int argc, char *argv[])
+{
+	GOptionContext *option_context;
+	GError *err = NULL;
+	guint i;
+	BluetoothSendto *app;
+
+	app = g_new0(BluetoothSendto, 1);
 
+	/* initialize gettext */
 	bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
 	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
 	textdomain(GETTEXT_PACKAGE);
 
-	error = NULL;
-
-	if (gtk_init_with_args(&argc, &argv, "[FILE...]",
-				options, GETTEXT_PACKAGE, &error) == FALSE) {
-		if (error != NULL) {
-			g_printerr("%s\n", error->message);
-			g_error_free(error);
-		} else
-			g_printerr("An unknown error occurred\n");
-
-		gtk_exit(1);
-	}
-
-	gtk_window_set_default_icon_name("stock_bluetooth");
-
-	if (argc < 2) {
-		filename = open_file_dialog();
-		if (filename == NULL)
-			gtk_exit(1);
-	} else
-		filename = g_strdup(argv[1]);
-
-	if (option_device == NULL) {
-		address = browse_device_dialog();
-		if (address == NULL) {
-			g_free(filename);
-			gtk_exit(1);
-		}
-	} else
-		address = g_strdup(option_device);
-
-	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING_INT,
-				G_TYPE_NONE, G_TYPE_STRING,
-				G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+	/* parse command line arguments */
+	option_context = g_option_context_new("<file list>");
+	g_option_context_add_main_entries(option_context, options, GETTEXT_PACKAGE);
+	g_option_context_add_group(option_context, gtk_get_option_group(TRUE));
+	g_option_context_parse(option_context, &argc, &argv, &err);
+	g_option_context_free(option_context);
+	if (err) {
+		printf("Usage:\n");
+		printf("  %s [--dest=BDADDR] <file list>\n", g_get_prgname());
+		g_error_free(err);
+		free_mem(app);
+		return 0;
+	}
 
-	error = NULL;
+	gtk_init(&argc, &argv);
 
-	conn = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
-	if (conn == NULL) {
-		if (error != NULL) {
-			g_printerr("Connecting to session bus failed: %s\n",
-							error->message);
-			g_error_free(error);
-		} else
-			g_print("An unknown error occured\n");
+	/* get a list of files to send from command-line */
+	if (files_to_send)
+		app->file_count = g_strv_length(files_to_send);
+	else
+		app->file_count = 0;
+	for (i = 0; i < app->file_count; i++) {
+		app->file_list = g_slist_append(app->file_list,
+						g_strdup(*(files_to_send + i)));
+	}
+	/* show file chooser if no files were specified */
+	if (!app->file_count) {
+		app->file_list = select_files_dialog();
+		app->file_count = g_slist_length(app->file_list);
+	}
+	/* Check if there are some files to send */
+	if (!app->file_count) {
+		free_mem(app);
+		return 0;
+	}
+	/* Check for non regular and non existand files and
+	 * determine total bytes to send */
+	for (i = 0; i < app->file_count; i++) {
+		GSList *item;
+		gchar *path, *filename;
+		gint64 size;
+
+		item = g_slist_nth(app->file_list, i);
+		path = (gchar *) item->data;
+		filename = normalise_filename(path, &size);
+		if (filename == NULL) {
+			g_free(path);
+			app->file_list = g_slist_remove_link(app->file_list, item);
+			continue;
+		}
+		g_free(item->data);
+		item->data = filename;
+		app->byte_count += size;
+	}
 
-		gtk_exit(1);
+	/* determine Bluetooth device to send to */
+	if (!bdaddrstr) {
+		bdaddrstr = browse_device_dialog();
+		if (!bdaddrstr) {
+			free_mem(app);
+			return 0;
+		}
 	}
 
-	create_window(filename);
+	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING_INT,
+					  G_TYPE_NONE, G_TYPE_STRING,
+					  G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING,
+					  G_TYPE_NONE, G_TYPE_STRING,
+					  G_TYPE_STRING, G_TYPE_INVALID);
+
+	/* init DBus connection */
+	app->connection = dbus_g_bus_get(DBUS_BUS_SESSION, &err);
+	if (!app->connection) {
+		g_printerr("Connecting to session bus failed: %s\n",
+			   err->message);
+		g_error_free(err);
+		free_mem(app);
+		return 1;
+	}
 
-	proxy = dbus_g_proxy_new_for_name(conn, "org.openobex",
-				"/org/openobex", "org.openobex.Manager");
+	/* init UI */
+	ui_init(app);
 
-	dbus_g_proxy_begin_call(proxy, "CreateBluetoothSession",
-				create_notify, filename, NULL,
-				G_TYPE_STRING, address, G_TYPE_STRING, "opp",
+	/* init DBus proxy, create Session */
+	app->manager_proxy = dbus_g_proxy_new_for_name(app->connection,
+						       "org.openobex",
+						       "/org/openobex",
+						       "org.openobex.Manager");
+
+	dbus_g_proxy_begin_call(app->manager_proxy, "CreateBluetoothSession",
+				(DBusGProxyCallNotify) session_created_cb, app, NULL,
+				G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
 				G_TYPE_INVALID);
 
+	/* Go into main loop */
 	gtk_main();
 
-	dbus_g_connection_unref(conn);
+	gtk_widget_destroy(app->main_dialog);
 
-	g_free(address);
-	g_free(filename);
+	/* done sending, free memory */
+	free_mem(app);
 
 	return 0;
 }
+
--- /dev/null	2008-01-24 17:45:36.352009045 +0000
+++ utils.c	2008-02-01 16:00:39.000000000 +0000
@@ -0,0 +1,115 @@
+/*
+ * bluetooth-sendto
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-2007  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ *  Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+
+#include "bluetooth-device-selection.h"
+
+static void
+selected_device_changed_cb(BluetoothDeviceSelection *selector,
+			   gchar *address, gpointer user_data)
+{
+	GtkWidget *dialog = user_data;
+
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
+					  GTK_RESPONSE_ACCEPT, address != NULL);
+}
+
+gchar *
+browse_device_dialog(void)
+{
+	GtkWidget *dialog;
+	GtkWidget *selector;
+	gchar *address = NULL;
+
+	dialog = gtk_dialog_new_with_buttons(_("Select Device"),
+					     NULL, GTK_DIALOG_NO_SEPARATOR,
+					     GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+					     GTK_STOCK_CONNECT, GTK_RESPONSE_ACCEPT, NULL);
+
+	gtk_window_set_icon_name(GTK_WINDOW(dialog), "stock_bluetooth");
+
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
+					  GTK_RESPONSE_ACCEPT, FALSE);
+
+	gtk_window_set_default_size(GTK_WINDOW(dialog), 450, 300);
+
+	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
+
+	selector = bluetooth_device_selection_new(_("Select destination device"));
+	bluetooth_device_selection_start_discovery(
+	    BLUETOOTH_DEVICE_SELECTION(selector));
+
+	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), selector);
+
+	g_signal_connect(selector, "selected-device-changed",
+			 G_CALLBACK(selected_device_changed_cb), dialog);
+
+	gtk_widget_show_all(selector);
+
+	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
+		g_object_get(selector, "device-selected", &address, NULL);
+
+	gtk_widget_destroy(dialog);
+
+	return address;
+}
+
+GSList *
+select_files_dialog(void)
+{
+	GtkWidget *dialog;
+	GSList *file_list;
+
+	file_list = NULL;
+	dialog = gtk_file_chooser_dialog_new(_("Choose files to send"), NULL,
+					     GTK_FILE_CHOOSER_ACTION_OPEN,
+					     GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+					     _("Send"), GTK_RESPONSE_ACCEPT,
+					     NULL);
+	gtk_window_set_icon_name(GTK_WINDOW(dialog), "stock_bluetooth");
+	gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
+
+	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+		file_list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
+	}
+	gtk_widget_destroy(dialog);
+
+	return file_list;
+}
+
--- /dev/null	2008-01-24 17:45:36.352009045 +0000
+++ utils.h	2008-02-01 16:00:15.000000000 +0000
@@ -0,0 +1,30 @@
+/*
+ * bluetooth-sendto
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-2007  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ *  Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <glib.h>
+
+gchar *browse_device_dialog(void);
+GSList *select_files_dialog(void);
+

[-- Attachment #3: Type: text/plain, Size: 228 bytes --]

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/

[-- Attachment #4: Type: text/plain, Size: 164 bytes --]

_______________________________________________
Bluez-devel mailing list
Bluez-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bluez-devel

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Bluez-devel] [PATCH] Allow multiple files to be sent
  2008-02-01 16:08     ` Bastien Nocera
@ 2008-02-01 16:20       ` Bastien Nocera
  2008-02-06 12:18         ` [Bluez-devel] [PATCH] Updated sendto Bastien Nocera
  0 siblings, 1 reply; 16+ messages in thread
From: Bastien Nocera @ 2008-02-01 16:20 UTC (permalink / raw)
  To: BlueZ development

[-- Attachment #1: Type: text/plain, Size: 1010 bytes --]


On Fri, 2008-02-01 at 16:08 +0000, Bastien Nocera wrote:
> On Fri, 2008-02-01 at 13:14 +0000, Bastien Nocera wrote:
> > On Fri, 2008-01-18 at 13:43 +0000, Bastien Nocera wrote:
> > > Hello again,
> > > 
> > > On Thu, 2008-01-17 at 10:43 +0000, Bastien Nocera wrote: 
> > > > Heya,
> > > > 
> > > > Another for bluetooth-sento feature parity with gnome-obex-send.
> > > 
> > > Another patch, based on Tadas' gnome-obex-send port.
> > > 
> > > - Fix to run with the latest obex-data-server (in 2 places)
> > > - Add an appropriate error when the target device is a Palm
> > > - Accept relative local paths and URIs as well as absolute filenames as
> > > arguments (needed for ease of use and nautilus-sendto, respectively)
> > > - Gather the name of the Bluetooth device from hcid to show in the UI
> > 
> > Updated patch to use the marshal file in common/ instead of our own.
> 
> Updated patch:
> - follow coding style
> - move the helper dialogues in another file

And the function declarations on one line.

[-- Attachment #2: bluez-gnome-new-sendto-4.patch --]
[-- Type: text/x-patch, Size: 36909 bytes --]

Index: Makefile.am
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/Makefile.am,v
retrieving revision 1.8
diff -u -p -r1.8 Makefile.am
--- Makefile.am	1 Feb 2008 14:03:47 -0000	1.8
+++ Makefile.am	1 Feb 2008 16:19:25 -0000
@@ -1,7 +1,7 @@
 
 noinst_PROGRAMS = bluetooth-sendto
 
-bluetooth_sendto_SOURCES = main.c
+bluetooth_sendto_SOURCES = main.c utils.c utils.h
 
 bluetooth_sendto_LDADD = $(top_builddir)/common/libcommon.a \
 					@GTK_LIBS@ @DBUS_LIBS@
@@ -15,3 +15,4 @@ noinst_MANS = bluetooth-sendto.1
 EXTRA_DIST = $(noinst_MANS)
 
 MAINTAINERCLEANFILES = Makefile.in
+
Index: main.c
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/main.c,v
retrieving revision 1.12
diff -u -p -r1.12 main.c
--- main.c	16 Jan 2008 19:13:39 -0000	1.12
+++ main.c	1 Feb 2008 16:19:26 -0000
@@ -1,9 +1,11 @@
 /*
+ * bluetooth-sendto
  *
  *  BlueZ - Bluetooth protocol stack for Linux
  *
  *  Copyright (C) 2005-2007  Marcel Holtmann <marcel@holtmann.org>
- *
+ *  Copyright (C) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ *  Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -25,342 +27,747 @@
 #include <config.h>
 #endif
 
-#include <dbus/dbus-glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
 
+#include <dbus/dbus-glib.h>
+#include <glib.h>
 #include <glib/gi18n.h>
-
+#include <glib/gstdio.h>
 #include <gtk/gtk.h>
 
-#include "bluetooth-device-selection.h"
-
+#include "dbus-glue.h"
 #include "marshal.h"
+#include "utils.h"
 
-static DBusGConnection *conn = NULL;
+typedef struct _appinfo {
+	/* DBus */
+	DBusGConnection *connection;
+	DBusGProxy *manager_proxy;
+	DBusGProxy *session_proxy;
+	/* GTK */
+	GtkWidget *main_dialog;
+	GtkWidget *from_label;
+	GtkWidget *operation_label;
+	GtkWidget *progress_bar;
+	/* sending related */
+	gsize file_length;
+	guint file_count;
+	guint current_file;
+	GSList *file_list;
+	guint byte_count;
+	guint bytes_sent;
+	gint64 first_transfer_time;
+	gint64 last_update_time;
+} BluetoothSendto;
+
+typedef enum {
+	BUTTONS_OK,
+	BUTTONS_RETRY_CANCEL,
+	BUTTONS_RETRY_SKIP_CANCEL
+} ButtonSet;
+
+static gchar **files_to_send = NULL;
+static gchar *bdaddrstr = NULL;
+static gchar *device_name = NULL;
+
+#define DIALOG_RESPONSE_SKIP 1
+#define DIALOG_RESPONSE_RETRY 2
+
+static void error_occurred_cb(DBusGProxy *proxy, const gchar *error_name,
+			      const gchar *error_message, BluetoothSendto *app);
+
+static const GOptionEntry options[] = {
+	{"dest", 'd', 0, G_OPTION_ARG_STRING, &bdaddrstr, 
+		"Bluetooth address of destination device", "BDADDR"},
+	{G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &files_to_send},
+	{NULL}
+};
 
-static GtkWidget *dialog;
-static GtkWidget *label_filename;
-static GtkWidget *label_status;
-static GtkWidget *progress;
+static gint64 get_system_time(void)
+{
+	struct timeval tmp;
 
-static gint filesize = -1;
+	gettimeofday(&tmp, NULL);
+	return (gint64)tmp.tv_usec + (gint64)tmp.tv_sec * G_GINT64_CONSTANT(1000000);
+}
 
-static gchar *open_file_dialog(void)
+static gboolean is_palm_device(const gchar *bdaddr)
 {
-	GtkWidget *dialog;
-	gchar *filename = NULL;
+	return (g_str_has_prefix(bdaddr, "00:04:6B")
+		|| g_str_has_prefix(bdaddr, "00:07:E0")
+		|| g_str_has_prefix(bdaddr, "00:0E:20"));
+}
 
-	dialog = gtk_file_chooser_dialog_new(_("Select File"), NULL,
-				GTK_FILE_CHOOSER_ACTION_OPEN,
-				GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
-				GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
+/* Return the local absolute filename for the file, and its size
+ * if the file exists and is stat()'able */
+static gchar *normalise_filename(const gchar *filename, gint64 *size)
+{
+	char *ret;
+	struct stat file_stat;
 
-	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
-		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+	g_return_val_if_fail(filename != NULL, NULL);
+	g_return_val_if_fail(size != NULL, NULL);
 
-	gtk_widget_destroy(dialog);
+	if (g_str_has_prefix(filename, "file://")) {
+		ret = g_filename_from_uri(filename, NULL, NULL);
+		if (ret == NULL)
+			return NULL;
+	} else if (filename[0] == '/') {
+		ret = g_strdup(filename);
+	} else {
+		gchar *curdir;
 
-	return filename;
-}
+		curdir = g_get_current_dir();
+		ret = g_build_filename(curdir, filename, NULL);
+		g_free(curdir);
+	}
 
-static void selected_device_changed(BluetoothDeviceSelection *selector,
-					gchar *address, gpointer user_data)
-{
-	GtkWidget *dialog = user_data;
+	if (!g_file_test(ret, G_FILE_TEST_IS_REGULAR)) {
+		g_free(ret);
+		return NULL;
+	}
+
+	if (!g_stat(ret, &file_stat)) {
+		*size = file_stat.st_size;
+	} else {
+		g_free(ret);
+		return NULL;
+	}
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-				GTK_RESPONSE_ACCEPT, address != NULL);
+	return ret;
 }
 
-static gchar *browse_device_dialog(void)
+static gchar *get_device_name(BluetoothSendto *app)
 {
-	GtkWidget *dialog;
-	GtkWidget *selector;
-	gchar *address = NULL;
+	DBusGConnection *connection;
+	DBusGProxy *manager;
+	GError *error = NULL;
+	gchar *name, **adapters;
+	guint i;
 
-	dialog = gtk_dialog_new_with_buttons(_("Select Device"),
-				NULL, GTK_DIALOG_NO_SEPARATOR,
-				GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
-				GTK_STOCK_CONNECT, GTK_RESPONSE_ACCEPT, NULL);
+	name = NULL;
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_ACCEPT, FALSE);
+	connection = dbus_g_bus_get(DBUS_BUS_SYSTEM, NULL);
+	if (connection == NULL)
+		return NULL;
+
+	manager = dbus_g_proxy_new_for_name(connection, "org.bluez",
+					    "/org/bluez", "org.bluez.Manager");
+	if (!manager) {
+		dbus_g_connection_unref(connection);
+		return NULL;
+	}
 
-	gtk_window_set_default_size(GTK_WINDOW(dialog), 450, 300);
+	if (!manager_list_adapters(manager, &adapters, &error)) {
+		g_object_unref(manager);
+		dbus_g_connection_unref(connection);
+		return NULL;
+	}
 
-	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
+	for (i = 0; adapters[i] != NULL; i++) {
+		DBusGProxy *adapter;
 
-	selector = bluetooth_device_selection_new(_("Select destination device"));
+		adapter = dbus_g_proxy_new_for_name(connection, "org.bluez",
+						    adapters[i], "org.bluez.Adapter");
+		if (dbus_g_proxy_call(adapter, "GetRemoteName", NULL,
+				      G_TYPE_STRING, bdaddrstr, G_TYPE_INVALID,
+				      G_TYPE_STRING, &name, G_TYPE_INVALID)) {
+			if (name != NULL && name[0] != '\0') {
+				g_object_unref(adapter);
+				break;
+			}
+		}
+		g_object_unref(adapter);
+	}
 
-	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), selector);
+	g_object_unref(manager);
+	dbus_g_connection_unref(connection);
 
-	g_signal_connect(selector, "selected-device-changed",
-				G_CALLBACK(selected_device_changed), dialog);
+	return name;
+}
 
-	gtk_widget_show_all(selector);
+static gint show_error_dialog(GtkWindow *parent, ButtonSet buttons,
+			      const gchar *primary_text, const gchar *secondary_text)
+{
+	GtkWidget *dialog;
+	gchar *primary_text_markup;
+	gint ret;
 
-	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
-		g_object_get(selector, "device-selected", &address, NULL);
+	primary_text_markup = g_strdup_printf("<span weight=\"bold\" size=\"larger\">%s</span>", 
+					      primary_text);
+	dialog = gtk_message_dialog_new_with_markup(parent, 0, GTK_MESSAGE_ERROR, 
+						    buttons==BUTTONS_OK? GTK_BUTTONS_OK: GTK_BUTTONS_CANCEL, 
+						    primary_text_markup);
+	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
+						 secondary_text);
+	if (buttons == BUTTONS_RETRY_SKIP_CANCEL) {
+		gtk_dialog_add_button(GTK_DIALOG(dialog), _("Skip"),
+				      DIALOG_RESPONSE_SKIP);
+	}
+	if (buttons != BUTTONS_OK) {
+		gtk_dialog_add_button(GTK_DIALOG(dialog), _("Retry"),
+				      DIALOG_RESPONSE_RETRY);
+	}
+	ret = gtk_dialog_run(GTK_DIALOG(dialog));
 
 	gtk_widget_destroy(dialog);
+	g_free(primary_text_markup);
+	return ret;
+}
 
-	return address;
+static gint show_connection_error_dialog(GtkWindow *parent, const gchar *bdaddr,
+					 const gchar *primary_text)
+{
+	if (!is_palm_device(bdaddrstr)) {
+		return show_error_dialog(parent,
+					 BUTTONS_RETRY_CANCEL,
+					 primary_text,
+					 _("Make sure that remote device is on and that it accepts Bluetooth connections."));
+	} else {
+		return show_error_dialog(parent,
+					 BUTTONS_RETRY_CANCEL,
+					 primary_text,
+					 _("Make sure \"Beam receive\" is enabled in the Power preferences, and Bluetooth is enabled, on your Palm device."));
+	}
 }
 
-static void response_callback(GtkWidget *dialog,
-					gint response, gpointer user_data)
+static gint handle_file_sending_error(const gchar *error_text,
+				      const gchar *error_secondary_text, BluetoothSendto *app)
 {
-	gtk_widget_destroy(dialog);
+	ButtonSet buttons;
+	gint response_id;
+
+	if (app->file_count > 1 && app->current_file < app->file_count - 1)
+		buttons = BUTTONS_RETRY_SKIP_CANCEL;
+	else
+		buttons = BUTTONS_RETRY_CANCEL;
+
+	response_id = show_error_dialog(GTK_WINDOW(app->main_dialog), buttons, 
+					error_text, error_secondary_text);
+
+	switch (response_id) {
+	case DIALOG_RESPONSE_SKIP:
+		app->current_file++;
+		app->bytes_sent += app->file_length;
+	case DIALOG_RESPONSE_RETRY:
+		return TRUE;
+	case GTK_RESPONSE_CANCEL:
+	default:
+		gtk_main_quit();
+		break;
+	}
+	return response_id;
+}
 
+static void cancel_clicked_cb(GtkWidget *button, BluetoothSendto *app)
+{
 	gtk_main_quit();
 }
 
-static void create_window(const gchar *filename)
+static void ui_init(BluetoothSendto *app)
 {
-	GtkWidget *vbox;
-	GtkWidget *label;
+	GtkWidget *vbox, *vbox_parent, *table;
+	GtkWidget *label1, *label2, *label3, *target_label, *from_label, *operation_label;
+	GtkWidget *progress_bar, *button;
 	gchar *text;
 
-	dialog = gtk_dialog_new_with_buttons(_("File Transfer"), NULL,
-				GTK_DIALOG_NO_SEPARATOR,
-				GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
-
-	gtk_window_set_default_size(GTK_WINDOW(dialog), 400, -1);
+	/* initialize UI */
 
-	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
+	device_name = get_device_name(app);
 
-	vbox = gtk_vbox_new(FALSE, 6);
-	gtk_container_set_border_width(GTK_CONTAINER(vbox), 12);
-	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox);
-
-	label = gtk_label_new(NULL);
-	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+	/* main dialog */
+	app->main_dialog = gtk_dialog_new();
+	gtk_dialog_set_has_separator(GTK_DIALOG(app->main_dialog), FALSE);
+	gtk_window_set_title(GTK_WINDOW(app->main_dialog),
+			     _("Bluetooth file transfer"));
+	gtk_window_set_icon_name(GTK_WINDOW(app->main_dialog), "stock_bluetooth");
+	gtk_window_set_type_hint(GTK_WINDOW(app->main_dialog),
+				 GDK_WINDOW_TYPE_HINT_NORMAL);
+	gtk_window_set_position(GTK_WINDOW(app->main_dialog), GTK_WIN_POS_CENTER);
+	gtk_window_set_resizable(GTK_WINDOW(app->main_dialog), FALSE);
+	gtk_widget_set(app->main_dialog, "width-request", 400, NULL);
+	button = gtk_dialog_add_button(GTK_DIALOG(app->main_dialog),
+				       GTK_STOCK_CANCEL,
+				       GTK_RESPONSE_CANCEL);
+	g_signal_connect(G_OBJECT(button), "clicked",
+			 G_CALLBACK(cancel_clicked_cb), app);
+	gtk_container_set_border_width(GTK_CONTAINER(app->main_dialog), 6);
+
+	/* table elements */
+	from_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(from_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(from_label), PANGO_ELLIPSIZE_MIDDLE);
+	target_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(target_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(target_label), PANGO_ELLIPSIZE_END);
+	gtk_label_set_text(GTK_LABEL(target_label), device_name ? device_name : bdaddrstr);
+	label2 = gtk_label_new(NULL);
+	text = g_markup_printf_escaped("<b>%s</b>", _("From:"));
+	gtk_label_set_markup(GTK_LABEL(label2), text);
+	g_free(text);
+	label3 = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(label3), 1, 0.5);
+	text = g_markup_printf_escaped("<b>%s</b>", _("To:"));
+	gtk_label_set_markup(GTK_LABEL(label3), text);
+	g_free(text);
 
-	label_filename = label;
+	/* table */
+	table = gtk_table_new(2, 2, FALSE);
+	gtk_table_set_col_spacings(GTK_TABLE(table), 4);
+	gtk_table_set_row_spacings(GTK_TABLE(table), 4);
+	gtk_table_attach_defaults(GTK_TABLE(table), from_label, 1, 2, 0, 1);
+	gtk_table_attach_defaults(GTK_TABLE(table), target_label, 1, 2, 1, 2);
+	gtk_table_attach(GTK_TABLE(table), label2, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), label3, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+
+	/* vbox elements */
+	label1 = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(label1), 0, 0.5);
+	text = g_markup_printf_escaped("<span weight=\"bold\" size=\"larger\">%s</span>",
+				       _("Sending files via bluetooth"));
+	gtk_label_set_markup(GTK_LABEL(label1), text);
+	g_free(text);
+	progress_bar = gtk_progress_bar_new();
+	gtk_progress_bar_set_ellipsize(GTK_PROGRESS_BAR(progress_bar),
+				       PANGO_ELLIPSIZE_END);
+	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
+				  _("Connecting..."));
+	operation_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(operation_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(operation_label), PANGO_ELLIPSIZE_END);
+
+	/* vbox */
+	vbox = gtk_vbox_new(FALSE, 0);
+	gtk_box_set_spacing(GTK_BOX(vbox), 6);
+	gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
+	gtk_box_pack_start_defaults(GTK_BOX(vbox), label1);
+	gtk_box_pack_start(GTK_BOX(vbox), table, TRUE, TRUE, 9);
+	gtk_box_pack_start_defaults(GTK_BOX(vbox), progress_bar);
+	gtk_box_pack_start(GTK_BOX(vbox), operation_label, TRUE, TRUE, 2);
+	vbox_parent = gtk_bin_get_child(GTK_BIN(app->main_dialog));
+	gtk_box_pack_start_defaults(GTK_BOX(vbox_parent), vbox);
+
+	/* Set widget variables in app */
+	app->from_label = from_label;
+	app->operation_label = operation_label;
+	app->progress_bar = progress_bar;
+
+	/* show it */
+	gtk_widget_show_all(app->main_dialog);
+}
+
+static gboolean send_one(BluetoothSendto *app)
+{
+	const gchar *fname;
+	gint ret;
+	gchar *bname, *dirname;
+	GError *err = NULL;
+	gchar *operation_text, *operation_markup, *progressbar_text;
+
+	if ((fname = g_slist_nth_data(app->file_list, app->current_file))) {
+		/* there's a file to send */
+		dbus_g_proxy_call(app->session_proxy, "SendFile", &err,
+				  G_TYPE_STRING, fname, G_TYPE_INVALID, G_TYPE_INVALID);
+
+		if (err != NULL) {
+			ret = handle_file_sending_error(_("Unable to read file"),
+							err->message, app);
+			if (ret != GTK_RESPONSE_CANCEL) {
+				g_idle_add((GSourceFunc) send_one, app);
+			}
+			g_error_free(err);
+			return FALSE;
+		}
 
-	progress = gtk_progress_bar_new();
-	gtk_box_pack_start(GTK_BOX(vbox), progress, FALSE, FALSE, 0);
+		if (!app->first_transfer_time) {
+			app->first_transfer_time = get_system_time();
+		}
+		bname = g_path_get_basename(fname);
+		dirname = g_path_get_dirname(fname);
 
-	label = gtk_label_new(NULL);
-	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 6);
+		operation_text = g_strdup_printf(_("Sending %s"), bname);
+		operation_markup = g_markup_printf_escaped("<i>%s</i>", operation_text);
+		progressbar_text = g_strdup_printf(_("Sending file: %d of %d"),
+						   app->current_file+1,
+						   app->file_count);
+		gtk_label_set_text(GTK_LABEL(app->from_label), dirname);
+		gtk_label_set_markup(GTK_LABEL(app->operation_label), operation_markup);
+		gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app->progress_bar),
+					  progressbar_text);
+
+		g_free(bname);
+		g_free(dirname);
+		g_free(operation_text);
+		g_free(operation_markup);
+		g_free(progressbar_text);
+	} else {
+		/* nothing left to send, time to quit */
+		gtk_main_quit();
+	}
+	return FALSE;
+}
 
-	label_status = label;
+static void session_connected_cb(DBusGProxy *proxy, BluetoothSendto *app)
+{
+	/* OBEX connect has succeeded, so start sending */
+	g_idle_add((GSourceFunc) send_one, app);
+}
 
-	text = g_strdup_printf("<b>%s</b>", filename);
-	gtk_label_set_markup(GTK_LABEL(label_filename), text);
-	g_free(text);
+static void transfer_started_cb(DBusGProxy *proxy, const gchar *filename,
+				const gchar *local_path, gint byte_count, BluetoothSendto *app)
+{
+	app->file_length = byte_count;
+}
 
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Connecting..."));
+static void transfer_cancelled_cb(DBusGProxy *proxy, BluetoothSendto *app)
+{
+	gint ret;
+	gchar *error_text;
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, FALSE);
+	error_text = g_strdup_printf(_("An error occured while sending file '%s'"),
+				     (gchar *)g_slist_nth_data(app->file_list, app->current_file));
+	ret = handle_file_sending_error(error_text,
+					_("The remote device cancelled the transfer"), app);
 
-	g_signal_connect(G_OBJECT(dialog), "response",
-				G_CALLBACK(response_callback), NULL);
+	if (ret != GTK_RESPONSE_CANCEL)
+		g_idle_add((GSourceFunc) send_one, app);
 
-	gtk_widget_show_all(dialog);
+	g_free(error_text);
 }
 
-static void transfer_started(DBusGProxy *proxy, gchar *a, gchar *b,
-						gint size, gpointer user_data)
+static void transfer_progress_cb(DBusGProxy *proxy, gint bytes_transferred, BluetoothSendto *app)
 {
-	gchar *text;
+	gdouble frac;
+	guint actual_bytes_sent;
+	gint elapsed_time;
+	gint transfer_rate;
+	gint time_remaining;
+	gint64 current_time;
+	gchar *str, *str2;
+	gchar *progressbar_text;
 
-	filesize = size;
+	/* update progress bar fraction */
+	actual_bytes_sent = app->bytes_sent + bytes_transferred;
+	frac = (gdouble)actual_bytes_sent / app->byte_count;
+	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(app->progress_bar), frac);
 
-	text = g_strdup_printf(_("Starting transfer of %d bytes"), size);
-	gtk_label_set_markup(GTK_LABEL(label_status), text);
-	g_free(text);
+	/* update progress bar text (time remaining)
+	 * (only update once in a second) */
+	current_time = get_system_time();
+	elapsed_time = (current_time - app->first_transfer_time) / 1000000;
 
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0.0);
-}
+	if (app->last_update_time) {
+		if (current_time < app->last_update_time + 1000000)
+			return;
+		else
+			app->last_update_time = current_time;
+	} else {
+		app->last_update_time = current_time;
+	}
 
-static void transfer_progress(DBusGProxy *proxy, guint bytes, gpointer user_data)
-{
-	gchar *text;
-	gdouble fraction;
+	if (!elapsed_time)
+		return;
 
-	text = g_strdup_printf(_("Transfered %d of %d bytes"), bytes, filesize);
-	gtk_label_set_markup(GTK_LABEL(label_status), text);
-	g_free(text);
+	transfer_rate = actual_bytes_sent / elapsed_time;
+	if (transfer_rate == 0)
+		return;
 
-	fraction = (gdouble) bytes / (gdouble) filesize;
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), fraction);
-}
+	time_remaining = (app->byte_count - actual_bytes_sent) / transfer_rate;
 
-static void transfer_completed(DBusGProxy *proxy, gpointer user_data)
-{
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Completed"));
+	if (time_remaining >= 3600) {
+		str = g_strdup_printf(_("(%d:%02d:%d Remaining)"), 
+				      time_remaining / 3600,
+				      (time_remaining % 3600) / 60,
+				      (time_remaining % 3600) % 60);
+	} else {
+		str = g_strdup_printf(_("(%d:%02d Remaining)"), 
+				      time_remaining / 60,
+				      time_remaining % 60);
+	}
 
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 1.0);
+	/* Translators:
+	 * Sending file 1 of 3 */
+	str2 = g_strdup_printf(_("Sending file %d of %d"),
+			       app->current_file + 1,
+			       app->file_count);
+	progressbar_text = g_strconcat(str2, " ", str, NULL);
+	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(app->progress_bar),
+				  progressbar_text);
+	g_free(str);
+	g_free(str2);
+	g_free(progressbar_text);
+}
 
-	dbus_g_proxy_call(proxy, "Disconnect", NULL, G_TYPE_INVALID,
-							G_TYPE_INVALID);
+static void transfer_completed_cb(DBusGProxy *proxy, BluetoothSendto *app)
+{
+	app->current_file++;
+	app->bytes_sent += app->file_length;
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, TRUE);
+	g_idle_add((GSourceFunc) send_one, app);
 }
 
-static void session_connected(DBusGProxy *proxy, gpointer user_data)
+static void session_created_cb(DBusGProxy *proxy, DBusGProxyCall *call,
+			       BluetoothSendto *app)
 {
-	gchar *filename = user_data;
 	GError *error = NULL;
+	const gchar *path = NULL;
 
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Connected"));
+	if (!dbus_g_proxy_end_call(proxy, call, &error,
+				  G_TYPE_STRING, &path, G_TYPE_INVALID)) {
+		const gchar *error_name = NULL;
+		gint response_id;
+
+		if (error && error->code == DBUS_GERROR_REMOTE_EXCEPTION)
+			error_name = dbus_g_error_get_name(error);
+		if (error_name && !strcmp(error_name, "org.openobex.Error.ConnectionAttemptFailed")) {
+			response_id = show_connection_error_dialog(GTK_WINDOW(app->main_dialog), bdaddrstr, error->message);
+			if (response_id == DIALOG_RESPONSE_RETRY) {
+				/* Try to create Session again */
+				dbus_g_proxy_begin_call(app->manager_proxy,
+							"CreateBluetoothSession",
+							(DBusGProxyCallNotify) session_created_cb, app, NULL,
+							G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+							G_TYPE_BOOLEAN, FALSE, G_TYPE_INVALID);
+				goto out;
+			}
+		} else {
+			show_error_dialog(GTK_WINDOW(app->main_dialog), BUTTONS_OK,
+					  error->message, "");
+		}
 
-	dbus_g_proxy_call(proxy, "SendFile", &error,
-				G_TYPE_STRING, filename, G_TYPE_INVALID,
-							G_TYPE_INVALID);
-
-	if (error != NULL) {
-		g_printerr("Sending of file %s failed: %s\n", filename,
-							error->message);
-		g_error_free(error);
+		/* Failed, quit main loop */
 		gtk_main_quit();
+		goto out;
 	}
-}
 
-static void create_notify(DBusGProxy *proxy,
-				DBusGProxyCall *call, void *user_data)
-{
-	GError *error = NULL;
-	const gchar *path = NULL;
+	app->session_proxy = dbus_g_proxy_new_for_name(app->connection,
+						       "org.openobex",
+						       path,
+						       "org.openobex.Session");
 
-	if (dbus_g_proxy_end_call(proxy, call, &error,
-			G_TYPE_STRING, &path, G_TYPE_INVALID) == FALSE) {
-		gchar *text, *message;
-
-		if (error != NULL) {
-			message = g_strdup(error->message);
-			g_error_free(error);
-		} else
-			message = g_strdup(_("An unknown error occured"));
-
-		text = g_strdup_printf("<span foreground=\"red\">%s</span>",
-								message);
-		gtk_label_set_markup(GTK_LABEL(label_status), text);
-		g_free(text);
+	dbus_g_proxy_add_signal(app->session_proxy, "Connected",
+				G_TYPE_INVALID);
 
-		g_free(message);
+	dbus_g_proxy_connect_signal(app->session_proxy, "Connected",
+				    G_CALLBACK(session_connected_cb), app, NULL);
 
-		gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, TRUE);
-		return;
-	}
+	dbus_g_proxy_add_signal(app->session_proxy, "TransferStarted",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+
+	dbus_g_proxy_connect_signal(app->session_proxy, "TransferStarted",
+				    G_CALLBACK(transfer_started_cb), app, NULL);
 
-	proxy = dbus_g_proxy_new_for_name(conn, "org.openobex",
-						path, "org.openobex.Session");
+	dbus_g_proxy_add_signal(app->session_proxy, "Cancelled",
+				G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "Connected", G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(app->session_proxy, "Cancelled",
+				    G_CALLBACK(transfer_cancelled_cb), app, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "Connected",
-				G_CALLBACK(session_connected), user_data, NULL);
+	dbus_g_proxy_add_signal(app->session_proxy, "ErrorOccurred",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "TransferStarted", G_TYPE_STRING,
-				G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(app->session_proxy, "ErrorOccurred",
+				    G_CALLBACK(error_occurred_cb), app, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferStarted",
-				G_CALLBACK(transfer_started), NULL, NULL);
+	dbus_g_proxy_add_signal(app->session_proxy, "TransferProgress",
+				G_TYPE_UINT, G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "TransferProgress",
-						G_TYPE_UINT, G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(app->session_proxy, "TransferProgress",
+				    G_CALLBACK(transfer_progress_cb), app, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferProgress",
-				G_CALLBACK(transfer_progress), NULL, NULL);
+	dbus_g_proxy_add_signal(app->session_proxy, "TransferCompleted", 
+				G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "TransferCompleted", G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(app->session_proxy, "TransferCompleted",
+				    G_CALLBACK(transfer_completed_cb), app, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferCompleted",
-				G_CALLBACK(transfer_completed), NULL, NULL);
+	dbus_g_proxy_call(app->session_proxy, "Connect", &error, G_TYPE_INVALID,
+			  G_TYPE_INVALID);
 
-	dbus_g_proxy_call(proxy, "Connect", NULL, G_TYPE_INVALID,
-							G_TYPE_INVALID);
+out:
+	if (error)
+		g_error_free(error);
 }
 
-static gchar *option_device = NULL;
+static void error_occurred_cb(DBusGProxy *proxy, const gchar *error_name,	
+			      const gchar *error_message, BluetoothSendto *app)
+{
+	gint ret;
+	gboolean link_error = FALSE;
+	gchar *error_text;
+	const gchar *reason;
+
+	g_message("ErrorOccurred");
+	g_message("Error name: %s", error_name);
+	g_message("Error message: %s", error_message);
+
+	error_text = g_strdup_printf(_("An error occured while sending file '%s'"),
+				     (gchar *)g_slist_nth_data(app->file_list, app->current_file));
+	if (!strcmp(error_name, "org.openobex.Error.LinkError")) {
+		/* Connection to remote device was lost */
+		g_object_unref(G_OBJECT(app->session_proxy));
+		reason = _("Connection to remote device was lost");
+		link_error = TRUE;
+	} else if (!strcmp(error_name, "org.openobex.Error.Forbidden")) {
+		reason = _("Remote device rejected file");
+	} else {
+		reason = _("Unknown error");
+	}
+	ret = handle_file_sending_error(error_text, reason, app);
 
-static GOptionEntry options[] = {
-	{ "device", 0, 0, G_OPTION_ARG_STRING, &option_device,
-				N_("Remote device to use"), "ADDRESS" },
-	{ NULL },
-};
+	if (ret != GTK_RESPONSE_CANCEL) {
+		if (link_error) {
+			/* Need to reestablish connection */
+			dbus_g_proxy_begin_call(app->manager_proxy, "CreateBluetoothSession",
+						(DBusGProxyCallNotify) session_created_cb, app, NULL,
+						G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+						G_TYPE_INVALID);
+		} else {
+			g_idle_add((GSourceFunc) send_one, app);
+		}
+	}
+
+
+	g_free(error_text);
+}
+
+static void free_mem(BluetoothSendto *app)
+{
+	if (files_to_send)
+		g_strfreev(files_to_send);
+	g_free(bdaddrstr);
+
+	if (app) {
+		if (G_IS_OBJECT(app->manager_proxy))
+			g_object_unref(G_OBJECT(app->manager_proxy));
+		if (G_IS_OBJECT(app->session_proxy))
+			g_object_unref (G_OBJECT(app->session_proxy));
+		g_slist_free(app->file_list);
+		g_free(app);
+	}
+}
 
 int main(int argc, char *argv[])
 {
-	DBusGProxy *proxy;
-	GError *error;
-	gchar *filename, *address;
+	GOptionContext *option_context;
+	GError *err = NULL;
+	guint i;
+	BluetoothSendto *app;
+
+	app = g_new0(BluetoothSendto, 1);
 
+	/* initialize gettext */
 	bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
 	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
 	textdomain(GETTEXT_PACKAGE);
 
-	error = NULL;
-
-	if (gtk_init_with_args(&argc, &argv, "[FILE...]",
-				options, GETTEXT_PACKAGE, &error) == FALSE) {
-		if (error != NULL) {
-			g_printerr("%s\n", error->message);
-			g_error_free(error);
-		} else
-			g_printerr("An unknown error occurred\n");
-
-		gtk_exit(1);
-	}
-
-	gtk_window_set_default_icon_name("stock_bluetooth");
-
-	if (argc < 2) {
-		filename = open_file_dialog();
-		if (filename == NULL)
-			gtk_exit(1);
-	} else
-		filename = g_strdup(argv[1]);
-
-	if (option_device == NULL) {
-		address = browse_device_dialog();
-		if (address == NULL) {
-			g_free(filename);
-			gtk_exit(1);
-		}
-	} else
-		address = g_strdup(option_device);
-
-	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING_INT,
-				G_TYPE_NONE, G_TYPE_STRING,
-				G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+	/* parse command line arguments */
+	option_context = g_option_context_new("<file list>");
+	g_option_context_add_main_entries(option_context, options, GETTEXT_PACKAGE);
+	g_option_context_add_group(option_context, gtk_get_option_group(TRUE));
+	g_option_context_parse(option_context, &argc, &argv, &err);
+	g_option_context_free(option_context);
+	if (err) {
+		printf("Usage:\n");
+		printf("  %s [--dest=BDADDR] <file list>\n", g_get_prgname());
+		g_error_free(err);
+		free_mem(app);
+		return 0;
+	}
 
-	error = NULL;
+	gtk_init(&argc, &argv);
 
-	conn = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
-	if (conn == NULL) {
-		if (error != NULL) {
-			g_printerr("Connecting to session bus failed: %s\n",
-							error->message);
-			g_error_free(error);
-		} else
-			g_print("An unknown error occured\n");
+	/* get a list of files to send from command-line */
+	if (files_to_send)
+		app->file_count = g_strv_length(files_to_send);
+	else
+		app->file_count = 0;
+	for (i = 0; i < app->file_count; i++) {
+		app->file_list = g_slist_append(app->file_list,
+						g_strdup(*(files_to_send + i)));
+	}
+	/* show file chooser if no files were specified */
+	if (!app->file_count) {
+		app->file_list = select_files_dialog();
+		app->file_count = g_slist_length(app->file_list);
+	}
+	/* Check if there are some files to send */
+	if (!app->file_count) {
+		free_mem(app);
+		return 0;
+	}
+	/* Check for non regular and non existand files and
+	 * determine total bytes to send */
+	for (i = 0; i < app->file_count; i++) {
+		GSList *item;
+		gchar *path, *filename;
+		gint64 size;
+
+		item = g_slist_nth(app->file_list, i);
+		path = (gchar *) item->data;
+		filename = normalise_filename(path, &size);
+		if (filename == NULL) {
+			g_free(path);
+			app->file_list = g_slist_remove_link(app->file_list, item);
+			continue;
+		}
+		g_free(item->data);
+		item->data = filename;
+		app->byte_count += size;
+	}
 
-		gtk_exit(1);
+	/* determine Bluetooth device to send to */
+	if (!bdaddrstr) {
+		bdaddrstr = browse_device_dialog();
+		if (!bdaddrstr) {
+			free_mem(app);
+			return 0;
+		}
 	}
 
-	create_window(filename);
+	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING_INT,
+					  G_TYPE_NONE, G_TYPE_STRING,
+					  G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING,
+					  G_TYPE_NONE, G_TYPE_STRING,
+					  G_TYPE_STRING, G_TYPE_INVALID);
+
+	/* init DBus connection */
+	app->connection = dbus_g_bus_get(DBUS_BUS_SESSION, &err);
+	if (!app->connection) {
+		g_printerr("Connecting to session bus failed: %s\n",
+			   err->message);
+		g_error_free(err);
+		free_mem(app);
+		return 1;
+	}
 
-	proxy = dbus_g_proxy_new_for_name(conn, "org.openobex",
-				"/org/openobex", "org.openobex.Manager");
+	/* init UI */
+	ui_init(app);
 
-	dbus_g_proxy_begin_call(proxy, "CreateBluetoothSession",
-				create_notify, filename, NULL,
-				G_TYPE_STRING, address, G_TYPE_STRING, "opp",
+	/* init DBus proxy, create Session */
+	app->manager_proxy = dbus_g_proxy_new_for_name(app->connection,
+						       "org.openobex",
+						       "/org/openobex",
+						       "org.openobex.Manager");
+
+	dbus_g_proxy_begin_call(app->manager_proxy, "CreateBluetoothSession",
+				(DBusGProxyCallNotify) session_created_cb, app, NULL,
+				G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
 				G_TYPE_INVALID);
 
+	/* Go into main loop */
 	gtk_main();
 
-	dbus_g_connection_unref(conn);
+	gtk_widget_destroy(app->main_dialog);
 
-	g_free(address);
-	g_free(filename);
+	/* done sending, free memory */
+	free_mem(app);
 
 	return 0;
 }
+
--- /dev/null	2008-01-24 17:45:36.352009045 +0000
+++ utils.h	2008-02-01 16:00:15.000000000 +0000
@@ -0,0 +1,30 @@
+/*
+ * bluetooth-sendto
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-2007  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ *  Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <glib.h>
+
+gchar *browse_device_dialog(void);
+GSList *select_files_dialog(void);
+
--- /dev/null	2008-01-24 17:45:36.352009045 +0000
+++ utils.c	2008-02-01 16:13:24.000000000 +0000
@@ -0,0 +1,112 @@
+/*
+ * bluetooth-sendto
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-2007  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ *  Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+
+#include "bluetooth-device-selection.h"
+
+static void selected_device_changed_cb(BluetoothDeviceSelection *selector,
+				       gchar *address, gpointer user_data)
+{
+	GtkWidget *dialog = user_data;
+
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
+					  GTK_RESPONSE_ACCEPT, address != NULL);
+}
+
+gchar *browse_device_dialog(void)
+{
+	GtkWidget *dialog;
+	GtkWidget *selector;
+	gchar *address = NULL;
+
+	dialog = gtk_dialog_new_with_buttons(_("Select Device"),
+					     NULL, GTK_DIALOG_NO_SEPARATOR,
+					     GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+					     GTK_STOCK_CONNECT, GTK_RESPONSE_ACCEPT, NULL);
+
+	gtk_window_set_icon_name(GTK_WINDOW(dialog), "stock_bluetooth");
+
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
+					  GTK_RESPONSE_ACCEPT, FALSE);
+
+	gtk_window_set_default_size(GTK_WINDOW(dialog), 450, 300);
+
+	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
+
+	selector = bluetooth_device_selection_new(_("Select destination device"));
+	bluetooth_device_selection_start_discovery(
+	    BLUETOOTH_DEVICE_SELECTION(selector));
+
+	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), selector);
+
+	g_signal_connect(selector, "selected-device-changed",
+			 G_CALLBACK(selected_device_changed_cb), dialog);
+
+	gtk_widget_show_all(selector);
+
+	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
+		g_object_get(selector, "device-selected", &address, NULL);
+
+	gtk_widget_destroy(dialog);
+
+	return address;
+}
+
+GSList *select_files_dialog(void)
+{
+	GtkWidget *dialog;
+	GSList *file_list;
+
+	file_list = NULL;
+	dialog = gtk_file_chooser_dialog_new(_("Choose files to send"), NULL,
+					     GTK_FILE_CHOOSER_ACTION_OPEN,
+					     GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+					     _("Send"), GTK_RESPONSE_ACCEPT,
+					     NULL);
+	gtk_window_set_icon_name(GTK_WINDOW(dialog), "stock_bluetooth");
+	gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
+
+	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+		file_list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
+	}
+	gtk_widget_destroy(dialog);
+
+	return file_list;
+}
+

[-- Attachment #3: Type: text/plain, Size: 228 bytes --]

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/

[-- Attachment #4: Type: text/plain, Size: 164 bytes --]

_______________________________________________
Bluez-devel mailing list
Bluez-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bluez-devel

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [Bluez-devel] [PATCH] Updated sendto
  2008-02-01 16:20       ` Bastien Nocera
@ 2008-02-06 12:18         ` Bastien Nocera
  2008-02-06 13:08           ` Bastien Nocera
  0 siblings, 1 reply; 16+ messages in thread
From: Bastien Nocera @ 2008-02-06 12:18 UTC (permalink / raw)
  To: BlueZ development

[-- Attachment #1: Type: text/plain, Size: 1158 bytes --]


On Fri, 2008-02-01 at 16:20 +0000, Bastien Nocera wrote:
> On Fri, 2008-02-01 at 16:08 +0000, Bastien Nocera wrote:
> > On Fri, 2008-02-01 at 13:14 +0000, Bastien Nocera wrote:
> > > On Fri, 2008-01-18 at 13:43 +0000, Bastien Nocera wrote:
> > > > Hello again,
> > > > 
> > > > On Thu, 2008-01-17 at 10:43 +0000, Bastien Nocera wrote: 
> > > > > Heya,
> > > > > 
> > > > > Another for bluetooth-sento feature parity with gnome-obex-send.
> > > > 
> > > > Another patch, based on Tadas' gnome-obex-send port.
> > > > 
> > > > - Fix to run with the latest obex-data-server (in 2 places)
> > > > - Add an appropriate error when the target device is a Palm
> > > > - Accept relative local paths and URIs as well as absolute filenames as
> > > > arguments (needed for ease of use and nautilus-sendto, respectively)
> > > > - Gather the name of the Bluetooth device from hcid to show in the UI
> > > 
> > > Updated patch to use the marshal file in common/ instead of our own.
> > 
> > Updated patch:
> > - follow coding style
> > - move the helper dialogues in another file
> 
> And the function declarations on one line.

And remove the BluetoothSend *app bits.

[-- Attachment #2: bluez-gnome-new-sendto-5.patch --]
[-- Type: text/x-patch, Size: 38095 bytes --]

Index: sendto/Makefile.am
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/Makefile.am,v
retrieving revision 1.10
diff -u -p -r1.10 Makefile.am
--- sendto/Makefile.am	6 Feb 2008 01:53:17 -0000	1.10
+++ sendto/Makefile.am	6 Feb 2008 12:14:49 -0000
@@ -1,7 +1,7 @@
 
 bin_PROGRAMS = bluetooth-sendto
 
-bluetooth_sendto_SOURCES = main.c
+bluetooth_sendto_SOURCES = main.c utils.c utils.h
 
 bluetooth_sendto_LDADD = $(top_builddir)/common/libcommon.a \
 					@GTK_LIBS@ @DBUS_LIBS@
@@ -15,3 +15,4 @@ man_MANS = bluetooth-sendto.1
 EXTRA_DIST = $(man_MANS)
 
 MAINTAINERCLEANFILES = Makefile.in
+
Index: sendto/main.c
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/main.c,v
retrieving revision 1.16
diff -u -p -r1.16 main.c
--- sendto/main.c	2 Feb 2008 16:40:38 -0000	1.16
+++ sendto/main.c	6 Feb 2008 12:14:50 -0000
@@ -3,6 +3,8 @@
  *  BlueZ - Bluetooth protocol stack for Linux
  *
  *  Copyright (C) 2005-2008  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ *  Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
  *
  *
  *  This program is free software; you can redistribute it and/or modify
@@ -25,384 +27,742 @@
 #include <config.h>
 #endif
 
-#include <dbus/dbus-glib.h>
 
 #include <glib/gi18n.h>
-
 #include <gtk/gtk.h>
 
-#include "bluetooth-device-selection.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <dbus/dbus-glib.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
 
+#include "dbus-glue.h"
 #include "marshal.h"
+#include "utils.h"
 
-static DBusGConnection *conn = NULL;
+/* DBus */
+static DBusGConnection *connection = NULL;
+static DBusGProxy *manager_proxy = NULL;
+static DBusGProxy *session_proxy = NULL;
+/* GTK */
+static GtkWidget *main_dialog, *from_label, *operation_label, *progress_bar;
+/* sending related */
+static gsize file_length = 0;
+static guint file_count = 0;
+static guint current_file = 0;
+static GSList *file_list = NULL;
+static guint byte_count = 0;
+static guint bytes_sent = 0;
+static gint64 first_transfer_time = 0;
+static gint64 last_update_time = 0;
+
+typedef enum {
+	BUTTONS_OK,
+	BUTTONS_RETRY_CANCEL,
+	BUTTONS_RETRY_SKIP_CANCEL
+} ButtonSet;
+
+/* Command line options */
+static gchar **files_to_send = NULL;
+static gchar *bdaddrstr = NULL;
+static gchar *device_name = NULL;
+
+#define DIALOG_RESPONSE_SKIP 1
+#define DIALOG_RESPONSE_RETRY 2
+
+static void error_occurred_cb(DBusGProxy *proxy, const gchar *error_name,
+			      const gchar *error_message, gpointer user_data);
+
+static const GOptionEntry options[] = {
+	{"dest", 'd', 0, G_OPTION_ARG_STRING, &bdaddrstr, 
+		"Bluetooth address of destination device", "BDADDR"},
+	{G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &files_to_send},
+	{NULL}
+};
 
-static GtkWidget *dialog;
-static GtkWidget *label_filename;
-static GtkWidget *label_status;
-static GtkWidget *progress;
+static gint64 get_system_time(void)
+{
+	struct timeval tmp;
 
-static gint filesize = -1;
+	gettimeofday(&tmp, NULL);
+	return (gint64)tmp.tv_usec + (gint64)tmp.tv_sec * G_GINT64_CONSTANT(1000000);
+}
 
-static gchar *open_file_dialog(void)
+static gboolean is_palm_device(const gchar *bdaddr)
 {
-	GtkWidget *dialog;
-	gchar *filename = NULL;
+	return (g_str_has_prefix(bdaddr, "00:04:6B")
+		|| g_str_has_prefix(bdaddr, "00:07:E0")
+		|| g_str_has_prefix(bdaddr, "00:0E:20"));
+}
 
-	dialog = gtk_file_chooser_dialog_new(_("Select File"), NULL,
-				GTK_FILE_CHOOSER_ACTION_OPEN,
-				GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
-				GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
+/* Return the local absolute filename for the file, and its size
+ * if the file exists and is stat()'able */
+static gchar *normalise_filename(const gchar *filename, gint64 *size)
+{
+	char *ret;
+	struct stat file_stat;
 
-	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
-		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+	g_return_val_if_fail(filename != NULL, NULL);
+	g_return_val_if_fail(size != NULL, NULL);
 
-	gtk_widget_destroy(dialog);
+	if (g_str_has_prefix(filename, "file://")) {
+		ret = g_filename_from_uri(filename, NULL, NULL);
+		if (ret == NULL)
+			return NULL;
+	} else if (filename[0] == '/') {
+		ret = g_strdup(filename);
+	} else {
+		gchar *curdir;
 
-	return filename;
-}
+		curdir = g_get_current_dir();
+		ret = g_build_filename(curdir, filename, NULL);
+		g_free(curdir);
+	}
 
-static void selected_device_changed(BluetoothDeviceSelection *selector,
-					gchar *address, gpointer user_data)
-{
-	GtkWidget *dialog = user_data;
+	if (!g_file_test(ret, G_FILE_TEST_IS_REGULAR)) {
+		g_free(ret);
+		return NULL;
+	}
+
+	if (!g_stat(ret, &file_stat)) {
+		*size = file_stat.st_size;
+	} else {
+		g_free(ret);
+		return NULL;
+	}
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-				GTK_RESPONSE_ACCEPT, address != NULL);
+	return ret;
 }
 
-static gchar *browse_device_dialog(void)
+static gchar *get_device_name(void)
 {
-	GtkWidget *dialog;
-	GtkWidget *selector;
-	gchar *address = NULL;
+	DBusGConnection *connection;
+	DBusGProxy *manager;
+	GError *error = NULL;
+	gchar *name, **adapters;
+	guint i;
 
-	dialog = gtk_dialog_new_with_buttons(_("Select Device"),
-				NULL, GTK_DIALOG_NO_SEPARATOR,
-				GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
-				GTK_STOCK_CONNECT, GTK_RESPONSE_ACCEPT, NULL);
+	name = NULL;
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_ACCEPT, FALSE);
+	connection = dbus_g_bus_get(DBUS_BUS_SYSTEM, NULL);
+	if (connection == NULL)
+		return NULL;
+
+	manager = dbus_g_proxy_new_for_name(connection, "org.bluez",
+					    "/org/bluez", "org.bluez.Manager");
+	if (!manager) {
+		dbus_g_connection_unref(connection);
+		return NULL;
+	}
 
-	gtk_window_set_default_size(GTK_WINDOW(dialog), 450, 400);
+	if (!manager_list_adapters(manager, &adapters, &error)) {
+		g_object_unref(manager);
+		dbus_g_connection_unref(connection);
+		return NULL;
+	}
 
-	gtk_container_set_border_width(GTK_CONTAINER(dialog), 5);
-	gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), 2);
+	for (i = 0; adapters[i] != NULL; i++) {
+		DBusGProxy *adapter;
 
-	selector = bluetooth_device_selection_new(_("Select destination device"));
-	gtk_container_set_border_width(GTK_CONTAINER(selector), 5);
+		adapter = dbus_g_proxy_new_for_name(connection, "org.bluez",
+						    adapters[i], "org.bluez.Adapter");
+		if (dbus_g_proxy_call(adapter, "GetRemoteName", NULL,
+				      G_TYPE_STRING, bdaddrstr, G_TYPE_INVALID,
+				      G_TYPE_STRING, &name, G_TYPE_INVALID)) {
+			if (name != NULL && name[0] != '\0') {
+				g_object_unref(adapter);
+				break;
+			}
+		}
+		g_object_unref(adapter);
+	}
 
-	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), selector);
+	g_object_unref(manager);
+	dbus_g_connection_unref(connection);
 
-	g_signal_connect(selector, "selected-device-changed",
-				G_CALLBACK(selected_device_changed), dialog);
+	return name;
+}
 
-	gtk_widget_show_all(selector);
+static gint show_error_dialog(GtkWindow *parent, ButtonSet buttons,
+			      const gchar *primary_text, const gchar *secondary_text)
+{
+	GtkWidget *dialog;
+	gchar *primary_text_markup;
+	gint ret;
 
-	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
-		g_object_get(selector, "device-selected", &address, NULL);
+	primary_text_markup = g_strdup_printf("<span weight=\"bold\" size=\"larger\">%s</span>", 
+					      primary_text);
+	dialog = gtk_message_dialog_new_with_markup(parent, 0, GTK_MESSAGE_ERROR, 
+						    buttons==BUTTONS_OK? GTK_BUTTONS_OK: GTK_BUTTONS_CANCEL, 
+						    primary_text_markup);
+	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
+						 secondary_text);
+	if (buttons == BUTTONS_RETRY_SKIP_CANCEL) {
+		gtk_dialog_add_button(GTK_DIALOG(dialog), _("Skip"),
+				      DIALOG_RESPONSE_SKIP);
+	}
+	if (buttons != BUTTONS_OK) {
+		gtk_dialog_add_button(GTK_DIALOG(dialog), _("Retry"),
+				      DIALOG_RESPONSE_RETRY);
+	}
+	ret = gtk_dialog_run(GTK_DIALOG(dialog));
 
 	gtk_widget_destroy(dialog);
+	g_free(primary_text_markup);
+	return ret;
+}
 
-	return address;
+static gint show_connection_error_dialog(GtkWindow *parent, const gchar *bdaddr,
+					 const gchar *primary_text)
+{
+	if (!is_palm_device(bdaddrstr)) {
+		return show_error_dialog(parent,
+					 BUTTONS_RETRY_CANCEL,
+					 primary_text,
+					 _("Make sure that remote device is on and that it accepts Bluetooth connections."));
+	} else {
+		return show_error_dialog(parent,
+					 BUTTONS_RETRY_CANCEL,
+					 primary_text,
+					 _("Make sure \"Beam receive\" is enabled in the Power preferences, and Bluetooth is enabled, on your Palm device."));
+	}
 }
 
-static void response_callback(GtkWidget *dialog,
-					gint response, gpointer user_data)
+static gint handle_file_sending_error(const gchar *error_text,
+				      const gchar *error_secondary_text)
 {
-	gtk_widget_destroy(dialog);
+	ButtonSet buttons;
+	gint response_id;
+
+	if (file_count > 1 && current_file < file_count - 1)
+		buttons = BUTTONS_RETRY_SKIP_CANCEL;
+	else
+		buttons = BUTTONS_RETRY_CANCEL;
+
+	response_id = show_error_dialog(GTK_WINDOW(main_dialog), buttons, 
+					error_text, error_secondary_text);
+
+	switch (response_id) {
+	case DIALOG_RESPONSE_SKIP:
+		current_file++;
+		bytes_sent += file_length;
+	case DIALOG_RESPONSE_RETRY:
+		return TRUE;
+	case GTK_RESPONSE_CANCEL:
+	default:
+		gtk_main_quit();
+		break;
+	}
+	return response_id;
+}
 
+static void cancel_clicked_cb(GtkWidget *button, gpointer user_data)
+{
 	gtk_main_quit();
 }
 
-static void create_window(const gchar *filename)
+static void ui_init(void)
 {
-	GtkWidget *vbox;
-	GtkWidget *label;
+	GtkWidget *vbox, *vbox_parent, *table;
+	GtkWidget *label1, *label2, *label3, *target_label;
+	GtkWidget *button;
 	gchar *text;
 
-	dialog = gtk_dialog_new_with_buttons(_("File Transfer"), NULL,
-				GTK_DIALOG_NO_SEPARATOR,
-				GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
-
-	gtk_window_set_default_size(GTK_WINDOW(dialog), 400, -1);
+	/* initialize UI */
 
-	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
+	device_name = get_device_name();
 
-	vbox = gtk_vbox_new(FALSE, 6);
-	gtk_container_set_border_width(GTK_CONTAINER(vbox), 12);
-	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox);
-
-	label = gtk_label_new(NULL);
-	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+	/* main dialog */
+	main_dialog = gtk_dialog_new();
+	gtk_dialog_set_has_separator(GTK_DIALOG(main_dialog), FALSE);
+	gtk_window_set_title(GTK_WINDOW(main_dialog),
+			     _("Bluetooth file transfer"));
+	gtk_window_set_icon_name(GTK_WINDOW(main_dialog), "stock_bluetooth");
+	gtk_window_set_type_hint(GTK_WINDOW(main_dialog),
+				 GDK_WINDOW_TYPE_HINT_NORMAL);
+	gtk_window_set_position(GTK_WINDOW(main_dialog), GTK_WIN_POS_CENTER);
+	gtk_window_set_resizable(GTK_WINDOW(main_dialog), FALSE);
+	gtk_widget_set(main_dialog, "width-request", 400, NULL);
+	button = gtk_dialog_add_button(GTK_DIALOG(main_dialog),
+				       GTK_STOCK_CANCEL,
+				       GTK_RESPONSE_CANCEL);
+	g_signal_connect(G_OBJECT(button), "clicked",
+			 G_CALLBACK(cancel_clicked_cb), NULL);
+	gtk_container_set_border_width(GTK_CONTAINER(main_dialog), 6);
+
+	/* table elements */
+	from_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(from_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(from_label), PANGO_ELLIPSIZE_MIDDLE);
+	target_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(target_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(target_label), PANGO_ELLIPSIZE_END);
+	gtk_label_set_text(GTK_LABEL(target_label), device_name ? device_name : bdaddrstr);
+	label2 = gtk_label_new(NULL);
+	text = g_markup_printf_escaped("<b>%s</b>", _("From:"));
+	gtk_label_set_markup(GTK_LABEL(label2), text);
+	g_free(text);
+	label3 = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(label3), 1, 0.5);
+	text = g_markup_printf_escaped("<b>%s</b>", _("To:"));
+	gtk_label_set_markup(GTK_LABEL(label3), text);
+	g_free(text);
 
-	label_filename = label;
+	/* table */
+	table = gtk_table_new(2, 2, FALSE);
+	gtk_table_set_col_spacings(GTK_TABLE(table), 4);
+	gtk_table_set_row_spacings(GTK_TABLE(table), 4);
+	gtk_table_attach_defaults(GTK_TABLE(table), from_label, 1, 2, 0, 1);
+	gtk_table_attach_defaults(GTK_TABLE(table), target_label, 1, 2, 1, 2);
+	gtk_table_attach(GTK_TABLE(table), label2, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), label3, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+
+	/* vbox elements */
+	label1 = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(label1), 0, 0.5);
+	text = g_markup_printf_escaped("<span weight=\"bold\" size=\"larger\">%s</span>",
+				       _("Sending files via bluetooth"));
+	gtk_label_set_markup(GTK_LABEL(label1), text);
+	g_free(text);
+	progress_bar = gtk_progress_bar_new();
+	gtk_progress_bar_set_ellipsize(GTK_PROGRESS_BAR(progress_bar),
+				       PANGO_ELLIPSIZE_END);
+	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
+				  _("Connecting..."));
+	operation_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(operation_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(operation_label), PANGO_ELLIPSIZE_END);
+
+	/* vbox */
+	vbox = gtk_vbox_new(FALSE, 0);
+	gtk_box_set_spacing(GTK_BOX(vbox), 6);
+	gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
+	gtk_box_pack_start_defaults(GTK_BOX(vbox), label1);
+	gtk_box_pack_start(GTK_BOX(vbox), table, TRUE, TRUE, 9);
+	gtk_box_pack_start_defaults(GTK_BOX(vbox), progress_bar);
+	gtk_box_pack_start(GTK_BOX(vbox), operation_label, TRUE, TRUE, 2);
+	vbox_parent = gtk_bin_get_child(GTK_BIN(main_dialog));
+	gtk_box_pack_start_defaults(GTK_BOX(vbox_parent), vbox);
+
+	/* show it */
+	gtk_widget_show_all(main_dialog);
+}
+
+static gboolean send_one(gpointer user_data)
+{
+	const gchar *fname;
+	gint ret;
+	gchar *bname, *dirname;
+	GError *err = NULL;
+	gchar *operation_text, *operation_markup, *progressbar_text;
+
+	if ((fname = g_slist_nth_data(file_list, current_file))) {
+		/* there's a file to send */
+		dbus_g_proxy_call(session_proxy, "SendFile", &err,
+				  G_TYPE_STRING, fname, G_TYPE_INVALID, G_TYPE_INVALID);
+
+		if (err != NULL) {
+			ret = handle_file_sending_error(_("Unable to read file"),
+							err->message);
+			if (ret != GTK_RESPONSE_CANCEL) {
+				g_idle_add((GSourceFunc) send_one, NULL);
+			}
+			g_error_free(err);
+			return FALSE;
+		}
 
-	progress = gtk_progress_bar_new();
-	gtk_box_pack_start(GTK_BOX(vbox), progress, FALSE, FALSE, 0);
+		if (!first_transfer_time) {
+			first_transfer_time = get_system_time();
+		}
+		bname = g_path_get_basename(fname);
+		dirname = g_path_get_dirname(fname);
 
-	label = gtk_label_new(NULL);
-	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
-	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 6);
+		operation_text = g_strdup_printf(_("Sending %s"), bname);
+		operation_markup = g_markup_printf_escaped("<i>%s</i>", operation_text);
+		progressbar_text = g_strdup_printf(_("Sending file: %d of %d"),
+						   current_file+1,
+						   file_count);
+		gtk_label_set_text(GTK_LABEL(from_label), dirname);
+		gtk_label_set_markup(GTK_LABEL(operation_label), operation_markup);
+		gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
+					  progressbar_text);
+
+		g_free(bname);
+		g_free(dirname);
+		g_free(operation_text);
+		g_free(operation_markup);
+		g_free(progressbar_text);
+	} else {
+		/* nothing left to send, time to quit */
+		gtk_main_quit();
+	}
+	return FALSE;
+}
 
-	label_status = label;
+static void session_connected_cb(DBusGProxy *proxy, gpointer user_data)
+{
+	/* OBEX connect has succeeded, so start sending */
+	g_idle_add((GSourceFunc) send_one, NULL);
+}
 
-	text = g_strdup_printf("<b>%s</b>", filename);
-	gtk_label_set_markup(GTK_LABEL(label_filename), text);
-	g_free(text);
+static void transfer_started_cb(DBusGProxy *proxy, const gchar *filename,
+				const gchar *local_path, gint byte_count, gpointer user_data)
+{
+	file_length = byte_count;
+}
 
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Connecting..."));
+static void transfer_cancelled_cb(DBusGProxy *proxy, gpointer user_data)
+{
+	gint ret;
+	gchar *error_text;
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, FALSE);
+	error_text = g_strdup_printf(_("An error occured while sending file '%s'"),
+				     (gchar *)g_slist_nth_data(file_list, current_file));
+	ret = handle_file_sending_error(error_text,
+					_("The remote device cancelled the transfer"));
 
-	g_signal_connect(G_OBJECT(dialog), "response",
-				G_CALLBACK(response_callback), NULL);
+	if (ret != GTK_RESPONSE_CANCEL)
+		g_idle_add((GSourceFunc) send_one, NULL);
 
-	gtk_widget_show_all(dialog);
+	g_free(error_text);
 }
 
-static void transfer_started(DBusGProxy *proxy, gchar *a, gchar *b,
-						gint size, gpointer user_data)
+static void transfer_progress_cb(DBusGProxy *proxy, gint bytes_transferred, gpointer user_data)
 {
-	gchar *text;
+	gdouble frac;
+	guint actual_bytes_sent;
+	gint elapsed_time;
+	gint transfer_rate;
+	gint time_remaining;
+	gint64 current_time;
+	gchar *str, *str2;
+	gchar *progressbar_text;
 
-	filesize = size;
+	/* update progress bar fraction */
+	actual_bytes_sent = bytes_sent + bytes_transferred;
+	frac = (gdouble)actual_bytes_sent / byte_count;
+	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress_bar), frac);
 
-	text = g_strdup_printf(_("Starting transfer of %d bytes"), size);
-	gtk_label_set_markup(GTK_LABEL(label_status), text);
-	g_free(text);
+	/* update progress bar text (time remaining)
+	 * (only update once in a second) */
+	current_time = get_system_time();
+	elapsed_time = (current_time - first_transfer_time) / 1000000;
 
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0.0);
-}
+	if (last_update_time) {
+		if (current_time < last_update_time + 1000000)
+			return;
+		else
+			last_update_time = current_time;
+	} else {
+		last_update_time = current_time;
+	}
 
-static void transfer_progress(DBusGProxy *proxy, guint bytes, gpointer user_data)
-{
-	gchar *text;
-	gdouble fraction;
+	if (!elapsed_time)
+		return;
 
-	text = g_strdup_printf(_("Transfered %d of %d bytes"), bytes, filesize);
-	gtk_label_set_markup(GTK_LABEL(label_status), text);
-	g_free(text);
+	transfer_rate = actual_bytes_sent / elapsed_time;
+	if (transfer_rate == 0)
+		return;
 
-	fraction = (gdouble) bytes / (gdouble) filesize;
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), fraction);
-}
+	time_remaining = (byte_count - actual_bytes_sent) / transfer_rate;
 
-static void transfer_completed(DBusGProxy *proxy, gpointer user_data)
-{
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Completed"));
+	if (time_remaining >= 3600) {
+		str = g_strdup_printf(_("(%d:%02d:%d Remaining)"), 
+				      time_remaining / 3600,
+				      (time_remaining % 3600) / 60,
+				      (time_remaining % 3600) % 60);
+	} else {
+		str = g_strdup_printf(_("(%d:%02d Remaining)"), 
+				      time_remaining / 60,
+				      time_remaining % 60);
+	}
 
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 1.0);
+	/* Translators:
+	 * Sending file 1 of 3 */
+	str2 = g_strdup_printf(_("Sending file %d of %d"),
+			       current_file + 1,
+			       file_count);
+	progressbar_text = g_strconcat(str2, " ", str, NULL);
+	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
+				  progressbar_text);
+	g_free(str);
+	g_free(str2);
+	g_free(progressbar_text);
+}
 
-	dbus_g_proxy_call(proxy, "Disconnect", NULL, G_TYPE_INVALID,
-							G_TYPE_INVALID);
+static void transfer_completed_cb(DBusGProxy *proxy, gpointer user_data)
+{
+	current_file++;
+	bytes_sent += file_length;
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, TRUE);
+	g_idle_add((GSourceFunc) send_one, NULL);
 }
 
-static void session_connected(DBusGProxy *proxy, gpointer user_data)
+static void session_created_cb(DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data)
 {
-	gchar *filename = user_data;
 	GError *error = NULL;
+	const gchar *path = NULL;
 
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Connected"));
+	if (!dbus_g_proxy_end_call(proxy, call, &error,
+				   G_TYPE_STRING, &path, G_TYPE_INVALID)) {
+		const gchar *error_name = NULL;
+		gint response_id;
+
+		if (error && error->code == DBUS_GERROR_REMOTE_EXCEPTION)
+			error_name = dbus_g_error_get_name(error);
+		if (error_name && !strcmp(error_name, "org.openobex.Error.ConnectionAttemptFailed")) {
+			response_id = show_connection_error_dialog(GTK_WINDOW(main_dialog), bdaddrstr, error->message);
+			if (response_id == DIALOG_RESPONSE_RETRY) {
+				/* Try to create Session again */
+				dbus_g_proxy_begin_call(manager_proxy,
+							"CreateBluetoothSession",
+							(DBusGProxyCallNotify) session_created_cb, NULL, NULL,
+							G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+							G_TYPE_BOOLEAN, FALSE, G_TYPE_INVALID);
+				goto out;
+			}
+		} else {
+			show_error_dialog(GTK_WINDOW(main_dialog), BUTTONS_OK,
+					  error->message, "");
+		}
 
-	dbus_g_proxy_call(proxy, "SendFile", &error,
-				G_TYPE_STRING, filename, G_TYPE_INVALID,
-							G_TYPE_INVALID);
-
-	if (error != NULL) {
-		g_printerr("Sending of file %s failed: %s\n", filename,
-							error->message);
-		g_error_free(error);
+		/* Failed, quit main loop */
 		gtk_main_quit();
+		goto out;
 	}
-}
 
-static void create_notify(DBusGProxy *proxy,
-				DBusGProxyCall *call, void *user_data)
-{
-	GError *error = NULL;
-	const gchar *path = NULL;
+	session_proxy = dbus_g_proxy_new_for_name(connection,
+						       "org.openobex",
+						       path,
+						       "org.openobex.Session");
 
-	if (dbus_g_proxy_end_call(proxy, call, &error,
-			G_TYPE_STRING, &path, G_TYPE_INVALID) == FALSE) {
-		gchar *text, *message;
-
-		if (error != NULL) {
-			message = g_strdup(error->message);
-			g_error_free(error);
-		} else
-			message = g_strdup(_("An unknown error occured"));
-
-		text = g_strdup_printf("<span foreground=\"red\">%s</span>",
-								message);
-		gtk_label_set_markup(GTK_LABEL(label_status), text);
-		g_free(text);
+	dbus_g_proxy_add_signal(session_proxy, "Connected",
+				G_TYPE_INVALID);
 
-		g_free(message);
+	dbus_g_proxy_connect_signal(session_proxy, "Connected",
+				    G_CALLBACK(session_connected_cb), NULL, NULL);
 
-		gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, TRUE);
-		return;
-	}
+	dbus_g_proxy_add_signal(session_proxy, "TransferStarted",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+
+	dbus_g_proxy_connect_signal(session_proxy, "TransferStarted",
+				    G_CALLBACK(transfer_started_cb), NULL, NULL);
 
-	proxy = dbus_g_proxy_new_for_name(conn, "org.openobex",
-						path, "org.openobex.Session");
+	dbus_g_proxy_add_signal(session_proxy, "Cancelled",
+				G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "Connected", G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(session_proxy, "Cancelled",
+				    G_CALLBACK(transfer_cancelled_cb), NULL, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "Connected",
-				G_CALLBACK(session_connected), user_data, NULL);
+	dbus_g_proxy_add_signal(session_proxy, "ErrorOccurred",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "TransferStarted", G_TYPE_STRING,
-				G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(session_proxy, "ErrorOccurred",
+				    G_CALLBACK(error_occurred_cb), NULL, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferStarted",
-				G_CALLBACK(transfer_started), NULL, NULL);
+	dbus_g_proxy_add_signal(session_proxy, "TransferProgress",
+				G_TYPE_UINT, G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "TransferProgress",
-						G_TYPE_UINT, G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(session_proxy, "TransferProgress",
+				    G_CALLBACK(transfer_progress_cb), NULL, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferProgress",
-				G_CALLBACK(transfer_progress), NULL, NULL);
+	dbus_g_proxy_add_signal(session_proxy, "TransferCompleted", 
+				G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "TransferCompleted", G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(session_proxy, "TransferCompleted",
+				    G_CALLBACK(transfer_completed_cb), NULL, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferCompleted",
-				G_CALLBACK(transfer_completed), NULL, NULL);
+	dbus_g_proxy_call(session_proxy, "Connect", &error, G_TYPE_INVALID,
+			  G_TYPE_INVALID);
 
-	dbus_g_proxy_call(proxy, "Connect", NULL, G_TYPE_INVALID,
-							G_TYPE_INVALID);
+out:
+	if (error)
+		g_error_free(error);
 }
 
-static void show_error_dialog(const gchar *message)
+static void error_occurred_cb(DBusGProxy *proxy, const gchar *error_name, const gchar *error_message, gpointer user_data)
 {
-	GtkWidget *dialog;
+	gint ret;
+	gboolean link_error = FALSE;
+	gchar *error_text;
+	const gchar *reason;
+
+	g_message("ErrorOccurred");
+	g_message("Error name: %s", error_name);
+	g_message("Error message: %s", error_message);
+
+	error_text = g_strdup_printf(_("An error occured while sending file '%s'"),
+				     (gchar *)g_slist_nth_data(file_list, current_file));
+	if (!strcmp(error_name, "org.openobex.Error.LinkError")) {
+		/* Connection to remote device was lost */
+		g_object_unref(G_OBJECT(session_proxy));
+		reason = _("Connection to remote device was lost");
+		link_error = TRUE;
+	} else if (!strcmp(error_name, "org.openobex.Error.Forbidden")) {
+		reason = _("Remote device rejected file");
+	} else {
+		reason = _("Unknown error");
+	}
+	ret = handle_file_sending_error(error_text, reason);
 
-	dialog = gtk_message_dialog_new_with_markup(NULL, GTK_DIALOG_MODAL,
-				GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, message);
+	if (ret != GTK_RESPONSE_CANCEL) {
+		if (link_error) {
+			/* Need to reestablish connection */
+			dbus_g_proxy_begin_call(manager_proxy, "CreateBluetoothSession",
+						(DBusGProxyCallNotify) session_created_cb, NULL, NULL,
+						G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+						G_TYPE_INVALID);
+		} else {
+			g_idle_add((GSourceFunc) send_one, NULL);
+		}
+	}
 
-	gtk_dialog_run(GTK_DIALOG(dialog));
 
-	gtk_widget_destroy(dialog);
+	g_free(error_text);
 }
 
-static void name_owner_changed(DBusGProxy *proxy, const char *name,
-			const char *prev, const char *new, gpointer user_data)
+static void free_mem(void)
 {
-	if (g_str_equal(name, "org.openobex") == TRUE && *new == '\0')
-		gtk_main_quit();
+	if (files_to_send)
+		g_strfreev(files_to_send);
+	g_free(bdaddrstr);
+
+	if (main_dialog)
+		gtk_widget_destroy (main_dialog);
+	if (G_IS_OBJECT(manager_proxy))
+		g_object_unref(G_OBJECT(manager_proxy));
+	if (G_IS_OBJECT(session_proxy))
+		g_object_unref (G_OBJECT(session_proxy));
+	g_slist_free(file_list);
 }
 
-static gchar *option_device = NULL;
-
-static GOptionEntry options[] = {
-	{ "device", 0, 0, G_OPTION_ARG_STRING, &option_device,
-				N_("Remote device to use"), "ADDRESS" },
-	{ NULL },
-};
-
 int main(int argc, char *argv[])
 {
-	DBusGProxy *proxy;
-	GError *error;
-	gchar *filename, *address;
+	GOptionContext *option_context;
+	GError *error = NULL;
+	guint i;
 
+	/* initialize gettext */
 	bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
 	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
 	textdomain(GETTEXT_PACKAGE);
 
-	error = NULL;
-
-	if (gtk_init_with_args(&argc, &argv, "[FILE...]",
-				options, GETTEXT_PACKAGE, &error) == FALSE) {
-		if (error != NULL) {
-			g_printerr("%s\n", error->message);
-			g_error_free(error);
-		} else
-			g_printerr("An unknown error occurred\n");
-
-		gtk_exit(1);
-	}
-
-	error = NULL;
-
-	conn = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
-	if (conn == NULL) {
-		if (error != NULL) {
-			g_printerr("Connecting to session bus failed: %s\n",
-							error->message);
-			g_error_free(error);
-		} else
-			g_print("An unknown error occured\n");
-
-		gtk_exit(1);
+	/* parse command line arguments */
+	option_context = g_option_context_new("<file list>");
+	g_option_context_add_main_entries(option_context, options, GETTEXT_PACKAGE);
+	g_option_context_add_group(option_context, gtk_get_option_group(TRUE));
+	g_option_context_parse(option_context, &argc, &argv, &error);
+	g_option_context_free(option_context);
+	if (error) {
+		printf("Usage:\n");
+		printf("  %s [--dest=BDADDR] <file list>\n", g_get_prgname());
+		g_error_free(error);
+		free_mem();
+		return 0;
 	}
 
-	gtk_window_set_default_icon_name("stock_bluetooth");
+	gtk_init(&argc, &argv);
 
-	error = NULL;
-
-	proxy = dbus_g_proxy_new_for_name_owner(conn, "org.openobex",
-			"/org/openobex", "org.openobex.Manager", &error);
-	if (proxy == NULL) {
-		show_error_dialog(error->message);
+	/* init DBus connection */
+	connection = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
+	if (!connection) {
+		show_error_dialog(NULL, GTK_BUTTONS_OK,
+				  _("Please verify that D-Bus is correctly installed and setup."), error->message);
 		g_error_free(error);
-		gtk_exit(1);
+		return 1;
 	}
 
-	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING_INT,
-				G_TYPE_NONE, G_TYPE_STRING,
-				G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
-
-	if (argc < 2) {
-		filename = open_file_dialog();
-		if (filename == NULL)
-			gtk_exit(1);
-	} else
-		filename = g_strdup(argv[1]);
-
-	if (option_device == NULL) {
-		address = browse_device_dialog();
-		if (address == NULL) {
-			g_free(filename);
-			gtk_exit(1);
+	main_dialog = NULL;
+	from_label = NULL;
+	operation_label = NULL;
+	progress_bar = NULL;
+
+	/* get a list of files to send from command-line */
+	if (files_to_send)
+		file_count = g_strv_length(files_to_send);
+	else
+		file_count = 0;
+	for (i = 0; i < file_count; i++) {
+		file_list = g_slist_append(file_list,
+						g_strdup(*(files_to_send + i)));
+	}
+	/* show file chooser if no files were specified */
+	if (!file_count) {
+		file_list = select_files_dialog();
+		file_count = g_slist_length(file_list);
+	}
+	/* Check if there are some files to send */
+	if (!file_count) {
+		free_mem();
+		return 0;
+	}
+	/* Check for non regular and non existand files and
+	 * determine total bytes to send */
+	for (i = 0; i < file_count; i++) {
+		GSList *item;
+		gchar *path, *filename;
+		gint64 size;
+
+		item = g_slist_nth(file_list, i);
+		path = (gchar *) item->data;
+		filename = normalise_filename(path, &size);
+		if (filename == NULL) {
+			g_free(path);
+			file_list = g_slist_remove_link(file_list, item);
+			continue;
 		}
-	} else
-		address = g_strdup(option_device);
-
-	create_window(filename);
-
-	g_object_unref(proxy);
-
-	proxy = dbus_g_proxy_new_for_name(conn, "org.openobex",
-				"/org/openobex", "org.openobex.Manager");
-
-	dbus_g_proxy_add_signal(proxy, "NameOwnerChanged",
-		G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
+		g_free(item->data);
+		item->data = filename;
+		byte_count += size;
+	}
 
-	dbus_g_proxy_connect_signal(proxy, "NameOwnerChanged",
-				G_CALLBACK(name_owner_changed), NULL, NULL);
+	/* determine Bluetooth device to send to */
+	if (!bdaddrstr) {
+		bdaddrstr = browse_device_dialog();
+		if (!bdaddrstr) {
+			free_mem();
+			return 0;
+		}
+	}
 
-	dbus_g_proxy_begin_call(proxy, "CreateBluetoothSession",
-				create_notify, filename, NULL,
-				G_TYPE_STRING, address, G_TYPE_STRING, "opp",
+	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING_INT,
+					  G_TYPE_NONE, G_TYPE_STRING,
+					  G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING,
+					  G_TYPE_NONE, G_TYPE_STRING,
+					  G_TYPE_STRING, G_TYPE_INVALID);
+
+	/* init UI */
+	ui_init();
+
+	/* init DBus proxy, create Session */
+	manager_proxy = dbus_g_proxy_new_for_name(connection,
+						       "org.openobex",
+						       "/org/openobex",
+						       "org.openobex.Manager");
+	dbus_g_proxy_add_signal(manager_proxy, "NameOwnerChanged",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(manager_proxy, "NameOwnerChanged",
+				    G_CALLBACK(name_owner_changed), NULL, NULL);
+
+	dbus_g_proxy_begin_call(manager_proxy, "CreateBluetoothSession",
+				(DBusGProxyCallNotify) session_created_cb, NULL, NULL,
+				G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
 				G_TYPE_INVALID);
 
+	/* Go into main loop */
 	gtk_main();
 
-	g_object_unref(proxy);
-
-	dbus_g_connection_unref(conn);
-
-	g_free(address);
-	g_free(filename);
+	/* done sending, free memory */
+	free_mem();
 
 	return 0;
 }
+
--- /dev/null	2008-01-24 17:45:36.352009045 +0000
+++ sendto/utils.c	2008-02-06 11:33:46.000000000 +0000
@@ -0,0 +1,112 @@
+/*
+ * bluetooth-sendto
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-2007  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ *  Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+
+#include "bluetooth-device-selection.h"
+
+static void selected_device_changed_cb(BluetoothDeviceSelection *selector,
+				       gchar *address, gpointer user_data)
+{
+	GtkWidget *dialog = user_data;
+
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
+					  GTK_RESPONSE_ACCEPT, address != NULL);
+}
+
+gchar *browse_device_dialog(void)
+{
+	GtkWidget *dialog;
+	GtkWidget *selector;
+	gchar *address = NULL;
+
+	dialog = gtk_dialog_new_with_buttons(_("Select Destination Device"),
+					     NULL, GTK_DIALOG_NO_SEPARATOR,
+					     GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+					     GTK_STOCK_CONNECT, GTK_RESPONSE_ACCEPT, NULL);
+
+	gtk_window_set_icon_name(GTK_WINDOW(dialog), "stock_bluetooth");
+
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
+					  GTK_RESPONSE_ACCEPT, FALSE);
+
+	gtk_window_set_default_size(GTK_WINDOW(dialog), 450, 300);
+
+	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
+
+	selector = bluetooth_device_selection_new(_("Select destination device"));
+	bluetooth_device_selection_start_discovery(
+	    BLUETOOTH_DEVICE_SELECTION(selector));
+
+	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), selector);
+
+	g_signal_connect(selector, "selected-device-changed",
+			 G_CALLBACK(selected_device_changed_cb), dialog);
+
+	gtk_widget_show_all(selector);
+
+	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
+		g_object_get(selector, "device-selected", &address, NULL);
+
+	gtk_widget_destroy(dialog);
+
+	return address;
+}
+
+GSList *select_files_dialog(void)
+{
+	GtkWidget *dialog;
+	GSList *file_list;
+
+	file_list = NULL;
+	dialog = gtk_file_chooser_dialog_new(_("Choose files to send"), NULL,
+					     GTK_FILE_CHOOSER_ACTION_OPEN,
+					     GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+					     _("Send"), GTK_RESPONSE_ACCEPT,
+					     NULL);
+	gtk_window_set_icon_name(GTK_WINDOW(dialog), "stock_bluetooth");
+	gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
+
+	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+		file_list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
+	}
+	gtk_widget_destroy(dialog);
+
+	return file_list;
+}
+
--- /dev/null	2008-01-24 17:45:36.352009045 +0000
+++ sendto/utils.h	2008-02-01 16:00:15.000000000 +0000
@@ -0,0 +1,30 @@
+/*
+ * bluetooth-sendto
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-2007  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ *  Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <glib.h>
+
+gchar *browse_device_dialog(void);
+GSList *select_files_dialog(void);
+
Index: po/POTFILES.in
===================================================================
RCS file: /cvsroot/bluez/gnome/po/POTFILES.in,v
retrieving revision 1.14
diff -u -p -r1.14 POTFILES.in
--- po/POTFILES.in	17 Dec 2007 01:15:34 -0000	1.14
+++ po/POTFILES.in	6 Feb 2008 12:15:38 -0000
@@ -17,3 +17,5 @@ analyzer/dialog.c
 analyzer/tracer.c
 analyzer/bluetooth-analyzer.desktop.in
 analyzer/bluetooth-manager.xml.in
+sendto/main.c
+sendto/utils.c

[-- Attachment #3: Type: text/plain, Size: 228 bytes --]

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/

[-- Attachment #4: Type: text/plain, Size: 164 bytes --]

_______________________________________________
Bluez-devel mailing list
Bluez-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bluez-devel

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Bluez-devel] [PATCH] Updated sendto
  2008-02-06 12:18         ` [Bluez-devel] [PATCH] Updated sendto Bastien Nocera
@ 2008-02-06 13:08           ` Bastien Nocera
  2008-02-07  0:59             ` Bastien Nocera
  0 siblings, 1 reply; 16+ messages in thread
From: Bastien Nocera @ 2008-02-06 13:08 UTC (permalink / raw)
  To: BlueZ development

[-- Attachment #1: Type: text/plain, Size: 1293 bytes --]


On Wed, 2008-02-06 at 12:18 +0000, Bastien Nocera wrote:
> On Fri, 2008-02-01 at 16:20 +0000, Bastien Nocera wrote:
> > On Fri, 2008-02-01 at 16:08 +0000, Bastien Nocera wrote:
> > > On Fri, 2008-02-01 at 13:14 +0000, Bastien Nocera wrote:
> > > > On Fri, 2008-01-18 at 13:43 +0000, Bastien Nocera wrote:
> > > > > Hello again,
> > > > > 
> > > > > On Thu, 2008-01-17 at 10:43 +0000, Bastien Nocera wrote: 
> > > > > > Heya,
> > > > > > 
> > > > > > Another for bluetooth-sento feature parity with gnome-obex-send.
> > > > > 
> > > > > Another patch, based on Tadas' gnome-obex-send port.
> > > > > 
> > > > > - Fix to run with the latest obex-data-server (in 2 places)
> > > > > - Add an appropriate error when the target device is a Palm
> > > > > - Accept relative local paths and URIs as well as absolute filenames as
> > > > > arguments (needed for ease of use and nautilus-sendto, respectively)
> > > > > - Gather the name of the Bluetooth device from hcid to show in the UI
> > > > 
> > > > Updated patch to use the marshal file in common/ instead of our own.
> > > 
> > > Updated patch:
> > > - follow coding style
> > > - move the helper dialogues in another file
> > 
> > And the function declarations on one line.
> 
> And remove the BluetoothSend *app bits.

And one that builds.

[-- Attachment #2: bluez-gnome-new-sendto-6.patch --]
[-- Type: text/x-patch, Size: 37506 bytes --]

Index: sendto/Makefile.am
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/Makefile.am,v
retrieving revision 1.10
diff -u -p -r1.10 Makefile.am
--- sendto/Makefile.am	6 Feb 2008 01:53:17 -0000	1.10
+++ sendto/Makefile.am	6 Feb 2008 13:06:24 -0000
@@ -1,7 +1,7 @@
 
 bin_PROGRAMS = bluetooth-sendto
 
-bluetooth_sendto_SOURCES = main.c
+bluetooth_sendto_SOURCES = main.c utils.c utils.h
 
 bluetooth_sendto_LDADD = $(top_builddir)/common/libcommon.a \
 					@GTK_LIBS@ @DBUS_LIBS@
@@ -15,3 +15,4 @@ man_MANS = bluetooth-sendto.1
 EXTRA_DIST = $(man_MANS)
 
 MAINTAINERCLEANFILES = Makefile.in
+
Index: sendto/main.c
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/main.c,v
retrieving revision 1.17
diff -u -p -r1.17 main.c
--- sendto/main.c	6 Feb 2008 12:22:48 -0000	1.17
+++ sendto/main.c	6 Feb 2008 13:06:24 -0000
@@ -3,6 +3,8 @@
  *  BlueZ - Bluetooth protocol stack for Linux
  *
  *  Copyright (C) 2005-2008  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ *  Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
  *
  *
  *  This program is free software; you can redistribute it and/or modify
@@ -25,260 +27,620 @@
 #include <config.h>
 #endif
 
-#include <dbus/dbus-glib.h>
 
 #include <glib/gi18n.h>
-
 #include <gtk/gtk.h>
 
-#include "bluetooth-device-selection.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <dbus/dbus-glib.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
 
+#include "dbus-glue.h"
 #include "marshal.h"
+#include "utils.h"
 
-static DBusGConnection *conn = NULL;
+/* DBus */
+static DBusGConnection *connection = NULL;
+static DBusGProxy *manager_proxy = NULL;
+static DBusGProxy *session_proxy = NULL;
+/* GTK */
+static GtkWidget *main_dialog, *from_label, *operation_label, *progress_bar;
+/* sending related */
+static gsize file_length = 0;
+static guint file_count = 0;
+static guint current_file = 0;
+static GSList *file_list = NULL;
+static guint byte_count = 0;
+static guint bytes_sent = 0;
+static gint64 first_transfer_time = 0;
+static gint64 last_update_time = 0;
+
+typedef enum {
+	BUTTONS_OK,
+	BUTTONS_RETRY_CANCEL,
+	BUTTONS_RETRY_SKIP_CANCEL
+} ButtonSet;
+
+/* Command line options */
+static gchar **files_to_send = NULL;
+static gchar *bdaddrstr = NULL;
+static gchar *device_name = NULL;
+
+#define DIALOG_RESPONSE_SKIP 1
+#define DIALOG_RESPONSE_RETRY 2
+
+static void error_occurred_cb(DBusGProxy *proxy, const gchar *error_name,
+			      const gchar *error_message, gpointer user_data);
+
+static const GOptionEntry options[] = {
+	{"dest", 'd', 0, G_OPTION_ARG_STRING, &bdaddrstr, 
+		"Bluetooth address of destination device", "BDADDR"},
+	{G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &files_to_send},
+	{NULL}
+};
 
-static GtkWidget *dialog;
-static GtkWidget *label_filename;
-static GtkWidget *label_status;
-static GtkWidget *progress;
+static gint64 get_system_time(void)
+{
+	struct timeval tmp;
 
-static gint filesize = -1;
+	gettimeofday(&tmp, NULL);
+	return (gint64)tmp.tv_usec + (gint64)tmp.tv_sec * G_GINT64_CONSTANT(1000000);
+}
 
-static gchar *open_file_dialog(void)
+static gboolean is_palm_device(const gchar *bdaddr)
 {
-	GtkWidget *dialog;
-	gchar *filename = NULL;
+	return (g_str_has_prefix(bdaddr, "00:04:6B")
+		|| g_str_has_prefix(bdaddr, "00:07:E0")
+		|| g_str_has_prefix(bdaddr, "00:0E:20"));
+}
 
-	dialog = gtk_file_chooser_dialog_new(_("Select File"), NULL,
-				GTK_FILE_CHOOSER_ACTION_OPEN,
-				GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
-				GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
+/* Return the local absolute filename for the file, and its size
+ * if the file exists and is stat()'able */
+static gchar *normalise_filename(const gchar *filename, gint64 *size)
+{
+	char *ret;
+	struct stat file_stat;
 
-	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
-		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+	g_return_val_if_fail(filename != NULL, NULL);
+	g_return_val_if_fail(size != NULL, NULL);
 
-	gtk_widget_destroy(dialog);
+	if (g_str_has_prefix(filename, "file://")) {
+		ret = g_filename_from_uri(filename, NULL, NULL);
+		if (ret == NULL)
+			return NULL;
+	} else if (filename[0] == '/') {
+		ret = g_strdup(filename);
+	} else {
+		gchar *curdir;
 
-	return filename;
-}
+		curdir = g_get_current_dir();
+		ret = g_build_filename(curdir, filename, NULL);
+		g_free(curdir);
+	}
 
-static void selected_device_changed(BluetoothDeviceSelection *selector,
-					gchar *address, gpointer user_data)
-{
-	GtkWidget *dialog = user_data;
+	if (!g_file_test(ret, G_FILE_TEST_IS_REGULAR)) {
+		g_free(ret);
+		return NULL;
+	}
+
+	if (!g_stat(ret, &file_stat)) {
+		*size = file_stat.st_size;
+	} else {
+		g_free(ret);
+		return NULL;
+	}
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-				GTK_RESPONSE_ACCEPT, address != NULL);
+	return ret;
 }
 
-static gchar *browse_device_dialog(void)
+static gchar *get_device_name(void)
 {
-	GtkWidget *dialog;
-	GtkWidget *selector;
-	gchar *address = NULL;
+	DBusGConnection *connection;
+	DBusGProxy *manager;
+	GError *error = NULL;
+	gchar *name, **adapters;
+	guint i;
 
-	dialog = gtk_dialog_new_with_buttons(_("Select Device"),
-				NULL, GTK_DIALOG_NO_SEPARATOR,
-				GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
-				GTK_STOCK_CONNECT, GTK_RESPONSE_ACCEPT, NULL);
+	name = NULL;
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_ACCEPT, FALSE);
+	connection = dbus_g_bus_get(DBUS_BUS_SYSTEM, NULL);
+	if (connection == NULL)
+		return NULL;
+
+	manager = dbus_g_proxy_new_for_name(connection, "org.bluez",
+					    "/org/bluez", "org.bluez.Manager");
+	if (!manager) {
+		dbus_g_connection_unref(connection);
+		return NULL;
+	}
 
-	gtk_window_set_default_size(GTK_WINDOW(dialog), 450, 400);
+	if (!manager_list_adapters(manager, &adapters, &error)) {
+		g_object_unref(manager);
+		dbus_g_connection_unref(connection);
+		return NULL;
+	}
 
-	gtk_container_set_border_width(GTK_CONTAINER(dialog), 5);
-	gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), 2);
+	for (i = 0; adapters[i] != NULL; i++) {
+		DBusGProxy *adapter;
 
-	selector = bluetooth_device_selection_new(_("Select destination device"));
-	gtk_container_set_border_width(GTK_CONTAINER(selector), 5);
+		adapter = dbus_g_proxy_new_for_name(connection, "org.bluez",
+						    adapters[i], "org.bluez.Adapter");
+		if (dbus_g_proxy_call(adapter, "GetRemoteName", NULL,
+				      G_TYPE_STRING, bdaddrstr, G_TYPE_INVALID,
+				      G_TYPE_STRING, &name, G_TYPE_INVALID)) {
+			if (name != NULL && name[0] != '\0') {
+				g_object_unref(adapter);
+				break;
+			}
+		}
+		g_object_unref(adapter);
+	}
 
-	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), selector);
+	g_object_unref(manager);
+	dbus_g_connection_unref(connection);
 
-	g_signal_connect(selector, "selected-device-changed",
-				G_CALLBACK(selected_device_changed), dialog);
+	return name;
+}
 
-	gtk_widget_show_all(selector);
+static gint show_error_dialog(GtkWindow *parent, ButtonSet buttons,
+			      const gchar *primary_text, const gchar *secondary_text)
+{
+	GtkWidget *dialog;
+	gchar *primary_text_markup;
+	gint ret;
 
-	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
-		g_object_get(selector, "device-selected", &address, NULL);
+	primary_text_markup = g_strdup_printf("<span weight=\"bold\" size=\"larger\">%s</span>", 
+					      primary_text);
+	dialog = gtk_message_dialog_new_with_markup(parent, 0, GTK_MESSAGE_ERROR, 
+						    buttons==BUTTONS_OK? GTK_BUTTONS_OK: GTK_BUTTONS_CANCEL, 
+						    primary_text_markup);
+	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
+						 secondary_text);
+	if (buttons == BUTTONS_RETRY_SKIP_CANCEL) {
+		gtk_dialog_add_button(GTK_DIALOG(dialog), _("Skip"),
+				      DIALOG_RESPONSE_SKIP);
+	}
+	if (buttons != BUTTONS_OK) {
+		gtk_dialog_add_button(GTK_DIALOG(dialog), _("Retry"),
+				      DIALOG_RESPONSE_RETRY);
+	}
+	ret = gtk_dialog_run(GTK_DIALOG(dialog));
 
 	gtk_widget_destroy(dialog);
+	g_free(primary_text_markup);
+	return ret;
+}
 
-	return address;
+static gint show_connection_error_dialog(GtkWindow *parent, const gchar *bdaddr,
+					 const gchar *primary_text)
+{
+	if (!is_palm_device(bdaddrstr)) {
+		return show_error_dialog(parent,
+					 BUTTONS_RETRY_CANCEL,
+					 primary_text,
+					 _("Make sure that remote device is on and that it accepts Bluetooth connections."));
+	} else {
+		return show_error_dialog(parent,
+					 BUTTONS_RETRY_CANCEL,
+					 primary_text,
+					 _("Make sure \"Beam receive\" is enabled in the Power preferences, and Bluetooth is enabled, on your Palm device."));
+	}
 }
 
-static void response_callback(GtkWidget *dialog,
-					gint response, gpointer user_data)
+static gint handle_file_sending_error(const gchar *error_text,
+				      const gchar *error_secondary_text)
 {
-	gtk_widget_destroy(dialog);
+	ButtonSet buttons;
+	gint response_id;
+
+	if (file_count > 1 && current_file < file_count - 1)
+		buttons = BUTTONS_RETRY_SKIP_CANCEL;
+	else
+		buttons = BUTTONS_RETRY_CANCEL;
+
+	response_id = show_error_dialog(GTK_WINDOW(main_dialog), buttons, 
+					error_text, error_secondary_text);
+
+	switch (response_id) {
+	case DIALOG_RESPONSE_SKIP:
+		current_file++;
+		bytes_sent += file_length;
+	case DIALOG_RESPONSE_RETRY:
+		return TRUE;
+	case GTK_RESPONSE_CANCEL:
+	default:
+		gtk_main_quit();
+		break;
+	}
+	return response_id;
+}
 
+static void cancel_clicked_cb(GtkWidget *button, gpointer user_data)
+{
 	gtk_main_quit();
 }
 
-static void create_window(const gchar *filename)
+static void ui_init(void)
 {
-	GtkWidget *vbox;
-	GtkWidget *label;
+	GtkWidget *vbox, *vbox_parent, *table;
+	GtkWidget *label1, *label2, *label3, *target_label;
+	GtkWidget *button;
 	gchar *text;
 
-	dialog = gtk_dialog_new_with_buttons(_("File Transfer"), NULL,
-				GTK_DIALOG_NO_SEPARATOR,
-				GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
-
-	gtk_window_set_default_size(GTK_WINDOW(dialog), 400, -1);
+	/* initialize UI */
 
-	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
+	device_name = get_device_name();
 
-	vbox = gtk_vbox_new(FALSE, 6);
-	gtk_container_set_border_width(GTK_CONTAINER(vbox), 12);
-	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox);
-
-	label = gtk_label_new(NULL);
-	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+	/* main dialog */
+	main_dialog = gtk_dialog_new();
+	gtk_dialog_set_has_separator(GTK_DIALOG(main_dialog), FALSE);
+	gtk_window_set_title(GTK_WINDOW(main_dialog),
+			     _("Bluetooth file transfer"));
+	gtk_window_set_icon_name(GTK_WINDOW(main_dialog), "stock_bluetooth");
+	gtk_window_set_type_hint(GTK_WINDOW(main_dialog),
+				 GDK_WINDOW_TYPE_HINT_NORMAL);
+	gtk_window_set_position(GTK_WINDOW(main_dialog), GTK_WIN_POS_CENTER);
+	gtk_window_set_resizable(GTK_WINDOW(main_dialog), FALSE);
+	gtk_widget_set(main_dialog, "width-request", 400, NULL);
+	button = gtk_dialog_add_button(GTK_DIALOG(main_dialog),
+				       GTK_STOCK_CANCEL,
+				       GTK_RESPONSE_CANCEL);
+	g_signal_connect(G_OBJECT(button), "clicked",
+			 G_CALLBACK(cancel_clicked_cb), NULL);
+	gtk_container_set_border_width(GTK_CONTAINER(main_dialog), 6);
+
+	/* table elements */
+	from_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(from_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(from_label), PANGO_ELLIPSIZE_MIDDLE);
+	target_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(target_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(target_label), PANGO_ELLIPSIZE_END);
+	gtk_label_set_text(GTK_LABEL(target_label), device_name ? device_name : bdaddrstr);
+	label2 = gtk_label_new(NULL);
+	text = g_markup_printf_escaped("<b>%s</b>", _("From:"));
+	gtk_label_set_markup(GTK_LABEL(label2), text);
+	g_free(text);
+	label3 = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(label3), 1, 0.5);
+	text = g_markup_printf_escaped("<b>%s</b>", _("To:"));
+	gtk_label_set_markup(GTK_LABEL(label3), text);
+	g_free(text);
 
-	label_filename = label;
+	/* table */
+	table = gtk_table_new(2, 2, FALSE);
+	gtk_table_set_col_spacings(GTK_TABLE(table), 4);
+	gtk_table_set_row_spacings(GTK_TABLE(table), 4);
+	gtk_table_attach_defaults(GTK_TABLE(table), from_label, 1, 2, 0, 1);
+	gtk_table_attach_defaults(GTK_TABLE(table), target_label, 1, 2, 1, 2);
+	gtk_table_attach(GTK_TABLE(table), label2, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), label3, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+
+	/* vbox elements */
+	label1 = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(label1), 0, 0.5);
+	text = g_markup_printf_escaped("<span weight=\"bold\" size=\"larger\">%s</span>",
+				       _("Sending files via bluetooth"));
+	gtk_label_set_markup(GTK_LABEL(label1), text);
+	g_free(text);
+	progress_bar = gtk_progress_bar_new();
+	gtk_progress_bar_set_ellipsize(GTK_PROGRESS_BAR(progress_bar),
+				       PANGO_ELLIPSIZE_END);
+	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
+				  _("Connecting..."));
+	operation_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(operation_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(operation_label), PANGO_ELLIPSIZE_END);
+
+	/* vbox */
+	vbox = gtk_vbox_new(FALSE, 0);
+	gtk_box_set_spacing(GTK_BOX(vbox), 6);
+	gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
+	gtk_box_pack_start_defaults(GTK_BOX(vbox), label1);
+	gtk_box_pack_start(GTK_BOX(vbox), table, TRUE, TRUE, 9);
+	gtk_box_pack_start_defaults(GTK_BOX(vbox), progress_bar);
+	gtk_box_pack_start(GTK_BOX(vbox), operation_label, TRUE, TRUE, 2);
+	vbox_parent = gtk_bin_get_child(GTK_BIN(main_dialog));
+	gtk_box_pack_start_defaults(GTK_BOX(vbox_parent), vbox);
+
+	/* show it */
+	gtk_widget_show_all(main_dialog);
+}
+
+static gboolean send_one(gpointer user_data)
+{
+	const gchar *fname;
+	gint ret;
+	gchar *bname, *dirname;
+	GError *err = NULL;
+	gchar *operation_text, *operation_markup, *progressbar_text;
+
+	if ((fname = g_slist_nth_data(file_list, current_file))) {
+		/* there's a file to send */
+		dbus_g_proxy_call(session_proxy, "SendFile", &err,
+				  G_TYPE_STRING, fname, G_TYPE_INVALID, G_TYPE_INVALID);
+
+		if (err != NULL) {
+			ret = handle_file_sending_error(_("Unable to read file"),
+							err->message);
+			if (ret != GTK_RESPONSE_CANCEL) {
+				g_idle_add((GSourceFunc) send_one, NULL);
+			}
+			g_error_free(err);
+			return FALSE;
+		}
 
-	progress = gtk_progress_bar_new();
-	gtk_box_pack_start(GTK_BOX(vbox), progress, FALSE, FALSE, 0);
+		if (!first_transfer_time) {
+			first_transfer_time = get_system_time();
+		}
+		bname = g_path_get_basename(fname);
+		dirname = g_path_get_dirname(fname);
 
-	label = gtk_label_new(NULL);
-	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
-	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 6);
+		operation_text = g_strdup_printf(_("Sending %s"), bname);
+		operation_markup = g_markup_printf_escaped("<i>%s</i>", operation_text);
+		progressbar_text = g_strdup_printf(_("Sending file: %d of %d"),
+						   current_file+1,
+						   file_count);
+		gtk_label_set_text(GTK_LABEL(from_label), dirname);
+		gtk_label_set_markup(GTK_LABEL(operation_label), operation_markup);
+		gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
+					  progressbar_text);
+
+		g_free(bname);
+		g_free(dirname);
+		g_free(operation_text);
+		g_free(operation_markup);
+		g_free(progressbar_text);
+	} else {
+		/* nothing left to send, time to quit */
+		gtk_main_quit();
+	}
+	return FALSE;
+}
 
-	label_status = label;
+static void session_connected_cb(DBusGProxy *proxy, gpointer user_data)
+{
+	/* OBEX connect has succeeded, so start sending */
+	g_idle_add((GSourceFunc) send_one, NULL);
+}
 
-	text = g_strdup_printf("<b>%s</b>", filename);
-	gtk_label_set_markup(GTK_LABEL(label_filename), text);
-	g_free(text);
+static void transfer_started_cb(DBusGProxy *proxy, const gchar *filename,
+				const gchar *local_path, gint byte_count, gpointer user_data)
+{
+	file_length = byte_count;
+}
 
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Connecting..."));
+static void transfer_cancelled_cb(DBusGProxy *proxy, gpointer user_data)
+{
+	gint ret;
+	gchar *error_text;
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, FALSE);
+	error_text = g_strdup_printf(_("An error occured while sending file '%s'"),
+				     (gchar *)g_slist_nth_data(file_list, current_file));
+	ret = handle_file_sending_error(error_text,
+					_("The remote device cancelled the transfer"));
 
-	g_signal_connect(G_OBJECT(dialog), "response",
-				G_CALLBACK(response_callback), NULL);
+	if (ret != GTK_RESPONSE_CANCEL)
+		g_idle_add((GSourceFunc) send_one, NULL);
 
-	gtk_widget_show_all(dialog);
+	g_free(error_text);
 }
 
-static void transfer_started(DBusGProxy *proxy, gchar *a, gchar *b,
-						gint size, gpointer user_data)
+static void transfer_progress_cb(DBusGProxy *proxy, gint bytes_transferred, gpointer user_data)
 {
-	gchar *text;
+	gdouble frac;
+	guint actual_bytes_sent;
+	gint elapsed_time;
+	gint transfer_rate;
+	gint time_remaining;
+	gint64 current_time;
+	gchar *str, *str2;
+	gchar *progressbar_text;
 
-	filesize = size;
+	/* update progress bar fraction */
+	actual_bytes_sent = bytes_sent + bytes_transferred;
+	frac = (gdouble)actual_bytes_sent / byte_count;
+	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress_bar), frac);
 
-	text = g_strdup_printf(_("Starting transfer of %d bytes"), size);
-	gtk_label_set_markup(GTK_LABEL(label_status), text);
-	g_free(text);
+	/* update progress bar text (time remaining)
+	 * (only update once in a second) */
+	current_time = get_system_time();
+	elapsed_time = (current_time - first_transfer_time) / 1000000;
 
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0.0);
-}
+	if (last_update_time) {
+		if (current_time < last_update_time + 1000000)
+			return;
+		else
+			last_update_time = current_time;
+	} else {
+		last_update_time = current_time;
+	}
 
-static void transfer_progress(DBusGProxy *proxy, guint bytes, gpointer user_data)
-{
-	gchar *text;
-	gdouble fraction;
+	if (!elapsed_time)
+		return;
 
-	text = g_strdup_printf(_("Transfered %d of %d bytes"), bytes, filesize);
-	gtk_label_set_markup(GTK_LABEL(label_status), text);
-	g_free(text);
+	transfer_rate = actual_bytes_sent / elapsed_time;
+	if (transfer_rate == 0)
+		return;
 
-	fraction = (gdouble) bytes / (gdouble) filesize;
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), fraction);
-}
+	time_remaining = (byte_count - actual_bytes_sent) / transfer_rate;
 
-static void transfer_completed(DBusGProxy *proxy, gpointer user_data)
-{
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Completed"));
+	if (time_remaining >= 3600) {
+		str = g_strdup_printf(_("(%d:%02d:%d Remaining)"), 
+				      time_remaining / 3600,
+				      (time_remaining % 3600) / 60,
+				      (time_remaining % 3600) % 60);
+	} else {
+		str = g_strdup_printf(_("(%d:%02d Remaining)"), 
+				      time_remaining / 60,
+				      time_remaining % 60);
+	}
 
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 1.0);
+	/* Translators:
+	 * Sending file 1 of 3 */
+	str2 = g_strdup_printf(_("Sending file %d of %d"),
+			       current_file + 1,
+			       file_count);
+	progressbar_text = g_strconcat(str2, " ", str, NULL);
+	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
+				  progressbar_text);
+	g_free(str);
+	g_free(str2);
+	g_free(progressbar_text);
+}
 
-	dbus_g_proxy_call(proxy, "Disconnect", NULL, G_TYPE_INVALID,
-							G_TYPE_INVALID);
+static void transfer_completed_cb(DBusGProxy *proxy, gpointer user_data)
+{
+	current_file++;
+	bytes_sent += file_length;
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, TRUE);
+	g_idle_add((GSourceFunc) send_one, NULL);
 }
 
-static void session_connected(DBusGProxy *proxy, gpointer user_data)
+static void session_created_cb(DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data)
 {
-	gchar *filename = user_data;
 	GError *error = NULL;
+	const gchar *path = NULL;
 
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Connected"));
+	if (!dbus_g_proxy_end_call(proxy, call, &error,
+				   G_TYPE_STRING, &path, G_TYPE_INVALID)) {
+		const gchar *error_name = NULL;
+		gint response_id;
+
+		if (error && error->code == DBUS_GERROR_REMOTE_EXCEPTION)
+			error_name = dbus_g_error_get_name(error);
+		if (error_name && !strcmp(error_name, "org.openobex.Error.ConnectionAttemptFailed")) {
+			response_id = show_connection_error_dialog(GTK_WINDOW(main_dialog), bdaddrstr, error->message);
+			if (response_id == DIALOG_RESPONSE_RETRY) {
+				/* Try to create Session again */
+				dbus_g_proxy_begin_call(manager_proxy,
+							"CreateBluetoothSession",
+							(DBusGProxyCallNotify) session_created_cb, NULL, NULL,
+							G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+							G_TYPE_BOOLEAN, FALSE, G_TYPE_INVALID);
+				goto out;
+			}
+		} else {
+			show_error_dialog(GTK_WINDOW(main_dialog), BUTTONS_OK,
+					  error->message, "");
+		}
 
-	dbus_g_proxy_call(proxy, "SendFile", &error,
-				G_TYPE_STRING, filename, G_TYPE_INVALID,
-							G_TYPE_INVALID);
-
-	if (error != NULL) {
-		g_printerr("Sending of file %s failed: %s\n", filename,
-							error->message);
-		g_error_free(error);
+		/* Failed, quit main loop */
 		gtk_main_quit();
+		goto out;
 	}
-}
 
-static void create_notify(DBusGProxy *proxy,
-				DBusGProxyCall *call, void *user_data)
-{
-	GError *error = NULL;
-	const gchar *path = NULL;
+	session_proxy = dbus_g_proxy_new_for_name(connection,
+						       "org.openobex",
+						       path,
+						       "org.openobex.Session");
 
-	if (dbus_g_proxy_end_call(proxy, call, &error,
-			G_TYPE_STRING, &path, G_TYPE_INVALID) == FALSE) {
-		gchar *text, *message;
-
-		if (error != NULL) {
-			message = g_strdup(error->message);
-			g_error_free(error);
-		} else
-			message = g_strdup(_("An unknown error occured"));
-
-		text = g_strdup_printf("<span foreground=\"red\">%s</span>",
-								message);
-		gtk_label_set_markup(GTK_LABEL(label_status), text);
-		g_free(text);
+	dbus_g_proxy_add_signal(session_proxy, "Connected",
+				G_TYPE_INVALID);
 
-		g_free(message);
+	dbus_g_proxy_connect_signal(session_proxy, "Connected",
+				    G_CALLBACK(session_connected_cb), NULL, NULL);
 
-		gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, TRUE);
-		return;
-	}
+	dbus_g_proxy_add_signal(session_proxy, "TransferStarted",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+
+	dbus_g_proxy_connect_signal(session_proxy, "TransferStarted",
+				    G_CALLBACK(transfer_started_cb), NULL, NULL);
 
-	proxy = dbus_g_proxy_new_for_name(conn, "org.openobex",
-						path, "org.openobex.Session");
+	dbus_g_proxy_add_signal(session_proxy, "Cancelled",
+				G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "Connected", G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(session_proxy, "Cancelled",
+				    G_CALLBACK(transfer_cancelled_cb), NULL, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "Connected",
-				G_CALLBACK(session_connected), user_data, NULL);
+	dbus_g_proxy_add_signal(session_proxy, "ErrorOccurred",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "TransferStarted", G_TYPE_STRING,
-				G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(session_proxy, "ErrorOccurred",
+				    G_CALLBACK(error_occurred_cb), NULL, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferStarted",
-				G_CALLBACK(transfer_started), NULL, NULL);
+	dbus_g_proxy_add_signal(session_proxy, "TransferProgress",
+				G_TYPE_UINT, G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "TransferProgress",
-						G_TYPE_UINT, G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(session_proxy, "TransferProgress",
+				    G_CALLBACK(transfer_progress_cb), NULL, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferProgress",
-				G_CALLBACK(transfer_progress), NULL, NULL);
+	dbus_g_proxy_add_signal(session_proxy, "TransferCompleted", 
+				G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "TransferCompleted", G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(session_proxy, "TransferCompleted",
+				    G_CALLBACK(transfer_completed_cb), NULL, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferCompleted",
-				G_CALLBACK(transfer_completed), NULL, NULL);
+	dbus_g_proxy_call(session_proxy, "Connect", &error, G_TYPE_INVALID,
+			  G_TYPE_INVALID);
 
-	dbus_g_proxy_call(proxy, "Connect", NULL, G_TYPE_INVALID,
-							G_TYPE_INVALID);
+out:
+	if (error)
+		g_error_free(error);
+}
+
+static void error_occurred_cb(DBusGProxy *proxy, const gchar *error_name, const gchar *error_message, gpointer user_data)
+{
+	gint ret;
+	gboolean link_error = FALSE;
+	gchar *error_text;
+	const gchar *reason;
+
+	g_message("ErrorOccurred");
+	g_message("Error name: %s", error_name);
+	g_message("Error message: %s", error_message);
+
+	error_text = g_strdup_printf(_("An error occured while sending file '%s'"),
+				     (gchar *)g_slist_nth_data(file_list, current_file));
+	if (!strcmp(error_name, "org.openobex.Error.LinkError")) {
+		/* Connection to remote device was lost */
+		g_object_unref(G_OBJECT(session_proxy));
+		reason = _("Connection to remote device was lost");
+		link_error = TRUE;
+	} else if (!strcmp(error_name, "org.openobex.Error.Forbidden")) {
+		reason = _("Remote device rejected file");
+	} else {
+		reason = _("Unknown error");
+	}
+	ret = handle_file_sending_error(error_text, reason);
+
+	if (ret != GTK_RESPONSE_CANCEL) {
+		if (link_error) {
+			/* Need to reestablish connection */
+			dbus_g_proxy_begin_call(manager_proxy, "CreateBluetoothSession",
+						(DBusGProxyCallNotify) session_created_cb, NULL, NULL,
+						G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+						G_TYPE_INVALID);
+		} else {
+			g_idle_add((GSourceFunc) send_one, NULL);
+		}
+	}
+
+
+	g_free(error_text);
+}
+
+static void free_mem(void)
+{
+	if (files_to_send)
+		g_strfreev(files_to_send);
+	g_free(bdaddrstr);
+
+	if (main_dialog)
+		gtk_widget_destroy (main_dialog);
+	if (G_IS_OBJECT(manager_proxy))
+		g_object_unref(G_OBJECT(manager_proxy));
+	if (G_IS_OBJECT(session_proxy))
+		g_object_unref (G_OBJECT(session_proxy));
+	g_slist_free(file_list);
 }
 
 static void name_owner_changed(DBusGProxy *proxy, const char *name,
@@ -288,95 +650,126 @@ static void name_owner_changed(DBusGProx
 		gtk_main_quit();
 }
 
-static gchar *option_device = NULL;
-
-static GOptionEntry options[] = {
-	{ "device", 0, 0, G_OPTION_ARG_STRING, &option_device,
-				N_("Remote device to use"), "ADDRESS" },
-	{ NULL },
-};
-
 int main(int argc, char *argv[])
 {
-	DBusGProxy *proxy;
+	GOptionContext *option_context;
 	GError *error = NULL;
-	gchar *filename, *address;
+	guint i;
 
+	/* initialize gettext */
 	bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
 	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
 	textdomain(GETTEXT_PACKAGE);
 
-	error = NULL;
-
-	if (gtk_init_with_args(&argc, &argv, "[FILE...]",
-				options, GETTEXT_PACKAGE, &error) == FALSE) {
-		if (error != NULL) {
-			g_printerr("%s\n", error->message);
-			g_error_free(error);
-		} else
-			g_printerr("An unknown error occurred\n");
-
-		gtk_exit(1);
-	}
-
-	gtk_window_set_default_icon_name("stock_bluetooth");
-
-	if (argc < 2) {
-		filename = open_file_dialog();
-		if (filename == NULL)
-			gtk_exit(1);
-	} else
-		filename = g_strdup(argv[1]);
-
-	if (option_device == NULL) {
-		address = browse_device_dialog();
-		if (address == NULL) {
-			g_free(filename);
-			gtk_exit(1);
-		}
-	} else
-		address = g_strdup(option_device);
-
-	conn = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
-	if (conn == NULL) {
-		if (error != NULL) {
-			g_printerr("Connecting to session bus failed: %s\n",
-							error->message);
-			g_error_free(error);
-		} else
-			g_print("An unknown error occured\n");
-
-		gtk_exit(1);
+	/* parse command line arguments */
+	option_context = g_option_context_new("<file list>");
+	g_option_context_add_main_entries(option_context, options, GETTEXT_PACKAGE);
+	g_option_context_add_group(option_context, gtk_get_option_group(TRUE));
+	g_option_context_parse(option_context, &argc, &argv, &error);
+	g_option_context_free(option_context);
+	if (error) {
+		printf("Usage:\n");
+		printf("  %s [--dest=BDADDR] <file list>\n", g_get_prgname());
+		g_error_free(error);
+		free_mem();
+		return 0;
 	}
 
-	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING_INT,
-				G_TYPE_NONE, G_TYPE_STRING,
-				G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+	gtk_init(&argc, &argv);
 
-	create_window(filename);
-
-	proxy = dbus_g_proxy_new_for_name(conn, "org.openobex",
-				"/org/openobex", "org.openobex.Manager");
+	/* init DBus connection */
+	connection = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
+	if (!connection) {
+		show_error_dialog(NULL, GTK_BUTTONS_OK,
+				  _("Please verify that D-Bus is correctly installed and setup."), error->message);
+		g_error_free(error);
+		return 1;
+	}
 
-	dbus_g_proxy_add_signal(proxy, "NameOwnerChanged",
-		G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
+	main_dialog = NULL;
+	from_label = NULL;
+	operation_label = NULL;
+	progress_bar = NULL;
+
+	/* get a list of files to send from command-line */
+	if (files_to_send)
+		file_count = g_strv_length(files_to_send);
+	else
+		file_count = 0;
+	for (i = 0; i < file_count; i++) {
+		file_list = g_slist_append(file_list,
+						g_strdup(*(files_to_send + i)));
+	}
+	/* show file chooser if no files were specified */
+	if (!file_count) {
+		file_list = select_files_dialog();
+		file_count = g_slist_length(file_list);
+	}
+	/* Check if there are some files to send */
+	if (!file_count) {
+		free_mem();
+		return 0;
+	}
+	/* Check for non regular and non existand files and
+	 * determine total bytes to send */
+	for (i = 0; i < file_count; i++) {
+		GSList *item;
+		gchar *path, *filename;
+		gint64 size;
+
+		item = g_slist_nth(file_list, i);
+		path = (gchar *) item->data;
+		filename = normalise_filename(path, &size);
+		if (filename == NULL) {
+			g_free(path);
+			file_list = g_slist_remove_link(file_list, item);
+			continue;
+		}
+		g_free(item->data);
+		item->data = filename;
+		byte_count += size;
+	}
 
-	dbus_g_proxy_connect_signal(proxy, "NameOwnerChanged",
-				G_CALLBACK(name_owner_changed), NULL, NULL);
+	/* determine Bluetooth device to send to */
+	if (!bdaddrstr) {
+		bdaddrstr = browse_device_dialog();
+		if (!bdaddrstr) {
+			free_mem();
+			return 0;
+		}
+	}
 
-	dbus_g_proxy_begin_call(proxy, "CreateBluetoothSession",
-				create_notify, filename, NULL,
-				G_TYPE_STRING, address, G_TYPE_STRING, "opp",
+	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING_INT,
+					  G_TYPE_NONE, G_TYPE_STRING,
+					  G_TYPE_STRING, G_TYPE_INT, G_TYPE_INVALID);
+	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING,
+					  G_TYPE_NONE, G_TYPE_STRING,
+					  G_TYPE_STRING, G_TYPE_INVALID);
+
+	/* init UI */
+	ui_init();
+
+	/* init DBus proxy, create Session */
+	manager_proxy = dbus_g_proxy_new_for_name(connection,
+						       "org.openobex",
+						       "/org/openobex",
+						       "org.openobex.Manager");
+	dbus_g_proxy_add_signal(manager_proxy, "NameOwnerChanged",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(manager_proxy, "NameOwnerChanged",
+				    G_CALLBACK(name_owner_changed), NULL, NULL);
+
+	dbus_g_proxy_begin_call(manager_proxy, "CreateBluetoothSession",
+				(DBusGProxyCallNotify) session_created_cb, NULL, NULL,
+				G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
 				G_TYPE_INVALID);
 
+	/* Go into main loop */
 	gtk_main();
 
-	g_object_unref(proxy);
-
-	dbus_g_connection_unref(conn);
-
-	g_free(address);
-	g_free(filename);
+	/* done sending, free memory */
+	free_mem();
 
 	return 0;
 }
+
Index: po/POTFILES.in
===================================================================
RCS file: /cvsroot/bluez/gnome/po/POTFILES.in,v
retrieving revision 1.14
diff -u -p -r1.14 POTFILES.in
--- po/POTFILES.in	17 Dec 2007 01:15:34 -0000	1.14
+++ po/POTFILES.in	6 Feb 2008 13:06:24 -0000
@@ -17,3 +17,5 @@ analyzer/dialog.c
 analyzer/tracer.c
 analyzer/bluetooth-analyzer.desktop.in
 analyzer/bluetooth-manager.xml.in
+sendto/main.c
+sendto/utils.c
--- /dev/null	2008-01-24 17:45:36.352009045 +0000
+++ sendto/utils.c	2008-02-06 13:05:42.000000000 +0000
@@ -0,0 +1,112 @@
+/*
+ * bluetooth-sendto
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-2007  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ *  Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+
+#include "bluetooth-device-selection.h"
+
+static void selected_device_changed_cb(BluetoothDeviceSelection *selector,
+				       gchar *address, gpointer user_data)
+{
+	GtkWidget *dialog = user_data;
+
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
+					  GTK_RESPONSE_ACCEPT, address != NULL);
+}
+
+gchar *browse_device_dialog(void)
+{
+	GtkWidget *dialog;
+	GtkWidget *selector;
+	gchar *address = NULL;
+
+	dialog = gtk_dialog_new_with_buttons(_("Select Destination Device"),
+					     NULL, GTK_DIALOG_NO_SEPARATOR,
+					     GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+					     GTK_STOCK_CONNECT, GTK_RESPONSE_ACCEPT, NULL);
+
+	gtk_window_set_icon_name(GTK_WINDOW(dialog), "stock_bluetooth");
+
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
+					  GTK_RESPONSE_ACCEPT, FALSE);
+
+	gtk_window_set_default_size(GTK_WINDOW(dialog), 450, 300);
+
+	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
+
+	selector = bluetooth_device_selection_new(_("Select destination device"));
+	bluetooth_device_selection_start_discovery(
+	    BLUETOOTH_DEVICE_SELECTION(selector));
+
+	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), selector);
+
+	g_signal_connect(selector, "selected-device-changed",
+			 G_CALLBACK(selected_device_changed_cb), dialog);
+
+	gtk_widget_show_all(selector);
+
+	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
+		g_object_get(selector, "device-selected", &address, NULL);
+
+	gtk_widget_destroy(dialog);
+
+	return address;
+}
+
+GSList *select_files_dialog(void)
+{
+	GtkWidget *dialog;
+	GSList *file_list;
+
+	file_list = NULL;
+	dialog = gtk_file_chooser_dialog_new(_("Choose files to send"), NULL,
+					     GTK_FILE_CHOOSER_ACTION_OPEN,
+					     GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+					     _("Send"), GTK_RESPONSE_ACCEPT,
+					     NULL);
+	gtk_window_set_icon_name(GTK_WINDOW(dialog), "stock_bluetooth");
+	gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
+
+	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+		file_list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
+	}
+	gtk_widget_destroy(dialog);
+
+	return file_list;
+}
+
--- /dev/null	2008-01-24 17:45:36.352009045 +0000
+++ sendto/utils.h	2008-02-06 13:05:42.000000000 +0000
@@ -0,0 +1,30 @@
+/*
+ * bluetooth-sendto
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-2007  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ *  Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <glib.h>
+
+gchar *browse_device_dialog(void);
+GSList *select_files_dialog(void);
+

[-- Attachment #3: Type: text/plain, Size: 228 bytes --]

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/

[-- Attachment #4: Type: text/plain, Size: 164 bytes --]

_______________________________________________
Bluez-devel mailing list
Bluez-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bluez-devel

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Bluez-devel] [PATCH] Updated sendto
  2008-02-06 13:08           ` Bastien Nocera
@ 2008-02-07  0:59             ` Bastien Nocera
  2008-02-23  1:30               ` Bastien Nocera
  0 siblings, 1 reply; 16+ messages in thread
From: Bastien Nocera @ 2008-02-07  0:59 UTC (permalink / raw)
  To: BlueZ development

[-- Attachment #1: Type: text/plain, Size: 1467 bytes --]


On Wed, 2008-02-06 at 13:08 +0000, Bastien Nocera wrote:
> On Wed, 2008-02-06 at 12:18 +0000, Bastien Nocera wrote:
> > On Fri, 2008-02-01 at 16:20 +0000, Bastien Nocera wrote:
> > > On Fri, 2008-02-01 at 16:08 +0000, Bastien Nocera wrote:
> > > > On Fri, 2008-02-01 at 13:14 +0000, Bastien Nocera wrote:
> > > > > On Fri, 2008-01-18 at 13:43 +0000, Bastien Nocera wrote:
> > > > > > Hello again,
> > > > > > 
> > > > > > On Thu, 2008-01-17 at 10:43 +0000, Bastien Nocera wrote: 
> > > > > > > Heya,
> > > > > > > 
> > > > > > > Another for bluetooth-sento feature parity with gnome-obex-send.
> > > > > > 
> > > > > > Another patch, based on Tadas' gnome-obex-send port.
> > > > > > 
> > > > > > - Fix to run with the latest obex-data-server (in 2 places)
> > > > > > - Add an appropriate error when the target device is a Palm
> > > > > > - Accept relative local paths and URIs as well as absolute filenames as
> > > > > > arguments (needed for ease of use and nautilus-sendto, respectively)
> > > > > > - Gather the name of the Bluetooth device from hcid to show in the UI
> > > > > 
> > > > > Updated patch to use the marshal file in common/ instead of our own.
> > > > 
> > > > Updated patch:
> > > > - follow coding style
> > > > - move the helper dialogues in another file
> > > 
> > > And the function declarations on one line.
> > 
> > And remove the BluetoothSend *app bits.
> 
> And one that builds.

And one that works with obex-data-server 0.1

Thanks!

[-- Attachment #2: bluez-gnome-new-sendto-7.patch --]
[-- Type: text/x-patch, Size: 37745 bytes --]

Index: po/POTFILES.in
===================================================================
RCS file: /cvsroot/bluez/gnome/po/POTFILES.in,v
retrieving revision 1.14
diff -u -p -r1.14 POTFILES.in
--- po/POTFILES.in	17 Dec 2007 01:15:34 -0000	1.14
+++ po/POTFILES.in	7 Feb 2008 00:57:49 -0000
@@ -17,3 +17,5 @@ analyzer/dialog.c
 analyzer/tracer.c
 analyzer/bluetooth-analyzer.desktop.in
 analyzer/bluetooth-manager.xml.in
+sendto/main.c
+sendto/utils.c
Index: sendto/Makefile.am
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/Makefile.am,v
retrieving revision 1.10
diff -u -p -r1.10 Makefile.am
--- sendto/Makefile.am	6 Feb 2008 01:53:17 -0000	1.10
+++ sendto/Makefile.am	7 Feb 2008 00:57:49 -0000
@@ -1,7 +1,7 @@
 
 bin_PROGRAMS = bluetooth-sendto
 
-bluetooth_sendto_SOURCES = main.c
+bluetooth_sendto_SOURCES = main.c utils.c utils.h
 
 bluetooth_sendto_LDADD = $(top_builddir)/common/libcommon.a \
 					@GTK_LIBS@ @DBUS_LIBS@
@@ -15,3 +15,4 @@ man_MANS = bluetooth-sendto.1
 EXTRA_DIST = $(man_MANS)
 
 MAINTAINERCLEANFILES = Makefile.in
+
Index: sendto/main.c
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/main.c,v
retrieving revision 1.20
diff -u -p -r1.20 main.c
--- sendto/main.c	7 Feb 2008 00:05:53 -0000	1.20
+++ sendto/main.c	7 Feb 2008 00:57:49 -0000
@@ -3,6 +3,8 @@
  *  BlueZ - Bluetooth protocol stack for Linux
  *
  *  Copyright (C) 2005-2008  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ *  Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
  *
  *
  *  This program is free software; you can redistribute it and/or modify
@@ -25,263 +27,620 @@
 #include <config.h>
 #endif
 
-#include <dbus/dbus-glib.h>
 
 #include <glib/gi18n.h>
-
 #include <gtk/gtk.h>
 
-#include "bluetooth-device-selection.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <dbus/dbus-glib.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
 
+#include "dbus-glue.h"
 #include "marshal.h"
+#include "utils.h"
 
-static DBusGConnection *conn = NULL;
+/* DBus */
+static DBusGConnection *connection = NULL;
+static DBusGProxy *manager_proxy = NULL;
+static DBusGProxy *session_proxy = NULL;
+/* GTK */
+static GtkWidget *main_dialog, *from_label, *operation_label, *progress_bar;
+/* sending related */
+static gsize file_length = 0;
+static guint file_count = 0;
+static guint current_file = 0;
+static GSList *file_list = NULL;
+static guint byte_count = 0;
+static guint bytes_sent = 0;
+static gint64 first_transfer_time = 0;
+static gint64 last_update_time = 0;
+
+typedef enum {
+	BUTTONS_OK,
+	BUTTONS_RETRY_CANCEL,
+	BUTTONS_RETRY_SKIP_CANCEL
+} ButtonSet;
+
+/* Command line options */
+static gchar **files_to_send = NULL;
+static gchar *bdaddrstr = NULL;
+static gchar *device_name = NULL;
+
+#define DIALOG_RESPONSE_SKIP 1
+#define DIALOG_RESPONSE_RETRY 2
+
+static void error_occurred_cb(DBusGProxy *proxy, const gchar *error_name,
+			      const gchar *error_message, gpointer user_data);
+
+static const GOptionEntry options[] = {
+	{"dest", 'd', 0, G_OPTION_ARG_STRING, &bdaddrstr, 
+		"Bluetooth address of destination device", "BDADDR"},
+	{G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &files_to_send},
+	{NULL}
+};
 
-static GtkWidget *dialog;
-static GtkWidget *label_filename;
-static GtkWidget *label_status;
-static GtkWidget *progress;
+static gint64 get_system_time(void)
+{
+	struct timeval tmp;
 
-static guint64 filesize = 0;
+	gettimeofday(&tmp, NULL);
+	return (gint64)tmp.tv_usec + (gint64)tmp.tv_sec * G_GINT64_CONSTANT(1000000);
+}
 
-static gchar *open_file_dialog(void)
+static gboolean is_palm_device(const gchar *bdaddr)
 {
-	GtkWidget *dialog;
-	gchar *filename = NULL;
+	return (g_str_has_prefix(bdaddr, "00:04:6B")
+		|| g_str_has_prefix(bdaddr, "00:07:E0")
+		|| g_str_has_prefix(bdaddr, "00:0E:20"));
+}
 
-	dialog = gtk_file_chooser_dialog_new(_("Select File"), NULL,
-				GTK_FILE_CHOOSER_ACTION_OPEN,
-				GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
-				GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
+/* Return the local absolute filename for the file, and its size
+ * if the file exists and is stat()'able */
+static gchar *normalise_filename(const gchar *filename, gint64 *size)
+{
+	char *ret;
+	struct stat file_stat;
 
-	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
-		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+	g_return_val_if_fail(filename != NULL, NULL);
+	g_return_val_if_fail(size != NULL, NULL);
 
-	gtk_widget_destroy(dialog);
+	if (g_str_has_prefix(filename, "file://")) {
+		ret = g_filename_from_uri(filename, NULL, NULL);
+		if (ret == NULL)
+			return NULL;
+	} else if (filename[0] == '/') {
+		ret = g_strdup(filename);
+	} else {
+		gchar *curdir;
 
-	return filename;
-}
+		curdir = g_get_current_dir();
+		ret = g_build_filename(curdir, filename, NULL);
+		g_free(curdir);
+	}
 
-static void selected_device_changed(BluetoothDeviceSelection *selector,
-					gchar *address, gpointer user_data)
-{
-	GtkWidget *dialog = user_data;
+	if (!g_file_test(ret, G_FILE_TEST_IS_REGULAR)) {
+		g_free(ret);
+		return NULL;
+	}
+
+	if (!g_stat(ret, &file_stat)) {
+		*size = file_stat.st_size;
+	} else {
+		g_free(ret);
+		return NULL;
+	}
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-				GTK_RESPONSE_ACCEPT, address != NULL);
+	return ret;
 }
 
-static gchar *browse_device_dialog(void)
+static gchar *get_device_name(void)
 {
-	GtkWidget *dialog;
-	GtkWidget *selector;
-	gchar *address = NULL;
+	DBusGConnection *connection;
+	DBusGProxy *manager;
+	GError *error = NULL;
+	gchar *name, **adapters;
+	guint i;
 
-	dialog = gtk_dialog_new_with_buttons(_("Select Device"),
-				NULL, GTK_DIALOG_NO_SEPARATOR,
-				GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
-				GTK_STOCK_CONNECT, GTK_RESPONSE_ACCEPT, NULL);
+	name = NULL;
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_ACCEPT, FALSE);
+	connection = dbus_g_bus_get(DBUS_BUS_SYSTEM, NULL);
+	if (connection == NULL)
+		return NULL;
+
+	manager = dbus_g_proxy_new_for_name(connection, "org.bluez",
+					    "/org/bluez", "org.bluez.Manager");
+	if (!manager) {
+		dbus_g_connection_unref(connection);
+		return NULL;
+	}
 
-	gtk_window_set_default_size(GTK_WINDOW(dialog), 450, 400);
+	if (!manager_list_adapters(manager, &adapters, &error)) {
+		g_object_unref(manager);
+		dbus_g_connection_unref(connection);
+		return NULL;
+	}
 
-	gtk_container_set_border_width(GTK_CONTAINER(dialog), 5);
-	gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), 2);
+	for (i = 0; adapters[i] != NULL; i++) {
+		DBusGProxy *adapter;
 
-	selector = bluetooth_device_selection_new(_("Select destination device"));
-	gtk_container_set_border_width(GTK_CONTAINER(selector), 5);
+		adapter = dbus_g_proxy_new_for_name(connection, "org.bluez",
+						    adapters[i], "org.bluez.Adapter");
+		if (dbus_g_proxy_call(adapter, "GetRemoteName", NULL,
+				      G_TYPE_STRING, bdaddrstr, G_TYPE_INVALID,
+				      G_TYPE_STRING, &name, G_TYPE_INVALID)) {
+			if (name != NULL && name[0] != '\0') {
+				g_object_unref(adapter);
+				break;
+			}
+		}
+		g_object_unref(adapter);
+	}
 
-	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), selector);
+	g_object_unref(manager);
+	dbus_g_connection_unref(connection);
 
-	g_signal_connect(selector, "selected-device-changed",
-				G_CALLBACK(selected_device_changed), dialog);
+	return name;
+}
 
-	gtk_widget_show_all(selector);
+static gint show_error_dialog(GtkWindow *parent, ButtonSet buttons,
+			      const gchar *primary_text, const gchar *secondary_text)
+{
+	GtkWidget *dialog;
+	gchar *primary_text_markup;
+	gint ret;
 
-	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
-		g_object_get(selector, "device-selected", &address, NULL);
+	primary_text_markup = g_strdup_printf("<span weight=\"bold\" size=\"larger\">%s</span>", 
+					      primary_text);
+	dialog = gtk_message_dialog_new_with_markup(parent, 0, GTK_MESSAGE_ERROR, 
+						    buttons==BUTTONS_OK? GTK_BUTTONS_OK: GTK_BUTTONS_CANCEL, 
+						    primary_text_markup);
+	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
+						 secondary_text);
+	if (buttons == BUTTONS_RETRY_SKIP_CANCEL) {
+		gtk_dialog_add_button(GTK_DIALOG(dialog), _("Skip"),
+				      DIALOG_RESPONSE_SKIP);
+	}
+	if (buttons != BUTTONS_OK) {
+		gtk_dialog_add_button(GTK_DIALOG(dialog), _("Retry"),
+				      DIALOG_RESPONSE_RETRY);
+	}
+	ret = gtk_dialog_run(GTK_DIALOG(dialog));
 
 	gtk_widget_destroy(dialog);
+	g_free(primary_text_markup);
+	return ret;
+}
 
-	return address;
+static gint show_connection_error_dialog(GtkWindow *parent, const gchar *bdaddr,
+					 const gchar *primary_text)
+{
+	if (!is_palm_device(bdaddrstr)) {
+		return show_error_dialog(parent,
+					 BUTTONS_RETRY_CANCEL,
+					 primary_text,
+					 _("Make sure that remote device is on and that it accepts Bluetooth connections."));
+	} else {
+		return show_error_dialog(parent,
+					 BUTTONS_RETRY_CANCEL,
+					 primary_text,
+					 _("Make sure \"Beam receive\" is enabled in the Power preferences, and Bluetooth is enabled, on your Palm device."));
+	}
 }
 
-static void response_callback(GtkWidget *dialog,
-					gint response, gpointer user_data)
+static gint handle_file_sending_error(const gchar *error_text,
+				      const gchar *error_secondary_text)
 {
-	gtk_widget_destroy(dialog);
+	ButtonSet buttons;
+	gint response_id;
+
+	if (file_count > 1 && current_file < file_count - 1)
+		buttons = BUTTONS_RETRY_SKIP_CANCEL;
+	else
+		buttons = BUTTONS_RETRY_CANCEL;
+
+	response_id = show_error_dialog(GTK_WINDOW(main_dialog), buttons, 
+					error_text, error_secondary_text);
+
+	switch (response_id) {
+	case DIALOG_RESPONSE_SKIP:
+		current_file++;
+		bytes_sent += file_length;
+	case DIALOG_RESPONSE_RETRY:
+		return TRUE;
+	case GTK_RESPONSE_CANCEL:
+	default:
+		gtk_main_quit();
+		break;
+	}
+	return response_id;
+}
 
+static void cancel_clicked_cb(GtkWidget *button, gpointer user_data)
+{
 	gtk_main_quit();
 }
 
-static void create_window(const gchar *filename)
+static void ui_init(void)
 {
-	GtkWidget *vbox;
-	GtkWidget *label;
+	GtkWidget *vbox, *vbox_parent, *table;
+	GtkWidget *label1, *label2, *label3, *target_label;
+	GtkWidget *button;
 	gchar *text;
 
-	dialog = gtk_dialog_new_with_buttons(_("File Transfer"), NULL,
-				GTK_DIALOG_NO_SEPARATOR,
-				GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
-
-	gtk_window_set_default_size(GTK_WINDOW(dialog), 400, -1);
+	/* initialize UI */
 
-	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
+	device_name = get_device_name();
 
-	vbox = gtk_vbox_new(FALSE, 6);
-	gtk_container_set_border_width(GTK_CONTAINER(vbox), 12);
-	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox);
-
-	label = gtk_label_new(NULL);
-	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+	/* main dialog */
+	main_dialog = gtk_dialog_new();
+	gtk_dialog_set_has_separator(GTK_DIALOG(main_dialog), FALSE);
+	gtk_window_set_title(GTK_WINDOW(main_dialog),
+			     _("Bluetooth file transfer"));
+	gtk_window_set_icon_name(GTK_WINDOW(main_dialog), "stock_bluetooth");
+	gtk_window_set_type_hint(GTK_WINDOW(main_dialog),
+				 GDK_WINDOW_TYPE_HINT_NORMAL);
+	gtk_window_set_position(GTK_WINDOW(main_dialog), GTK_WIN_POS_CENTER);
+	gtk_window_set_resizable(GTK_WINDOW(main_dialog), FALSE);
+	gtk_widget_set(main_dialog, "width-request", 400, NULL);
+	button = gtk_dialog_add_button(GTK_DIALOG(main_dialog),
+				       GTK_STOCK_CANCEL,
+				       GTK_RESPONSE_CANCEL);
+	g_signal_connect(G_OBJECT(button), "clicked",
+			 G_CALLBACK(cancel_clicked_cb), NULL);
+	gtk_container_set_border_width(GTK_CONTAINER(main_dialog), 6);
+
+	/* table elements */
+	from_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(from_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(from_label), PANGO_ELLIPSIZE_MIDDLE);
+	target_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(target_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(target_label), PANGO_ELLIPSIZE_END);
+	gtk_label_set_text(GTK_LABEL(target_label), device_name ? device_name : bdaddrstr);
+	label2 = gtk_label_new(NULL);
+	text = g_markup_printf_escaped("<b>%s</b>", _("From:"));
+	gtk_label_set_markup(GTK_LABEL(label2), text);
+	g_free(text);
+	label3 = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(label3), 1, 0.5);
+	text = g_markup_printf_escaped("<b>%s</b>", _("To:"));
+	gtk_label_set_markup(GTK_LABEL(label3), text);
+	g_free(text);
 
-	label_filename = label;
+	/* table */
+	table = gtk_table_new(2, 2, FALSE);
+	gtk_table_set_col_spacings(GTK_TABLE(table), 4);
+	gtk_table_set_row_spacings(GTK_TABLE(table), 4);
+	gtk_table_attach_defaults(GTK_TABLE(table), from_label, 1, 2, 0, 1);
+	gtk_table_attach_defaults(GTK_TABLE(table), target_label, 1, 2, 1, 2);
+	gtk_table_attach(GTK_TABLE(table), label2, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), label3, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+
+	/* vbox elements */
+	label1 = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(label1), 0, 0.5);
+	text = g_markup_printf_escaped("<span weight=\"bold\" size=\"larger\">%s</span>",
+				       _("Sending files via bluetooth"));
+	gtk_label_set_markup(GTK_LABEL(label1), text);
+	g_free(text);
+	progress_bar = gtk_progress_bar_new();
+	gtk_progress_bar_set_ellipsize(GTK_PROGRESS_BAR(progress_bar),
+				       PANGO_ELLIPSIZE_END);
+	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
+				  _("Connecting..."));
+	operation_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(operation_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(operation_label), PANGO_ELLIPSIZE_END);
+
+	/* vbox */
+	vbox = gtk_vbox_new(FALSE, 0);
+	gtk_box_set_spacing(GTK_BOX(vbox), 6);
+	gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
+	gtk_box_pack_start_defaults(GTK_BOX(vbox), label1);
+	gtk_box_pack_start(GTK_BOX(vbox), table, TRUE, TRUE, 9);
+	gtk_box_pack_start_defaults(GTK_BOX(vbox), progress_bar);
+	gtk_box_pack_start(GTK_BOX(vbox), operation_label, TRUE, TRUE, 2);
+	vbox_parent = gtk_bin_get_child(GTK_BIN(main_dialog));
+	gtk_box_pack_start_defaults(GTK_BOX(vbox_parent), vbox);
+
+	/* show it */
+	gtk_widget_show_all(main_dialog);
+}
+
+static gboolean send_one(gpointer user_data)
+{
+	const gchar *fname;
+	gint ret;
+	gchar *bname, *dirname;
+	GError *err = NULL;
+	gchar *operation_text, *operation_markup, *progressbar_text;
+
+	if ((fname = g_slist_nth_data(file_list, current_file))) {
+		/* there's a file to send */
+		dbus_g_proxy_call(session_proxy, "SendFile", &err,
+				  G_TYPE_STRING, fname, G_TYPE_INVALID, G_TYPE_INVALID);
+
+		if (err != NULL) {
+			ret = handle_file_sending_error(_("Unable to read file"),
+							err->message);
+			if (ret != GTK_RESPONSE_CANCEL) {
+				g_idle_add((GSourceFunc) send_one, NULL);
+			}
+			g_error_free(err);
+			return FALSE;
+		}
 
-	progress = gtk_progress_bar_new();
-	gtk_box_pack_start(GTK_BOX(vbox), progress, FALSE, FALSE, 0);
+		if (!first_transfer_time) {
+			first_transfer_time = get_system_time();
+		}
+		bname = g_path_get_basename(fname);
+		dirname = g_path_get_dirname(fname);
 
-	label = gtk_label_new(NULL);
-	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
-	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 6);
+		operation_text = g_strdup_printf(_("Sending %s"), bname);
+		operation_markup = g_markup_printf_escaped("<i>%s</i>", operation_text);
+		progressbar_text = g_strdup_printf(_("Sending file: %d of %d"),
+						   current_file+1,
+						   file_count);
+		gtk_label_set_text(GTK_LABEL(from_label), dirname);
+		gtk_label_set_markup(GTK_LABEL(operation_label), operation_markup);
+		gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
+					  progressbar_text);
+
+		g_free(bname);
+		g_free(dirname);
+		g_free(operation_text);
+		g_free(operation_markup);
+		g_free(progressbar_text);
+	} else {
+		/* nothing left to send, time to quit */
+		gtk_main_quit();
+	}
+	return FALSE;
+}
 
-	label_status = label;
+static void session_connected_cb(DBusGProxy *proxy, gpointer user_data)
+{
+	/* OBEX connect has succeeded, so start sending */
+	g_idle_add((GSourceFunc) send_one, NULL);
+}
 
-	text = g_strdup_printf("<b>%s</b>", filename);
-	gtk_label_set_markup(GTK_LABEL(label_filename), text);
-	g_free(text);
+static void transfer_started_cb(DBusGProxy *proxy, const gchar *filename,
+				const gchar *local_path, guint64 byte_count, gpointer user_data)
+{
+	file_length = byte_count;
+}
 
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Connecting..."));
+static void transfer_cancelled_cb(DBusGProxy *proxy, gpointer user_data)
+{
+	gint ret;
+	gchar *error_text;
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, FALSE);
+	error_text = g_strdup_printf(_("An error occured while sending file '%s'"),
+				     (gchar *)g_slist_nth_data(file_list, current_file));
+	ret = handle_file_sending_error(error_text,
+					_("The remote device cancelled the transfer"));
 
-	g_signal_connect(G_OBJECT(dialog), "response",
-				G_CALLBACK(response_callback), NULL);
+	if (ret != GTK_RESPONSE_CANCEL)
+		g_idle_add((GSourceFunc) send_one, NULL);
 
-	gtk_widget_show_all(dialog);
+	g_free(error_text);
 }
 
-static void transfer_started(DBusGProxy *proxy, gchar *a, gchar *b,
-					guint64 size, gpointer user_data)
+static void transfer_progress_cb(DBusGProxy *proxy, guint64 bytes_transferred, gpointer user_data)
 {
-	gchar *text;
+	gdouble frac;
+	guint actual_bytes_sent;
+	gint elapsed_time;
+	gint transfer_rate;
+	gint time_remaining;
+	gint64 current_time;
+	gchar *str, *str2;
+	gchar *progressbar_text;
 
-	filesize = size;
+	/* update progress bar fraction */
+	actual_bytes_sent = bytes_sent + bytes_transferred;
+	frac = (gdouble)actual_bytes_sent / byte_count;
+	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress_bar), frac);
 
-	text = g_strdup_printf(_("Starting transfer of %jd bytes"), size);
-	gtk_label_set_markup(GTK_LABEL(label_status), text);
-	g_free(text);
+	/* update progress bar text (time remaining)
+	 * (only update once in a second) */
+	current_time = get_system_time();
+	elapsed_time = (current_time - first_transfer_time) / 1000000;
 
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0.0);
-}
+	if (last_update_time) {
+		if (current_time < last_update_time + 1000000)
+			return;
+		else
+			last_update_time = current_time;
+	} else {
+		last_update_time = current_time;
+	}
 
-static void transfer_progress(DBusGProxy *proxy,
-					guint64 bytes, gpointer user_data)
-{
-	gchar *text;
-	gdouble fraction;
+	if (!elapsed_time)
+		return;
 
-	text = g_strdup_printf(_("Transfered %jd of %jd bytes"),
-							bytes, filesize);
-	gtk_label_set_markup(GTK_LABEL(label_status), text);
-	g_free(text);
+	transfer_rate = actual_bytes_sent / elapsed_time;
+	if (transfer_rate == 0)
+		return;
 
-	fraction = (gdouble) bytes / (gdouble) filesize;
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), fraction);
-}
+	time_remaining = (byte_count - actual_bytes_sent) / transfer_rate;
 
-static void transfer_completed(DBusGProxy *proxy, gpointer user_data)
-{
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Completed"));
+	if (time_remaining >= 3600) {
+		str = g_strdup_printf(_("(%d:%02d:%d Remaining)"), 
+				      time_remaining / 3600,
+				      (time_remaining % 3600) / 60,
+				      (time_remaining % 3600) % 60);
+	} else {
+		str = g_strdup_printf(_("(%d:%02d Remaining)"), 
+				      time_remaining / 60,
+				      time_remaining % 60);
+	}
 
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 1.0);
+	/* Translators:
+	 * Sending file 1 of 3 */
+	str2 = g_strdup_printf(_("Sending file %d of %d"),
+			       current_file + 1,
+			       file_count);
+	progressbar_text = g_strconcat(str2, " ", str, NULL);
+	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
+				  progressbar_text);
+	g_free(str);
+	g_free(str2);
+	g_free(progressbar_text);
+}
 
-	dbus_g_proxy_call(proxy, "Disconnect", NULL, G_TYPE_INVALID,
-							G_TYPE_INVALID);
+static void transfer_completed_cb(DBusGProxy *proxy, gpointer user_data)
+{
+	current_file++;
+	bytes_sent += file_length;
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, TRUE);
+	g_idle_add((GSourceFunc) send_one, NULL);
 }
 
-static void session_connected(DBusGProxy *proxy, gpointer user_data)
+static void session_created_cb(DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data)
 {
-	gchar *filename = user_data;
 	GError *error = NULL;
+	const gchar *path = NULL;
 
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Connected"));
+	if (!dbus_g_proxy_end_call(proxy, call, &error,
+				   DBUS_TYPE_G_OBJECT_PATH, &path, G_TYPE_INVALID)) {
+		const gchar *error_name = NULL;
+		gint response_id;
+
+		if (error && error->code == DBUS_GERROR_REMOTE_EXCEPTION)
+			error_name = dbus_g_error_get_name(error);
+		if (error_name && !strcmp(error_name, "org.openobex.Error.ConnectionAttemptFailed")) {
+			response_id = show_connection_error_dialog(GTK_WINDOW(main_dialog), bdaddrstr, error->message);
+			if (response_id == DIALOG_RESPONSE_RETRY) {
+				/* Try to create Session again */
+				dbus_g_proxy_begin_call(manager_proxy,
+							"CreateBluetoothSession",
+							(DBusGProxyCallNotify) session_created_cb, NULL, NULL,
+							G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+							G_TYPE_BOOLEAN, FALSE, G_TYPE_INVALID);
+				goto out;
+			}
+		} else {
+			show_error_dialog(GTK_WINDOW(main_dialog), BUTTONS_OK,
+					  error->message, "");
+		}
 
-	dbus_g_proxy_call(proxy, "SendFile", &error,
-				G_TYPE_STRING, filename, G_TYPE_INVALID,
-							G_TYPE_INVALID);
-
-	if (error != NULL) {
-		g_printerr("Sending of file %s failed: %s\n", filename,
-							error->message);
-		g_error_free(error);
+		/* Failed, quit main loop */
 		gtk_main_quit();
+		goto out;
 	}
-}
 
-static void create_notify(DBusGProxy *proxy,
-				DBusGProxyCall *call, void *user_data)
-{
-	GError *error = NULL;
-	const gchar *path = NULL;
+	session_proxy = dbus_g_proxy_new_for_name(connection,
+						       "org.openobex",
+						       path,
+						       "org.openobex.Session");
 
-	if (dbus_g_proxy_end_call(proxy, call, &error,
-					DBUS_TYPE_G_OBJECT_PATH, &path,
-						G_TYPE_INVALID) == FALSE) {
-		gchar *text, *message;
-
-		if (error != NULL) {
-			message = g_strdup(error->message);
-			g_error_free(error);
-		} else
-			message = g_strdup(_("An unknown error occured"));
-
-		text = g_strdup_printf("<span foreground=\"red\">%s</span>",
-								message);
-		gtk_label_set_markup(GTK_LABEL(label_status), text);
-		g_free(text);
+	dbus_g_proxy_add_signal(session_proxy, "Connected",
+				G_TYPE_INVALID);
 
-		g_free(message);
+	dbus_g_proxy_connect_signal(session_proxy, "Connected",
+				    G_CALLBACK(session_connected_cb), NULL, NULL);
 
-		gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, TRUE);
-		return;
-	}
+	dbus_g_proxy_add_signal(session_proxy, "TransferStarted",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);
+
+	dbus_g_proxy_connect_signal(session_proxy, "TransferStarted",
+				    G_CALLBACK(transfer_started_cb), NULL, NULL);
+
+	dbus_g_proxy_add_signal(session_proxy, "Cancelled",
+				G_TYPE_INVALID);
+
+	dbus_g_proxy_connect_signal(session_proxy, "Cancelled",
+				    G_CALLBACK(transfer_cancelled_cb), NULL, NULL);
 
-	proxy = dbus_g_proxy_new_for_name(conn, "org.openobex",
-						path, "org.openobex.Session");
+	dbus_g_proxy_add_signal(session_proxy, "ErrorOccurred",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "Connected", G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(session_proxy, "ErrorOccurred",
+				    G_CALLBACK(error_occurred_cb), NULL, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "Connected",
-				G_CALLBACK(session_connected), user_data, NULL);
+	dbus_g_proxy_add_signal(session_proxy, "TransferProgress",
+				G_TYPE_UINT64, G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "TransferStarted", G_TYPE_STRING,
-				G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(session_proxy, "TransferProgress",
+				    G_CALLBACK(transfer_progress_cb), NULL, NULL);
+
+	dbus_g_proxy_add_signal(session_proxy, "TransferCompleted", 
+				G_TYPE_INVALID);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferStarted",
-				G_CALLBACK(transfer_started), NULL, NULL);
+	dbus_g_proxy_connect_signal(session_proxy, "TransferCompleted",
+				    G_CALLBACK(transfer_completed_cb), NULL, NULL);
+
+	dbus_g_proxy_call(session_proxy, "Connect", &error, G_TYPE_INVALID,
+			  G_TYPE_INVALID);
+
+out:
+	if (error)
+		g_error_free(error);
+}
 
-	dbus_g_proxy_add_signal(proxy, "TransferProgress",
-						G_TYPE_UINT64, G_TYPE_INVALID);
+static void error_occurred_cb(DBusGProxy *proxy, const gchar *error_name, const gchar *error_message, gpointer user_data)
+{
+	gint ret;
+	gboolean link_error = FALSE;
+	gchar *error_text;
+	const gchar *reason;
+
+	g_message("ErrorOccurred");
+	g_message("Error name: %s", error_name);
+	g_message("Error message: %s", error_message);
+
+	error_text = g_strdup_printf(_("An error occured while sending file '%s'"),
+				     (gchar *)g_slist_nth_data(file_list, current_file));
+	if (!strcmp(error_name, "org.openobex.Error.LinkError")) {
+		/* Connection to remote device was lost */
+		g_object_unref(G_OBJECT(session_proxy));
+		reason = _("Connection to remote device was lost");
+		link_error = TRUE;
+	} else if (!strcmp(error_name, "org.openobex.Error.Forbidden")) {
+		reason = _("Remote device rejected file");
+	} else {
+		reason = _("Unknown error");
+	}
+	ret = handle_file_sending_error(error_text, reason);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferProgress",
-				G_CALLBACK(transfer_progress), NULL, NULL);
+	if (ret != GTK_RESPONSE_CANCEL) {
+		if (link_error) {
+			/* Need to reestablish connection */
+			dbus_g_proxy_begin_call(manager_proxy, "CreateBluetoothSession",
+						(DBusGProxyCallNotify) session_created_cb, NULL, NULL,
+						G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+						G_TYPE_INVALID);
+		} else {
+			g_idle_add((GSourceFunc) send_one, NULL);
+		}
+	}
 
-	dbus_g_proxy_add_signal(proxy, "TransferCompleted", G_TYPE_INVALID);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferCompleted",
-				G_CALLBACK(transfer_completed), NULL, NULL);
+	g_free(error_text);
+}
 
-	dbus_g_proxy_call(proxy, "Connect", NULL, G_TYPE_INVALID,
-							G_TYPE_INVALID);
+static void free_mem(void)
+{
+	if (files_to_send)
+		g_strfreev(files_to_send);
+	g_free(bdaddrstr);
+
+	if (main_dialog)
+		gtk_widget_destroy (main_dialog);
+	if (G_IS_OBJECT(manager_proxy))
+		g_object_unref(G_OBJECT(manager_proxy));
+	if (G_IS_OBJECT(session_proxy))
+		g_object_unref (G_OBJECT(session_proxy));
+	g_slist_free(file_list);
 }
 
 static void name_owner_changed(DBusGProxy *proxy, const char *name,
@@ -291,98 +650,128 @@ static void name_owner_changed(DBusGProx
 		gtk_main_quit();
 }
 
-static gchar *option_device = NULL;
-
-static GOptionEntry options[] = {
-	{ "device", 0, 0, G_OPTION_ARG_STRING, &option_device,
-				N_("Remote device to use"), "ADDRESS" },
-	{ NULL },
-};
-
 int main(int argc, char *argv[])
 {
-	DBusGProxy *proxy;
+	GOptionContext *option_context;
 	GError *error = NULL;
-	gchar *filename, *address;
+	guint i;
 
+	/* initialize gettext */
 	bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
 	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
 	textdomain(GETTEXT_PACKAGE);
 
-	error = NULL;
+	/* parse command line arguments */
+	option_context = g_option_context_new("<file list>");
+	g_option_context_add_main_entries(option_context, options, GETTEXT_PACKAGE);
+	g_option_context_add_group(option_context, gtk_get_option_group(TRUE));
+	g_option_context_parse(option_context, &argc, &argv, &error);
+	g_option_context_free(option_context);
+	if (error) {
+		printf("Usage:\n");
+		printf("  %s [--dest=BDADDR] <file list>\n", g_get_prgname());
+		g_error_free(error);
+		free_mem();
+		return 0;
+	}
 
-	if (gtk_init_with_args(&argc, &argv, "[FILE...]",
-				options, GETTEXT_PACKAGE, &error) == FALSE) {
-		if (error != NULL) {
-			g_printerr("%s\n", error->message);
-			g_error_free(error);
-		} else
-			g_printerr("An unknown error occurred\n");
-
-		gtk_exit(1);
-	}
-
-	gtk_window_set_default_icon_name("stock_bluetooth");
-
-	if (argc < 2) {
-		filename = open_file_dialog();
-		if (filename == NULL)
-			gtk_exit(1);
-	} else
-		filename = g_strdup(argv[1]);
-
-	if (option_device == NULL) {
-		address = browse_device_dialog();
-		if (address == NULL) {
-			g_free(filename);
-			gtk_exit(1);
-		}
-	} else
-		address = g_strdup(option_device);
+	gtk_init(&argc, &argv);
 
-	conn = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
-	if (conn == NULL) {
-		if (error != NULL) {
-			g_printerr("Connecting to session bus failed: %s\n",
-							error->message);
-			g_error_free(error);
-		} else
-			g_print("An unknown error occured\n");
+	/* init DBus connection */
+	connection = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
+	if (!connection) {
+		show_error_dialog(NULL, GTK_BUTTONS_OK,
+				  _("Please verify that D-Bus is correctly installed and setup."), error->message);
+		g_error_free(error);
+		return 1;
+	}
 
-		gtk_exit(1);
+	main_dialog = NULL;
+	from_label = NULL;
+	operation_label = NULL;
+	progress_bar = NULL;
+
+	/* get a list of files to send from command-line */
+	if (files_to_send)
+		file_count = g_strv_length(files_to_send);
+	else
+		file_count = 0;
+	for (i = 0; i < file_count; i++) {
+		file_list = g_slist_append(file_list,
+						g_strdup(*(files_to_send + i)));
+	}
+	/* show file chooser if no files were specified */
+	if (!file_count) {
+		file_list = select_files_dialog();
+		file_count = g_slist_length(file_list);
+	}
+	/* Check if there are some files to send */
+	if (!file_count) {
+		free_mem();
+		return 0;
+	}
+	/* Check for non regular and non existand files and
+	 * determine total bytes to send */
+	for (i = 0; i < file_count; i++) {
+		GSList *item;
+		gchar *path, *filename;
+		gint64 size;
+
+		item = g_slist_nth(file_list, i);
+		path = (gchar *) item->data;
+		filename = normalise_filename(path, &size);
+		if (filename == NULL) {
+			g_free(path);
+			file_list = g_slist_remove_link(file_list, item);
+			continue;
+		}
+		g_free(item->data);
+		item->data = filename;
+		byte_count += size;
 	}
 
-	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING_UINT64,
-				G_TYPE_NONE, G_TYPE_STRING,
-				G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);
+	/* determine Bluetooth device to send to */
+	if (!bdaddrstr) {
+		bdaddrstr = browse_device_dialog();
+		if (!bdaddrstr) {
+			free_mem();
+			return 0;
+		}
+	}
 
+	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING_UINT64,
+					  G_TYPE_NONE, G_TYPE_STRING,
+					  G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);
+	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING,
+					  G_TYPE_NONE, G_TYPE_STRING,
+					  G_TYPE_STRING, G_TYPE_INVALID);
 	dbus_g_object_register_marshaller(marshal_VOID__UINT64,
-				G_TYPE_NONE, G_TYPE_UINT64, G_TYPE_INVALID);
-
-	create_window(filename);
-
-	proxy = dbus_g_proxy_new_for_name(conn, "org.openobex",
-				"/org/openobex", "org.openobex.Manager");
-
-	dbus_g_proxy_add_signal(proxy, "NameOwnerChanged",
-		G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
+					  G_TYPE_NONE, G_TYPE_UINT64, G_TYPE_INVALID);
 
-	dbus_g_proxy_connect_signal(proxy, "NameOwnerChanged",
-				G_CALLBACK(name_owner_changed), NULL, NULL);
+	/* init UI */
+	ui_init();
 
-	dbus_g_proxy_begin_call(proxy, "CreateBluetoothSession",
-				create_notify, filename, NULL,
-				G_TYPE_STRING, address, G_TYPE_STRING, "opp",
+	/* init DBus proxy, create Session */
+	manager_proxy = dbus_g_proxy_new_for_name(connection,
+						       "org.openobex",
+						       "/org/openobex",
+						       "org.openobex.Manager");
+	dbus_g_proxy_add_signal(manager_proxy, "NameOwnerChanged",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(manager_proxy, "NameOwnerChanged",
+				    G_CALLBACK(name_owner_changed), NULL, NULL);
+
+	dbus_g_proxy_begin_call(manager_proxy, "CreateBluetoothSession",
+				(DBusGProxyCallNotify) session_created_cb, NULL, NULL,
+				G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
 				G_TYPE_INVALID);
 
+	/* Go into main loop */
 	gtk_main();
 
-	g_object_unref(proxy);
-
-	dbus_g_connection_unref(conn);
-
-	g_free(address);
-	g_free(filename);
+	/* done sending, free memory */
+	free_mem();
 
 	return 0;
 }
+
--- /dev/null	2008-01-24 17:45:36.352009045 +0000
+++ sendto/utils.c	2008-02-06 13:05:42.000000000 +0000
@@ -0,0 +1,112 @@
+/*
+ * bluetooth-sendto
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-2007  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ *  Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+
+#include "bluetooth-device-selection.h"
+
+static void selected_device_changed_cb(BluetoothDeviceSelection *selector,
+				       gchar *address, gpointer user_data)
+{
+	GtkWidget *dialog = user_data;
+
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
+					  GTK_RESPONSE_ACCEPT, address != NULL);
+}
+
+gchar *browse_device_dialog(void)
+{
+	GtkWidget *dialog;
+	GtkWidget *selector;
+	gchar *address = NULL;
+
+	dialog = gtk_dialog_new_with_buttons(_("Select Destination Device"),
+					     NULL, GTK_DIALOG_NO_SEPARATOR,
+					     GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+					     GTK_STOCK_CONNECT, GTK_RESPONSE_ACCEPT, NULL);
+
+	gtk_window_set_icon_name(GTK_WINDOW(dialog), "stock_bluetooth");
+
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
+					  GTK_RESPONSE_ACCEPT, FALSE);
+
+	gtk_window_set_default_size(GTK_WINDOW(dialog), 450, 300);
+
+	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
+
+	selector = bluetooth_device_selection_new(_("Select destination device"));
+	bluetooth_device_selection_start_discovery(
+	    BLUETOOTH_DEVICE_SELECTION(selector));
+
+	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), selector);
+
+	g_signal_connect(selector, "selected-device-changed",
+			 G_CALLBACK(selected_device_changed_cb), dialog);
+
+	gtk_widget_show_all(selector);
+
+	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
+		g_object_get(selector, "device-selected", &address, NULL);
+
+	gtk_widget_destroy(dialog);
+
+	return address;
+}
+
+GSList *select_files_dialog(void)
+{
+	GtkWidget *dialog;
+	GSList *file_list;
+
+	file_list = NULL;
+	dialog = gtk_file_chooser_dialog_new(_("Choose files to send"), NULL,
+					     GTK_FILE_CHOOSER_ACTION_OPEN,
+					     GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+					     _("Send"), GTK_RESPONSE_ACCEPT,
+					     NULL);
+	gtk_window_set_icon_name(GTK_WINDOW(dialog), "stock_bluetooth");
+	gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
+
+	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+		file_list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
+	}
+	gtk_widget_destroy(dialog);
+
+	return file_list;
+}
+
--- /dev/null	2008-01-24 17:45:36.352009045 +0000
+++ sendto/utils.h	2008-02-06 13:05:42.000000000 +0000
@@ -0,0 +1,30 @@
+/*
+ * bluetooth-sendto
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-2007  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ *  Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <glib.h>
+
+gchar *browse_device_dialog(void);
+GSList *select_files_dialog(void);
+

[-- Attachment #3: Type: text/plain, Size: 228 bytes --]

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/

[-- Attachment #4: Type: text/plain, Size: 164 bytes --]

_______________________________________________
Bluez-devel mailing list
Bluez-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bluez-devel

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Bluez-devel] [PATCH] Updated sendto
  2008-02-07  0:59             ` Bastien Nocera
@ 2008-02-23  1:30               ` Bastien Nocera
  2008-02-24 18:29                 ` Bastien Nocera
  0 siblings, 1 reply; 16+ messages in thread
From: Bastien Nocera @ 2008-02-23  1:30 UTC (permalink / raw)
  To: BlueZ development

[-- Attachment #1: Type: text/plain, Size: 253 bytes --]


On Thu, 2008-02-07 at 00:59 +0000, Bastien Nocera wrote:
<snip>
> And one that works with obex-data-server 0.1

Updated against current CVS, and fix a memleak when getting the adapters
list. New main.c file attached as well, for easier review.

Cheers

[-- Attachment #2: bluez-gnome-new-sendto-8.patch --]
[-- Type: text/x-patch, Size: 37927 bytes --]

Index: po/POTFILES.in
===================================================================
RCS file: /cvsroot/bluez/gnome/po/POTFILES.in,v
retrieving revision 1.18
diff -u -p -u -p -r1.18 POTFILES.in
--- po/POTFILES.in	12 Feb 2008 02:50:33 -0000	1.18
+++ po/POTFILES.in	23 Feb 2008 01:23:51 -0000
@@ -21,3 +21,5 @@ analyzer/dialog.c
 analyzer/tracer.c
 analyzer/bluetooth-analyzer.desktop.in
 analyzer/bluetooth-manager.xml.in
+sendto/main.c
+sendto/utils.c
Index: sendto/Makefile.am
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/Makefile.am,v
retrieving revision 1.10
diff -u -p -u -p -r1.10 Makefile.am
--- sendto/Makefile.am	6 Feb 2008 01:53:17 -0000	1.10
+++ sendto/Makefile.am	23 Feb 2008 01:23:54 -0000
@@ -1,7 +1,7 @@
 
 bin_PROGRAMS = bluetooth-sendto
 
-bluetooth_sendto_SOURCES = main.c
+bluetooth_sendto_SOURCES = main.c utils.c utils.h
 
 bluetooth_sendto_LDADD = $(top_builddir)/common/libcommon.a \
 					@GTK_LIBS@ @DBUS_LIBS@
@@ -15,3 +15,4 @@ man_MANS = bluetooth-sendto.1
 EXTRA_DIST = $(man_MANS)
 
 MAINTAINERCLEANFILES = Makefile.in
+
Index: sendto/main.c
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/main.c,v
retrieving revision 1.23
diff -u -p -u -p -r1.23 main.c
--- sendto/main.c	11 Feb 2008 22:24:30 -0000	1.23
+++ sendto/main.c	23 Feb 2008 01:23:54 -0000
@@ -3,6 +3,8 @@
  *  BlueZ - Bluetooth protocol stack for Linux
  *
  *  Copyright (C) 2005-2008  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ *  Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
  *
  *
  *  This program is free software; you can redistribute it and/or modify
@@ -25,267 +27,621 @@
 #include <config.h>
 #endif
 
-#include <dbus/dbus-glib.h>
 
 #include <glib/gi18n.h>
-
 #include <gtk/gtk.h>
 
-#include "bluetooth-device-selection.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <dbus/dbus-glib.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
 
+#include "dbus-glue.h"
 #include "marshal.h"
+#include "utils.h"
 
-static DBusGConnection *conn = NULL;
+/* DBus */
+static DBusGConnection *connection = NULL;
+static DBusGProxy *manager_proxy = NULL;
+static DBusGProxy *session_proxy = NULL;
+/* GTK */
+static GtkWidget *main_dialog, *from_label, *operation_label, *progress_bar;
+/* sending related */
+static gsize file_length = 0;
+static guint file_count = 0;
+static guint current_file = 0;
+static GSList *file_list = NULL;
+static guint byte_count = 0;
+static guint bytes_sent = 0;
+static gint64 first_transfer_time = 0;
+static gint64 last_update_time = 0;
+
+typedef enum {
+	BUTTONS_OK,
+	BUTTONS_RETRY_CANCEL,
+	BUTTONS_RETRY_SKIP_CANCEL
+} ButtonSet;
+
+/* Command line options */
+static gchar **files_to_send = NULL;
+static gchar *bdaddrstr = NULL;
+static gchar *device_name = NULL;
+
+#define DIALOG_RESPONSE_SKIP 1
+#define DIALOG_RESPONSE_RETRY 2
+
+static void error_occurred_cb(DBusGProxy *proxy, const gchar *error_name,
+			      const gchar *error_message, gpointer user_data);
+
+static const GOptionEntry options[] = {
+	{"dest", 'd', 0, G_OPTION_ARG_STRING, &bdaddrstr, 
+		"Bluetooth address of destination device", "BDADDR"},
+	{G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &files_to_send},
+	{NULL}
+};
 
-static GtkWidget *dialog;
-static GtkWidget *label_filename;
-static GtkWidget *label_status;
-static GtkWidget *progress;
+static gint64 get_system_time(void)
+{
+	struct timeval tmp;
 
-static guint64 filesize = 0;
+	gettimeofday(&tmp, NULL);
+	return (gint64)tmp.tv_usec + (gint64)tmp.tv_sec * G_GINT64_CONSTANT(1000000);
+}
 
-static gchar *open_file_dialog(void)
+static gboolean is_palm_device(const gchar *bdaddr)
 {
-	GtkWidget *dialog;
-	gchar *filename = NULL;
+	return (g_str_has_prefix(bdaddr, "00:04:6B")
+		|| g_str_has_prefix(bdaddr, "00:07:E0")
+		|| g_str_has_prefix(bdaddr, "00:0E:20"));
+}
 
-	dialog = gtk_file_chooser_dialog_new(_("Select File"), NULL,
-				GTK_FILE_CHOOSER_ACTION_OPEN,
-				GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
-				GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
+/* Return the local absolute filename for the file, and its size
+ * if the file exists and is stat()'able */
+static gchar *normalise_filename(const gchar *filename, gint64 *size)
+{
+	char *ret;
+	struct stat file_stat;
 
-	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
-		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+	g_return_val_if_fail(filename != NULL, NULL);
+	g_return_val_if_fail(size != NULL, NULL);
 
-	gtk_widget_destroy(dialog);
+	if (g_str_has_prefix(filename, "file://")) {
+		ret = g_filename_from_uri(filename, NULL, NULL);
+		if (ret == NULL)
+			return NULL;
+	} else if (filename[0] == '/') {
+		ret = g_strdup(filename);
+	} else {
+		gchar *curdir;
 
-	return filename;
-}
+		curdir = g_get_current_dir();
+		ret = g_build_filename(curdir, filename, NULL);
+		g_free(curdir);
+	}
 
-static void selected_device_changed(BluetoothDeviceSelection *selector,
-					gchar *address, gpointer user_data)
-{
-	GtkWidget *dialog = user_data;
+	if (!g_file_test(ret, G_FILE_TEST_IS_REGULAR)) {
+		g_free(ret);
+		return NULL;
+	}
+
+	if (!g_stat(ret, &file_stat)) {
+		*size = file_stat.st_size;
+	} else {
+		g_free(ret);
+		return NULL;
+	}
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-				GTK_RESPONSE_ACCEPT, address != NULL);
+	return ret;
 }
 
-static gchar *browse_device_dialog(void)
+static gchar *get_device_name(void)
 {
-	GtkWidget *dialog;
-	GtkWidget *selector;
-	gchar *address = NULL;
-
-	dialog = gtk_dialog_new_with_buttons(_("Select Device"),
-				NULL, GTK_DIALOG_NO_SEPARATOR,
-				GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
-				GTK_STOCK_CONNECT, GTK_RESPONSE_ACCEPT, NULL);
-
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_ACCEPT, FALSE);
+	DBusGConnection *connection;
+	DBusGProxy *manager;
+	GError *error = NULL;
+	gchar *name, **adapters;
+	guint i;
 
-	gtk_window_set_default_size(GTK_WINDOW(dialog), 450, 400);
+	name = NULL;
 
-	gtk_container_set_border_width(GTK_CONTAINER(dialog), 5);
-	gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), 2);
+	connection = dbus_g_bus_get(DBUS_BUS_SYSTEM, NULL);
+	if (connection == NULL)
+		return NULL;
+
+	manager = dbus_g_proxy_new_for_name(connection, "org.bluez",
+					    "/org/bluez", "org.bluez.Manager");
+	if (!manager) {
+		dbus_g_connection_unref(connection);
+		return NULL;
+	}
 
-	selector = bluetooth_device_selection_new(_("Select destination device"));
-	gtk_container_set_border_width(GTK_CONTAINER(selector), 5);
+	if (!manager_list_adapters(manager, &adapters, &error)) {
+		g_object_unref(manager);
+		dbus_g_connection_unref(connection);
+		return NULL;
+	}
 
-	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), selector);
+	for (i = 0; adapters[i] != NULL; i++) {
+		DBusGProxy *adapter;
 
-	g_signal_connect(selector, "selected-device-changed",
-				G_CALLBACK(selected_device_changed), dialog);
+		adapter = dbus_g_proxy_new_for_name(connection, "org.bluez",
+						    adapters[i], "org.bluez.Adapter");
+		if (dbus_g_proxy_call(adapter, "GetRemoteName", NULL,
+				      G_TYPE_STRING, bdaddrstr, G_TYPE_INVALID,
+				      G_TYPE_STRING, &name, G_TYPE_INVALID)) {
+			if (name != NULL && name[0] != '\0') {
+				g_object_unref(adapter);
+				break;
+			}
+		}
+		g_object_unref(adapter);
+	}
 
-	gtk_widget_show_all(selector);
+	g_strfreev(adapters);
+	g_object_unref(manager);
+	dbus_g_connection_unref(connection);
 
-	g_object_set(selector, "show-search", FALSE, NULL);
+	return name;
+}
 
-	bluetooth_device_selection_start_discovery(BLUETOOTH_DEVICE_SELECTION(selector));
+static gint show_error_dialog(GtkWindow *parent, ButtonSet buttons,
+			      const gchar *primary_text, const gchar *secondary_text)
+{
+	GtkWidget *dialog;
+	gchar *primary_text_markup;
+	gint ret;
 
-	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
-		g_object_get(selector, "device-selected", &address, NULL);
+	primary_text_markup = g_strdup_printf("<span weight=\"bold\" size=\"larger\">%s</span>", 
+					      primary_text);
+	dialog = gtk_message_dialog_new_with_markup(parent, 0, GTK_MESSAGE_ERROR, 
+						    buttons==BUTTONS_OK? GTK_BUTTONS_OK: GTK_BUTTONS_CANCEL, 
+						    primary_text_markup);
+	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
+						 secondary_text);
+	if (buttons == BUTTONS_RETRY_SKIP_CANCEL) {
+		gtk_dialog_add_button(GTK_DIALOG(dialog), _("Skip"),
+				      DIALOG_RESPONSE_SKIP);
+	}
+	if (buttons != BUTTONS_OK) {
+		gtk_dialog_add_button(GTK_DIALOG(dialog), _("Retry"),
+				      DIALOG_RESPONSE_RETRY);
+	}
+	ret = gtk_dialog_run(GTK_DIALOG(dialog));
 
 	gtk_widget_destroy(dialog);
+	g_free(primary_text_markup);
+	return ret;
+}
 
-	return address;
+static gint show_connection_error_dialog(GtkWindow *parent, const gchar *bdaddr,
+					 const gchar *primary_text)
+{
+	if (!is_palm_device(bdaddrstr)) {
+		return show_error_dialog(parent,
+					 BUTTONS_RETRY_CANCEL,
+					 primary_text,
+					 _("Make sure that remote device is on and that it accepts Bluetooth connections."));
+	} else {
+		return show_error_dialog(parent,
+					 BUTTONS_RETRY_CANCEL,
+					 primary_text,
+					 _("Make sure \"Beam receive\" is enabled in the Power preferences, and Bluetooth is enabled, on your Palm device."));
+	}
 }
 
-static void response_callback(GtkWidget *dialog,
-					gint response, gpointer user_data)
+static gint handle_file_sending_error(const gchar *error_text,
+				      const gchar *error_secondary_text)
 {
-	gtk_widget_destroy(dialog);
+	ButtonSet buttons;
+	gint response_id;
 
+	if (file_count > 1 && current_file < file_count - 1)
+		buttons = BUTTONS_RETRY_SKIP_CANCEL;
+	else
+		buttons = BUTTONS_RETRY_CANCEL;
+
+	response_id = show_error_dialog(GTK_WINDOW(main_dialog), buttons, 
+					error_text, error_secondary_text);
+
+	switch (response_id) {
+	case DIALOG_RESPONSE_SKIP:
+		current_file++;
+		bytes_sent += file_length;
+	case DIALOG_RESPONSE_RETRY:
+		return TRUE;
+	case GTK_RESPONSE_CANCEL:
+	default:
+		gtk_main_quit();
+		break;
+	}
+	return response_id;
+}
+
+static void cancel_clicked_cb(GtkWidget *button, gpointer user_data)
+{
 	gtk_main_quit();
 }
 
-static void create_window(const gchar *filename)
+static void ui_init(void)
 {
-	GtkWidget *vbox;
-	GtkWidget *label;
+	GtkWidget *vbox, *vbox_parent, *table;
+	GtkWidget *label1, *label2, *label3, *target_label;
+	GtkWidget *button;
 	gchar *text;
 
-	dialog = gtk_dialog_new_with_buttons(_("File Transfer"), NULL,
-				GTK_DIALOG_NO_SEPARATOR,
-				GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
-
-	gtk_window_set_default_size(GTK_WINDOW(dialog), 400, -1);
+	/* initialize UI */
 
-	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
+	device_name = get_device_name();
 
-	vbox = gtk_vbox_new(FALSE, 6);
-	gtk_container_set_border_width(GTK_CONTAINER(vbox), 12);
-	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox);
-
-	label = gtk_label_new(NULL);
-	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+	/* main dialog */
+	main_dialog = gtk_dialog_new();
+	gtk_dialog_set_has_separator(GTK_DIALOG(main_dialog), FALSE);
+	gtk_window_set_title(GTK_WINDOW(main_dialog),
+			     _("Bluetooth file transfer"));
+	gtk_window_set_icon_name(GTK_WINDOW(main_dialog), "stock_bluetooth");
+	gtk_window_set_type_hint(GTK_WINDOW(main_dialog),
+				 GDK_WINDOW_TYPE_HINT_NORMAL);
+	gtk_window_set_position(GTK_WINDOW(main_dialog), GTK_WIN_POS_CENTER);
+	gtk_window_set_resizable(GTK_WINDOW(main_dialog), FALSE);
+	gtk_widget_set(main_dialog, "width-request", 400, NULL);
+	button = gtk_dialog_add_button(GTK_DIALOG(main_dialog),
+				       GTK_STOCK_CANCEL,
+				       GTK_RESPONSE_CANCEL);
+	g_signal_connect(G_OBJECT(button), "clicked",
+			 G_CALLBACK(cancel_clicked_cb), NULL);
+	gtk_container_set_border_width(GTK_CONTAINER(main_dialog), 6);
+
+	/* table elements */
+	from_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(from_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(from_label), PANGO_ELLIPSIZE_MIDDLE);
+	target_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(target_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(target_label), PANGO_ELLIPSIZE_END);
+	gtk_label_set_text(GTK_LABEL(target_label), device_name ? device_name : bdaddrstr);
+	label2 = gtk_label_new(NULL);
+	text = g_markup_printf_escaped("<b>%s</b>", _("From:"));
+	gtk_label_set_markup(GTK_LABEL(label2), text);
+	g_free(text);
+	label3 = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(label3), 1, 0.5);
+	text = g_markup_printf_escaped("<b>%s</b>", _("To:"));
+	gtk_label_set_markup(GTK_LABEL(label3), text);
+	g_free(text);
 
-	label_filename = label;
+	/* table */
+	table = gtk_table_new(2, 2, FALSE);
+	gtk_table_set_col_spacings(GTK_TABLE(table), 4);
+	gtk_table_set_row_spacings(GTK_TABLE(table), 4);
+	gtk_table_attach_defaults(GTK_TABLE(table), from_label, 1, 2, 0, 1);
+	gtk_table_attach_defaults(GTK_TABLE(table), target_label, 1, 2, 1, 2);
+	gtk_table_attach(GTK_TABLE(table), label2, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), label3, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+
+	/* vbox elements */
+	label1 = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(label1), 0, 0.5);
+	text = g_markup_printf_escaped("<span weight=\"bold\" size=\"larger\">%s</span>",
+				       _("Sending files via bluetooth"));
+	gtk_label_set_markup(GTK_LABEL(label1), text);
+	g_free(text);
+	progress_bar = gtk_progress_bar_new();
+	gtk_progress_bar_set_ellipsize(GTK_PROGRESS_BAR(progress_bar),
+				       PANGO_ELLIPSIZE_END);
+	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
+				  _("Connecting..."));
+	operation_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(operation_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(operation_label), PANGO_ELLIPSIZE_END);
+
+	/* vbox */
+	vbox = gtk_vbox_new(FALSE, 0);
+	gtk_box_set_spacing(GTK_BOX(vbox), 6);
+	gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
+	gtk_box_pack_start_defaults(GTK_BOX(vbox), label1);
+	gtk_box_pack_start(GTK_BOX(vbox), table, TRUE, TRUE, 9);
+	gtk_box_pack_start_defaults(GTK_BOX(vbox), progress_bar);
+	gtk_box_pack_start(GTK_BOX(vbox), operation_label, TRUE, TRUE, 2);
+	vbox_parent = gtk_bin_get_child(GTK_BIN(main_dialog));
+	gtk_box_pack_start_defaults(GTK_BOX(vbox_parent), vbox);
+
+	/* show it */
+	gtk_widget_show_all(main_dialog);
+}
+
+static gboolean send_one(gpointer user_data)
+{
+	const gchar *fname;
+	gint ret;
+	gchar *bname, *dirname;
+	GError *err = NULL;
+	gchar *operation_text, *operation_markup, *progressbar_text;
+
+	if ((fname = g_slist_nth_data(file_list, current_file))) {
+		/* there's a file to send */
+		dbus_g_proxy_call(session_proxy, "SendFile", &err,
+				  G_TYPE_STRING, fname, G_TYPE_INVALID, G_TYPE_INVALID);
+
+		if (err != NULL) {
+			ret = handle_file_sending_error(_("Unable to read file"),
+							err->message);
+			if (ret != GTK_RESPONSE_CANCEL) {
+				g_idle_add((GSourceFunc) send_one, NULL);
+			}
+			g_error_free(err);
+			return FALSE;
+		}
 
-	progress = gtk_progress_bar_new();
-	gtk_box_pack_start(GTK_BOX(vbox), progress, FALSE, FALSE, 0);
+		if (!first_transfer_time) {
+			first_transfer_time = get_system_time();
+		}
+		bname = g_path_get_basename(fname);
+		dirname = g_path_get_dirname(fname);
 
-	label = gtk_label_new(NULL);
-	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
-	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 6);
+		operation_text = g_strdup_printf(_("Sending %s"), bname);
+		operation_markup = g_markup_printf_escaped("<i>%s</i>", operation_text);
+		progressbar_text = g_strdup_printf(_("Sending file: %d of %d"),
+						   current_file+1,
+						   file_count);
+		gtk_label_set_text(GTK_LABEL(from_label), dirname);
+		gtk_label_set_markup(GTK_LABEL(operation_label), operation_markup);
+		gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
+					  progressbar_text);
+
+		g_free(bname);
+		g_free(dirname);
+		g_free(operation_text);
+		g_free(operation_markup);
+		g_free(progressbar_text);
+	} else {
+		/* nothing left to send, time to quit */
+		gtk_main_quit();
+	}
+	return FALSE;
+}
 
-	label_status = label;
+static void session_connected_cb(DBusGProxy *proxy, gpointer user_data)
+{
+	/* OBEX connect has succeeded, so start sending */
+	g_idle_add((GSourceFunc) send_one, NULL);
+}
 
-	text = g_strdup_printf("<b>%s</b>", filename);
-	gtk_label_set_markup(GTK_LABEL(label_filename), text);
-	g_free(text);
+static void transfer_started_cb(DBusGProxy *proxy, const gchar *filename,
+				const gchar *local_path, guint64 byte_count, gpointer user_data)
+{
+	file_length = byte_count;
+}
 
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Connecting..."));
+static void transfer_cancelled_cb(DBusGProxy *proxy, gpointer user_data)
+{
+	gint ret;
+	gchar *error_text;
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, FALSE);
+	error_text = g_strdup_printf(_("An error occured while sending file '%s'"),
+				     (gchar *)g_slist_nth_data(file_list, current_file));
+	ret = handle_file_sending_error(error_text,
+					_("The remote device cancelled the transfer"));
 
-	g_signal_connect(G_OBJECT(dialog), "response",
-				G_CALLBACK(response_callback), NULL);
+	if (ret != GTK_RESPONSE_CANCEL)
+		g_idle_add((GSourceFunc) send_one, NULL);
 
-	gtk_widget_show_all(dialog);
+	g_free(error_text);
 }
 
-static void transfer_started(DBusGProxy *proxy, gchar *a, gchar *b,
-					guint64 size, gpointer user_data)
+static void transfer_progress_cb(DBusGProxy *proxy, guint64 bytes_transferred, gpointer user_data)
 {
-	gchar *text;
+	gdouble frac;
+	guint actual_bytes_sent;
+	gint elapsed_time;
+	gint transfer_rate;
+	gint time_remaining;
+	gint64 current_time;
+	gchar *str, *str2;
+	gchar *progressbar_text;
 
-	filesize = size;
+	/* update progress bar fraction */
+	actual_bytes_sent = bytes_sent + bytes_transferred;
+	frac = (gdouble)actual_bytes_sent / byte_count;
+	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress_bar), frac);
 
-	text = g_strdup_printf(_("Starting transfer of %jd bytes"), size);
-	gtk_label_set_markup(GTK_LABEL(label_status), text);
-	g_free(text);
+	/* update progress bar text (time remaining)
+	 * (only update once in a second) */
+	current_time = get_system_time();
+	elapsed_time = (current_time - first_transfer_time) / 1000000;
 
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0.0);
-}
+	if (last_update_time) {
+		if (current_time < last_update_time + 1000000)
+			return;
+		else
+			last_update_time = current_time;
+	} else {
+		last_update_time = current_time;
+	}
 
-static void transfer_progress(DBusGProxy *proxy,
-					guint64 bytes, gpointer user_data)
-{
-	gchar *text;
-	gdouble fraction;
+	if (!elapsed_time)
+		return;
 
-	text = g_strdup_printf(_("Transfered %jd of %jd bytes"),
-							bytes, filesize);
-	gtk_label_set_markup(GTK_LABEL(label_status), text);
-	g_free(text);
+	transfer_rate = actual_bytes_sent / elapsed_time;
+	if (transfer_rate == 0)
+		return;
 
-	fraction = (gdouble) bytes / (gdouble) filesize;
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), fraction);
-}
+	time_remaining = (byte_count - actual_bytes_sent) / transfer_rate;
 
-static void transfer_completed(DBusGProxy *proxy, gpointer user_data)
-{
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Completed"));
+	if (time_remaining >= 3600) {
+		str = g_strdup_printf(_("(%d:%02d:%d Remaining)"), 
+				      time_remaining / 3600,
+				      (time_remaining % 3600) / 60,
+				      (time_remaining % 3600) % 60);
+	} else {
+		str = g_strdup_printf(_("(%d:%02d Remaining)"), 
+				      time_remaining / 60,
+				      time_remaining % 60);
+	}
 
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 1.0);
+	/* Translators:
+	 * Sending file 1 of 3 */
+	str2 = g_strdup_printf(_("Sending file %d of %d"),
+			       current_file + 1,
+			       file_count);
+	progressbar_text = g_strconcat(str2, " ", str, NULL);
+	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
+				  progressbar_text);
+	g_free(str);
+	g_free(str2);
+	g_free(progressbar_text);
+}
 
-	dbus_g_proxy_call(proxy, "Disconnect", NULL, G_TYPE_INVALID,
-							G_TYPE_INVALID);
+static void transfer_completed_cb(DBusGProxy *proxy, gpointer user_data)
+{
+	current_file++;
+	bytes_sent += file_length;
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, TRUE);
+	g_idle_add((GSourceFunc) send_one, NULL);
 }
 
-static void session_connected(DBusGProxy *proxy, gpointer user_data)
+static void session_created_cb(DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data)
 {
-	gchar *filename = user_data;
 	GError *error = NULL;
+	const gchar *path = NULL;
 
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Connected"));
+	if (!dbus_g_proxy_end_call(proxy, call, &error,
+				   DBUS_TYPE_G_OBJECT_PATH, &path, G_TYPE_INVALID)) {
+		const gchar *error_name = NULL;
+		gint response_id;
+
+		if (error && error->code == DBUS_GERROR_REMOTE_EXCEPTION)
+			error_name = dbus_g_error_get_name(error);
+		if (error_name && !strcmp(error_name, "org.openobex.Error.ConnectionAttemptFailed")) {
+			response_id = show_connection_error_dialog(GTK_WINDOW(main_dialog), bdaddrstr, error->message);
+			if (response_id == DIALOG_RESPONSE_RETRY) {
+				/* Try to create Session again */
+				dbus_g_proxy_begin_call(manager_proxy,
+							"CreateBluetoothSession",
+							(DBusGProxyCallNotify) session_created_cb, NULL, NULL,
+							G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+							G_TYPE_BOOLEAN, FALSE, G_TYPE_INVALID);
+				goto out;
+			}
+		} else {
+			show_error_dialog(GTK_WINDOW(main_dialog), BUTTONS_OK,
+					  error->message, "");
+		}
 
-	dbus_g_proxy_call(proxy, "SendFile", &error,
-				G_TYPE_STRING, filename, G_TYPE_INVALID,
-							G_TYPE_INVALID);
-
-	if (error != NULL) {
-		g_printerr("Sending of file %s failed: %s\n", filename,
-							error->message);
-		g_error_free(error);
+		/* Failed, quit main loop */
 		gtk_main_quit();
+		goto out;
 	}
-}
 
-static void create_notify(DBusGProxy *proxy,
-				DBusGProxyCall *call, void *user_data)
-{
-	GError *error = NULL;
-	const gchar *path = NULL;
+	session_proxy = dbus_g_proxy_new_for_name(connection,
+						       "org.openobex",
+						       path,
+						       "org.openobex.Session");
 
-	if (dbus_g_proxy_end_call(proxy, call, &error,
-					DBUS_TYPE_G_OBJECT_PATH, &path,
-						G_TYPE_INVALID) == FALSE) {
-		gchar *text, *message;
-
-		if (error != NULL) {
-			message = g_strdup(error->message);
-			g_error_free(error);
-		} else
-			message = g_strdup(_("An unknown error occured"));
-
-		text = g_strdup_printf("<span foreground=\"red\">%s</span>",
-								message);
-		gtk_label_set_markup(GTK_LABEL(label_status), text);
-		g_free(text);
+	dbus_g_proxy_add_signal(session_proxy, "Connected",
+				G_TYPE_INVALID);
 
-		g_free(message);
+	dbus_g_proxy_connect_signal(session_proxy, "Connected",
+				    G_CALLBACK(session_connected_cb), NULL, NULL);
 
-		gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, TRUE);
-		return;
-	}
+	dbus_g_proxy_add_signal(session_proxy, "TransferStarted",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);
 
-	proxy = dbus_g_proxy_new_for_name(conn, "org.openobex",
-						path, "org.openobex.Session");
+	dbus_g_proxy_connect_signal(session_proxy, "TransferStarted",
+				    G_CALLBACK(transfer_started_cb), NULL, NULL);
 
-	dbus_g_proxy_add_signal(proxy, "Connected", G_TYPE_INVALID);
+	dbus_g_proxy_add_signal(session_proxy, "Cancelled",
+				G_TYPE_INVALID);
 
-	dbus_g_proxy_connect_signal(proxy, "Connected",
-				G_CALLBACK(session_connected), user_data, NULL);
+	dbus_g_proxy_connect_signal(session_proxy, "Cancelled",
+				    G_CALLBACK(transfer_cancelled_cb), NULL, NULL);
 
-	dbus_g_proxy_add_signal(proxy, "TransferStarted", G_TYPE_STRING,
-				G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);
+	dbus_g_proxy_add_signal(session_proxy, "ErrorOccurred",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferStarted",
-				G_CALLBACK(transfer_started), NULL, NULL);
+	dbus_g_proxy_connect_signal(session_proxy, "ErrorOccurred",
+				    G_CALLBACK(error_occurred_cb), NULL, NULL);
 
-	dbus_g_proxy_add_signal(proxy, "TransferProgress",
-						G_TYPE_UINT64, G_TYPE_INVALID);
+	dbus_g_proxy_add_signal(session_proxy, "TransferProgress",
+				G_TYPE_UINT64, G_TYPE_INVALID);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferProgress",
-				G_CALLBACK(transfer_progress), NULL, NULL);
+	dbus_g_proxy_connect_signal(session_proxy, "TransferProgress",
+				    G_CALLBACK(transfer_progress_cb), NULL, NULL);
 
-	dbus_g_proxy_add_signal(proxy, "TransferCompleted", G_TYPE_INVALID);
+	dbus_g_proxy_add_signal(session_proxy, "TransferCompleted", 
+				G_TYPE_INVALID);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferCompleted",
-				G_CALLBACK(transfer_completed), NULL, NULL);
+	dbus_g_proxy_connect_signal(session_proxy, "TransferCompleted",
+				    G_CALLBACK(transfer_completed_cb), NULL, NULL);
 
-	dbus_g_proxy_call(proxy, "Connect", NULL, G_TYPE_INVALID,
-							G_TYPE_INVALID);
+	dbus_g_proxy_call(session_proxy, "Connect", &error, G_TYPE_INVALID,
+			  G_TYPE_INVALID);
+
+out:
+	if (error)
+		g_error_free(error);
+}
+
+static void error_occurred_cb(DBusGProxy *proxy, const gchar *error_name, const gchar *error_message, gpointer user_data)
+{
+	gint ret;
+	gboolean link_error = FALSE;
+	gchar *error_text;
+	const gchar *reason;
+
+	g_message("ErrorOccurred");
+	g_message("Error name: %s", error_name);
+	g_message("Error message: %s", error_message);
+
+	error_text = g_strdup_printf(_("An error occured while sending file '%s'"),
+				     (gchar *)g_slist_nth_data(file_list, current_file));
+	if (!strcmp(error_name, "org.openobex.Error.LinkError")) {
+		/* Connection to remote device was lost */
+		g_object_unref(G_OBJECT(session_proxy));
+		reason = _("Connection to remote device was lost");
+		link_error = TRUE;
+	} else if (!strcmp(error_name, "org.openobex.Error.Forbidden")) {
+		reason = _("Remote device rejected file");
+	} else {
+		reason = _("Unknown error");
+	}
+	ret = handle_file_sending_error(error_text, reason);
+
+	if (ret != GTK_RESPONSE_CANCEL) {
+		if (link_error) {
+			/* Need to reestablish connection */
+			dbus_g_proxy_begin_call(manager_proxy, "CreateBluetoothSession",
+						(DBusGProxyCallNotify) session_created_cb, NULL, NULL,
+						G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+						G_TYPE_INVALID);
+		} else {
+			g_idle_add((GSourceFunc) send_one, NULL);
+		}
+	}
+
+
+	g_free(error_text);
+}
+
+static void free_mem(void)
+{
+	if (files_to_send)
+		g_strfreev(files_to_send);
+	g_free(bdaddrstr);
+
+	if (main_dialog)
+		gtk_widget_destroy (main_dialog);
+	if (G_IS_OBJECT(manager_proxy))
+		g_object_unref(G_OBJECT(manager_proxy));
+	if (G_IS_OBJECT(session_proxy))
+		g_object_unref (G_OBJECT(session_proxy));
+	g_slist_free(file_list);
 }
 
 static void name_owner_changed(DBusGProxy *proxy, const char *name,
@@ -295,98 +651,128 @@ static void name_owner_changed(DBusGProx
 		gtk_main_quit();
 }
 
-static gchar *option_device = NULL;
-
-static GOptionEntry options[] = {
-	{ "device", 0, 0, G_OPTION_ARG_STRING, &option_device,
-				N_("Remote device to use"), "ADDRESS" },
-	{ NULL },
-};
-
 int main(int argc, char *argv[])
 {
-	DBusGProxy *proxy;
+	GOptionContext *option_context;
 	GError *error = NULL;
-	gchar *filename, *address;
+	guint i;
 
+	/* initialize gettext */
 	bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
 	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
 	textdomain(GETTEXT_PACKAGE);
 
-	error = NULL;
+	/* parse command line arguments */
+	option_context = g_option_context_new("<file list>");
+	g_option_context_add_main_entries(option_context, options, GETTEXT_PACKAGE);
+	g_option_context_add_group(option_context, gtk_get_option_group(TRUE));
+	g_option_context_parse(option_context, &argc, &argv, &error);
+	g_option_context_free(option_context);
+	if (error) {
+		printf("Usage:\n");
+		printf("  %s [--dest=BDADDR] <file list>\n", g_get_prgname());
+		g_error_free(error);
+		free_mem();
+		return 0;
+	}
 
-	if (gtk_init_with_args(&argc, &argv, "[FILE...]",
-				options, GETTEXT_PACKAGE, &error) == FALSE) {
-		if (error != NULL) {
-			g_printerr("%s\n", error->message);
-			g_error_free(error);
-		} else
-			g_printerr("An unknown error occurred\n");
-
-		gtk_exit(1);
-	}
-
-	gtk_window_set_default_icon_name("bluetooth");
-
-	if (argc < 2) {
-		filename = open_file_dialog();
-		if (filename == NULL)
-			gtk_exit(1);
-	} else
-		filename = g_strdup(argv[1]);
-
-	if (option_device == NULL) {
-		address = browse_device_dialog();
-		if (address == NULL) {
-			g_free(filename);
-			gtk_exit(1);
-		}
-	} else
-		address = g_strdup(option_device);
+	gtk_init(&argc, &argv);
 
-	conn = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
-	if (conn == NULL) {
-		if (error != NULL) {
-			g_printerr("Connecting to session bus failed: %s\n",
-							error->message);
-			g_error_free(error);
-		} else
-			g_print("An unknown error occured\n");
+	/* init DBus connection */
+	connection = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
+	if (!connection) {
+		show_error_dialog(NULL, GTK_BUTTONS_OK,
+				  _("Please verify that D-Bus is correctly installed and setup."), error->message);
+		g_error_free(error);
+		return 1;
+	}
 
-		gtk_exit(1);
+	main_dialog = NULL;
+	from_label = NULL;
+	operation_label = NULL;
+	progress_bar = NULL;
+
+	/* get a list of files to send from command-line */
+	if (files_to_send)
+		file_count = g_strv_length(files_to_send);
+	else
+		file_count = 0;
+	for (i = 0; i < file_count; i++) {
+		file_list = g_slist_append(file_list,
+						g_strdup(*(files_to_send + i)));
+	}
+	/* show file chooser if no files were specified */
+	if (!file_count) {
+		file_list = select_files_dialog();
+		file_count = g_slist_length(file_list);
+	}
+	/* Check if there are some files to send */
+	if (!file_count) {
+		free_mem();
+		return 0;
+	}
+	/* Check for non regular and non existand files and
+	 * determine total bytes to send */
+	for (i = 0; i < file_count; i++) {
+		GSList *item;
+		gchar *path, *filename;
+		gint64 size;
+
+		item = g_slist_nth(file_list, i);
+		path = (gchar *) item->data;
+		filename = normalise_filename(path, &size);
+		if (filename == NULL) {
+			g_free(path);
+			file_list = g_slist_remove_link(file_list, item);
+			continue;
+		}
+		g_free(item->data);
+		item->data = filename;
+		byte_count += size;
 	}
 
-	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING_UINT64,
-				G_TYPE_NONE, G_TYPE_STRING,
-				G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);
+	/* determine Bluetooth device to send to */
+	if (!bdaddrstr) {
+		bdaddrstr = browse_device_dialog();
+		if (!bdaddrstr) {
+			free_mem();
+			return 0;
+		}
+	}
 
+	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING_UINT64,
+					  G_TYPE_NONE, G_TYPE_STRING,
+					  G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);
+	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING,
+					  G_TYPE_NONE, G_TYPE_STRING,
+					  G_TYPE_STRING, G_TYPE_INVALID);
 	dbus_g_object_register_marshaller(marshal_VOID__UINT64,
-				G_TYPE_NONE, G_TYPE_UINT64, G_TYPE_INVALID);
-
-	create_window(filename);
-
-	proxy = dbus_g_proxy_new_for_name(conn, "org.openobex",
-				"/org/openobex", "org.openobex.Manager");
-
-	dbus_g_proxy_add_signal(proxy, "NameOwnerChanged",
-		G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
+					  G_TYPE_NONE, G_TYPE_UINT64, G_TYPE_INVALID);
 
-	dbus_g_proxy_connect_signal(proxy, "NameOwnerChanged",
-				G_CALLBACK(name_owner_changed), NULL, NULL);
+	/* init UI */
+	ui_init();
 
-	dbus_g_proxy_begin_call(proxy, "CreateBluetoothSession",
-				create_notify, filename, NULL,
-				G_TYPE_STRING, address, G_TYPE_STRING, "opp",
+	/* init DBus proxy, create Session */
+	manager_proxy = dbus_g_proxy_new_for_name(connection,
+						       "org.openobex",
+						       "/org/openobex",
+						       "org.openobex.Manager");
+	dbus_g_proxy_add_signal(manager_proxy, "NameOwnerChanged",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(manager_proxy, "NameOwnerChanged",
+				    G_CALLBACK(name_owner_changed), NULL, NULL);
+
+	dbus_g_proxy_begin_call(manager_proxy, "CreateBluetoothSession",
+				(DBusGProxyCallNotify) session_created_cb, NULL, NULL,
+				G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
 				G_TYPE_INVALID);
 
+	/* Go into main loop */
 	gtk_main();
 
-	g_object_unref(proxy);
-
-	dbus_g_connection_unref(conn);
-
-	g_free(address);
-	g_free(filename);
+	/* done sending, free memory */
+	free_mem();
 
 	return 0;
 }
+
--- /dev/null	2008-02-19 13:52:26.315003021 +0000
+++ sendto/utils.c	2008-02-06 13:05:42.000000000 +0000
@@ -0,0 +1,112 @@
+/*
+ * bluetooth-sendto
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-2007  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ *  Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+
+#include "bluetooth-device-selection.h"
+
+static void selected_device_changed_cb(BluetoothDeviceSelection *selector,
+				       gchar *address, gpointer user_data)
+{
+	GtkWidget *dialog = user_data;
+
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
+					  GTK_RESPONSE_ACCEPT, address != NULL);
+}
+
+gchar *browse_device_dialog(void)
+{
+	GtkWidget *dialog;
+	GtkWidget *selector;
+	gchar *address = NULL;
+
+	dialog = gtk_dialog_new_with_buttons(_("Select Destination Device"),
+					     NULL, GTK_DIALOG_NO_SEPARATOR,
+					     GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+					     GTK_STOCK_CONNECT, GTK_RESPONSE_ACCEPT, NULL);
+
+	gtk_window_set_icon_name(GTK_WINDOW(dialog), "stock_bluetooth");
+
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
+					  GTK_RESPONSE_ACCEPT, FALSE);
+
+	gtk_window_set_default_size(GTK_WINDOW(dialog), 450, 300);
+
+	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
+
+	selector = bluetooth_device_selection_new(_("Select destination device"));
+	bluetooth_device_selection_start_discovery(
+	    BLUETOOTH_DEVICE_SELECTION(selector));
+
+	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), selector);
+
+	g_signal_connect(selector, "selected-device-changed",
+			 G_CALLBACK(selected_device_changed_cb), dialog);
+
+	gtk_widget_show_all(selector);
+
+	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
+		g_object_get(selector, "device-selected", &address, NULL);
+
+	gtk_widget_destroy(dialog);
+
+	return address;
+}
+
+GSList *select_files_dialog(void)
+{
+	GtkWidget *dialog;
+	GSList *file_list;
+
+	file_list = NULL;
+	dialog = gtk_file_chooser_dialog_new(_("Choose files to send"), NULL,
+					     GTK_FILE_CHOOSER_ACTION_OPEN,
+					     GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+					     _("Send"), GTK_RESPONSE_ACCEPT,
+					     NULL);
+	gtk_window_set_icon_name(GTK_WINDOW(dialog), "stock_bluetooth");
+	gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
+
+	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+		file_list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
+	}
+	gtk_widget_destroy(dialog);
+
+	return file_list;
+}
+
--- /dev/null	2008-02-19 13:52:26.315003021 +0000
+++ sendto/utils.h	2008-02-06 13:05:42.000000000 +0000
@@ -0,0 +1,30 @@
+/*
+ * bluetooth-sendto
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2005-2007  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ *  Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <glib.h>
+
+gchar *browse_device_dialog(void);
+GSList *select_files_dialog(void);
+

[-- Attachment #3: main.c --]
[-- Type: text/x-csrc, Size: 22682 bytes --]

/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2005-2008  Marcel Holtmann <marcel@holtmann.org>
 *  Copyright (C) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
 *  Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
 *
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif


#include <glib/gi18n.h>
#include <gtk/gtk.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>

#include <dbus/dbus-glib.h>
#include <glib.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <gtk/gtk.h>

#include "dbus-glue.h"
#include "marshal.h"
#include "utils.h"

/* DBus */
static DBusGConnection *connection = NULL;
static DBusGProxy *manager_proxy = NULL;
static DBusGProxy *session_proxy = NULL;
/* GTK */
static GtkWidget *main_dialog, *from_label, *operation_label, *progress_bar;
/* sending related */
static gsize file_length = 0;
static guint file_count = 0;
static guint current_file = 0;
static GSList *file_list = NULL;
static guint byte_count = 0;
static guint bytes_sent = 0;
static gint64 first_transfer_time = 0;
static gint64 last_update_time = 0;

typedef enum {
	BUTTONS_OK,
	BUTTONS_RETRY_CANCEL,
	BUTTONS_RETRY_SKIP_CANCEL
} ButtonSet;

/* Command line options */
static gchar **files_to_send = NULL;
static gchar *bdaddrstr = NULL;
static gchar *device_name = NULL;

#define DIALOG_RESPONSE_SKIP 1
#define DIALOG_RESPONSE_RETRY 2

static void error_occurred_cb(DBusGProxy *proxy, const gchar *error_name,
			      const gchar *error_message, gpointer user_data);

static const GOptionEntry options[] = {
	{"dest", 'd', 0, G_OPTION_ARG_STRING, &bdaddrstr, 
		"Bluetooth address of destination device", "BDADDR"},
	{G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &files_to_send},
	{NULL}
};

static gint64 get_system_time(void)
{
	struct timeval tmp;

	gettimeofday(&tmp, NULL);
	return (gint64)tmp.tv_usec + (gint64)tmp.tv_sec * G_GINT64_CONSTANT(1000000);
}

static gboolean is_palm_device(const gchar *bdaddr)
{
	return (g_str_has_prefix(bdaddr, "00:04:6B")
		|| g_str_has_prefix(bdaddr, "00:07:E0")
		|| g_str_has_prefix(bdaddr, "00:0E:20"));
}

/* Return the local absolute filename for the file, and its size
 * if the file exists and is stat()'able */
static gchar *normalise_filename(const gchar *filename, gint64 *size)
{
	char *ret;
	struct stat file_stat;

	g_return_val_if_fail(filename != NULL, NULL);
	g_return_val_if_fail(size != NULL, NULL);

	if (g_str_has_prefix(filename, "file://")) {
		ret = g_filename_from_uri(filename, NULL, NULL);
		if (ret == NULL)
			return NULL;
	} else if (filename[0] == '/') {
		ret = g_strdup(filename);
	} else {
		gchar *curdir;

		curdir = g_get_current_dir();
		ret = g_build_filename(curdir, filename, NULL);
		g_free(curdir);
	}

	if (!g_file_test(ret, G_FILE_TEST_IS_REGULAR)) {
		g_free(ret);
		return NULL;
	}

	if (!g_stat(ret, &file_stat)) {
		*size = file_stat.st_size;
	} else {
		g_free(ret);
		return NULL;
	}

	return ret;
}

static gchar *get_device_name(void)
{
	DBusGConnection *connection;
	DBusGProxy *manager;
	GError *error = NULL;
	gchar *name, **adapters;
	guint i;

	name = NULL;

	connection = dbus_g_bus_get(DBUS_BUS_SYSTEM, NULL);
	if (connection == NULL)
		return NULL;

	manager = dbus_g_proxy_new_for_name(connection, "org.bluez",
					    "/org/bluez", "org.bluez.Manager");
	if (!manager) {
		dbus_g_connection_unref(connection);
		return NULL;
	}

	if (!manager_list_adapters(manager, &adapters, &error)) {
		g_object_unref(manager);
		dbus_g_connection_unref(connection);
		return NULL;
	}

	for (i = 0; adapters[i] != NULL; i++) {
		DBusGProxy *adapter;

		adapter = dbus_g_proxy_new_for_name(connection, "org.bluez",
						    adapters[i], "org.bluez.Adapter");
		if (dbus_g_proxy_call(adapter, "GetRemoteName", NULL,
				      G_TYPE_STRING, bdaddrstr, G_TYPE_INVALID,
				      G_TYPE_STRING, &name, G_TYPE_INVALID)) {
			if (name != NULL && name[0] != '\0') {
				g_object_unref(adapter);
				break;
			}
		}
		g_object_unref(adapter);
	}

	g_strfreev(adapters);
	g_object_unref(manager);
	dbus_g_connection_unref(connection);

	return name;
}

static gint show_error_dialog(GtkWindow *parent, ButtonSet buttons,
			      const gchar *primary_text, const gchar *secondary_text)
{
	GtkWidget *dialog;
	gchar *primary_text_markup;
	gint ret;

	primary_text_markup = g_strdup_printf("<span weight=\"bold\" size=\"larger\">%s</span>", 
					      primary_text);
	dialog = gtk_message_dialog_new_with_markup(parent, 0, GTK_MESSAGE_ERROR, 
						    buttons==BUTTONS_OK? GTK_BUTTONS_OK: GTK_BUTTONS_CANCEL, 
						    primary_text_markup);
	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
						 secondary_text);
	if (buttons == BUTTONS_RETRY_SKIP_CANCEL) {
		gtk_dialog_add_button(GTK_DIALOG(dialog), _("Skip"),
				      DIALOG_RESPONSE_SKIP);
	}
	if (buttons != BUTTONS_OK) {
		gtk_dialog_add_button(GTK_DIALOG(dialog), _("Retry"),
				      DIALOG_RESPONSE_RETRY);
	}
	ret = gtk_dialog_run(GTK_DIALOG(dialog));

	gtk_widget_destroy(dialog);
	g_free(primary_text_markup);
	return ret;
}

static gint show_connection_error_dialog(GtkWindow *parent, const gchar *bdaddr,
					 const gchar *primary_text)
{
	if (!is_palm_device(bdaddrstr)) {
		return show_error_dialog(parent,
					 BUTTONS_RETRY_CANCEL,
					 primary_text,
					 _("Make sure that remote device is on and that it accepts Bluetooth connections."));
	} else {
		return show_error_dialog(parent,
					 BUTTONS_RETRY_CANCEL,
					 primary_text,
					 _("Make sure \"Beam receive\" is enabled in the Power preferences, and Bluetooth is enabled, on your Palm device."));
	}
}

static gint handle_file_sending_error(const gchar *error_text,
				      const gchar *error_secondary_text)
{
	ButtonSet buttons;
	gint response_id;

	if (file_count > 1 && current_file < file_count - 1)
		buttons = BUTTONS_RETRY_SKIP_CANCEL;
	else
		buttons = BUTTONS_RETRY_CANCEL;

	response_id = show_error_dialog(GTK_WINDOW(main_dialog), buttons, 
					error_text, error_secondary_text);

	switch (response_id) {
	case DIALOG_RESPONSE_SKIP:
		current_file++;
		bytes_sent += file_length;
	case DIALOG_RESPONSE_RETRY:
		return TRUE;
	case GTK_RESPONSE_CANCEL:
	default:
		gtk_main_quit();
		break;
	}
	return response_id;
}

static void cancel_clicked_cb(GtkWidget *button, gpointer user_data)
{
	gtk_main_quit();
}

static void ui_init(void)
{
	GtkWidget *vbox, *vbox_parent, *table;
	GtkWidget *label1, *label2, *label3, *target_label;
	GtkWidget *button;
	gchar *text;

	/* initialize UI */

	device_name = get_device_name();

	/* main dialog */
	main_dialog = gtk_dialog_new();
	gtk_dialog_set_has_separator(GTK_DIALOG(main_dialog), FALSE);
	gtk_window_set_title(GTK_WINDOW(main_dialog),
			     _("Bluetooth file transfer"));
	gtk_window_set_icon_name(GTK_WINDOW(main_dialog), "stock_bluetooth");
	gtk_window_set_type_hint(GTK_WINDOW(main_dialog),
				 GDK_WINDOW_TYPE_HINT_NORMAL);
	gtk_window_set_position(GTK_WINDOW(main_dialog), GTK_WIN_POS_CENTER);
	gtk_window_set_resizable(GTK_WINDOW(main_dialog), FALSE);
	gtk_widget_set(main_dialog, "width-request", 400, NULL);
	button = gtk_dialog_add_button(GTK_DIALOG(main_dialog),
				       GTK_STOCK_CANCEL,
				       GTK_RESPONSE_CANCEL);
	g_signal_connect(G_OBJECT(button), "clicked",
			 G_CALLBACK(cancel_clicked_cb), NULL);
	gtk_container_set_border_width(GTK_CONTAINER(main_dialog), 6);

	/* table elements */
	from_label = gtk_label_new(NULL);
	gtk_misc_set_alignment(GTK_MISC(from_label), 0, 0.5);
	gtk_label_set_ellipsize(GTK_LABEL(from_label), PANGO_ELLIPSIZE_MIDDLE);
	target_label = gtk_label_new(NULL);
	gtk_misc_set_alignment(GTK_MISC(target_label), 0, 0.5);
	gtk_label_set_ellipsize(GTK_LABEL(target_label), PANGO_ELLIPSIZE_END);
	gtk_label_set_text(GTK_LABEL(target_label), device_name ? device_name : bdaddrstr);
	label2 = gtk_label_new(NULL);
	text = g_markup_printf_escaped("<b>%s</b>", _("From:"));
	gtk_label_set_markup(GTK_LABEL(label2), text);
	g_free(text);
	label3 = gtk_label_new(NULL);
	gtk_misc_set_alignment(GTK_MISC(label3), 1, 0.5);
	text = g_markup_printf_escaped("<b>%s</b>", _("To:"));
	gtk_label_set_markup(GTK_LABEL(label3), text);
	g_free(text);

	/* table */
	table = gtk_table_new(2, 2, FALSE);
	gtk_table_set_col_spacings(GTK_TABLE(table), 4);
	gtk_table_set_row_spacings(GTK_TABLE(table), 4);
	gtk_table_attach_defaults(GTK_TABLE(table), from_label, 1, 2, 0, 1);
	gtk_table_attach_defaults(GTK_TABLE(table), target_label, 1, 2, 1, 2);
	gtk_table_attach(GTK_TABLE(table), label2, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
	gtk_table_attach(GTK_TABLE(table), label3, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);

	/* vbox elements */
	label1 = gtk_label_new(NULL);
	gtk_misc_set_alignment(GTK_MISC(label1), 0, 0.5);
	text = g_markup_printf_escaped("<span weight=\"bold\" size=\"larger\">%s</span>",
				       _("Sending files via bluetooth"));
	gtk_label_set_markup(GTK_LABEL(label1), text);
	g_free(text);
	progress_bar = gtk_progress_bar_new();
	gtk_progress_bar_set_ellipsize(GTK_PROGRESS_BAR(progress_bar),
				       PANGO_ELLIPSIZE_END);
	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
				  _("Connecting..."));
	operation_label = gtk_label_new(NULL);
	gtk_misc_set_alignment(GTK_MISC(operation_label), 0, 0.5);
	gtk_label_set_ellipsize(GTK_LABEL(operation_label), PANGO_ELLIPSIZE_END);

	/* vbox */
	vbox = gtk_vbox_new(FALSE, 0);
	gtk_box_set_spacing(GTK_BOX(vbox), 6);
	gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
	gtk_box_pack_start_defaults(GTK_BOX(vbox), label1);
	gtk_box_pack_start(GTK_BOX(vbox), table, TRUE, TRUE, 9);
	gtk_box_pack_start_defaults(GTK_BOX(vbox), progress_bar);
	gtk_box_pack_start(GTK_BOX(vbox), operation_label, TRUE, TRUE, 2);
	vbox_parent = gtk_bin_get_child(GTK_BIN(main_dialog));
	gtk_box_pack_start_defaults(GTK_BOX(vbox_parent), vbox);

	/* show it */
	gtk_widget_show_all(main_dialog);
}

static gboolean send_one(gpointer user_data)
{
	const gchar *fname;
	gint ret;
	gchar *bname, *dirname;
	GError *err = NULL;
	gchar *operation_text, *operation_markup, *progressbar_text;

	if ((fname = g_slist_nth_data(file_list, current_file))) {
		/* there's a file to send */
		dbus_g_proxy_call(session_proxy, "SendFile", &err,
				  G_TYPE_STRING, fname, G_TYPE_INVALID, G_TYPE_INVALID);

		if (err != NULL) {
			ret = handle_file_sending_error(_("Unable to read file"),
							err->message);
			if (ret != GTK_RESPONSE_CANCEL) {
				g_idle_add((GSourceFunc) send_one, NULL);
			}
			g_error_free(err);
			return FALSE;
		}

		if (!first_transfer_time) {
			first_transfer_time = get_system_time();
		}
		bname = g_path_get_basename(fname);
		dirname = g_path_get_dirname(fname);

		operation_text = g_strdup_printf(_("Sending %s"), bname);
		operation_markup = g_markup_printf_escaped("<i>%s</i>", operation_text);
		progressbar_text = g_strdup_printf(_("Sending file: %d of %d"),
						   current_file+1,
						   file_count);
		gtk_label_set_text(GTK_LABEL(from_label), dirname);
		gtk_label_set_markup(GTK_LABEL(operation_label), operation_markup);
		gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
					  progressbar_text);

		g_free(bname);
		g_free(dirname);
		g_free(operation_text);
		g_free(operation_markup);
		g_free(progressbar_text);
	} else {
		/* nothing left to send, time to quit */
		gtk_main_quit();
	}
	return FALSE;
}

static void session_connected_cb(DBusGProxy *proxy, gpointer user_data)
{
	/* OBEX connect has succeeded, so start sending */
	g_idle_add((GSourceFunc) send_one, NULL);
}

static void transfer_started_cb(DBusGProxy *proxy, const gchar *filename,
				const gchar *local_path, guint64 byte_count, gpointer user_data)
{
	file_length = byte_count;
}

static void transfer_cancelled_cb(DBusGProxy *proxy, gpointer user_data)
{
	gint ret;
	gchar *error_text;

	error_text = g_strdup_printf(_("An error occured while sending file '%s'"),
				     (gchar *)g_slist_nth_data(file_list, current_file));
	ret = handle_file_sending_error(error_text,
					_("The remote device cancelled the transfer"));

	if (ret != GTK_RESPONSE_CANCEL)
		g_idle_add((GSourceFunc) send_one, NULL);

	g_free(error_text);
}

static void transfer_progress_cb(DBusGProxy *proxy, guint64 bytes_transferred, gpointer user_data)
{
	gdouble frac;
	guint actual_bytes_sent;
	gint elapsed_time;
	gint transfer_rate;
	gint time_remaining;
	gint64 current_time;
	gchar *str, *str2;
	gchar *progressbar_text;

	/* update progress bar fraction */
	actual_bytes_sent = bytes_sent + bytes_transferred;
	frac = (gdouble)actual_bytes_sent / byte_count;
	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress_bar), frac);

	/* update progress bar text (time remaining)
	 * (only update once in a second) */
	current_time = get_system_time();
	elapsed_time = (current_time - first_transfer_time) / 1000000;

	if (last_update_time) {
		if (current_time < last_update_time + 1000000)
			return;
		else
			last_update_time = current_time;
	} else {
		last_update_time = current_time;
	}

	if (!elapsed_time)
		return;

	transfer_rate = actual_bytes_sent / elapsed_time;
	if (transfer_rate == 0)
		return;

	time_remaining = (byte_count - actual_bytes_sent) / transfer_rate;

	if (time_remaining >= 3600) {
		str = g_strdup_printf(_("(%d:%02d:%d Remaining)"), 
				      time_remaining / 3600,
				      (time_remaining % 3600) / 60,
				      (time_remaining % 3600) % 60);
	} else {
		str = g_strdup_printf(_("(%d:%02d Remaining)"), 
				      time_remaining / 60,
				      time_remaining % 60);
	}

	/* Translators:
	 * Sending file 1 of 3 */
	str2 = g_strdup_printf(_("Sending file %d of %d"),
			       current_file + 1,
			       file_count);
	progressbar_text = g_strconcat(str2, " ", str, NULL);
	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
				  progressbar_text);
	g_free(str);
	g_free(str2);
	g_free(progressbar_text);
}

static void transfer_completed_cb(DBusGProxy *proxy, gpointer user_data)
{
	current_file++;
	bytes_sent += file_length;

	g_idle_add((GSourceFunc) send_one, NULL);
}

static void session_created_cb(DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data)
{
	GError *error = NULL;
	const gchar *path = NULL;

	if (!dbus_g_proxy_end_call(proxy, call, &error,
				   DBUS_TYPE_G_OBJECT_PATH, &path, G_TYPE_INVALID)) {
		const gchar *error_name = NULL;
		gint response_id;

		if (error && error->code == DBUS_GERROR_REMOTE_EXCEPTION)
			error_name = dbus_g_error_get_name(error);
		if (error_name && !strcmp(error_name, "org.openobex.Error.ConnectionAttemptFailed")) {
			response_id = show_connection_error_dialog(GTK_WINDOW(main_dialog), bdaddrstr, error->message);
			if (response_id == DIALOG_RESPONSE_RETRY) {
				/* Try to create Session again */
				dbus_g_proxy_begin_call(manager_proxy,
							"CreateBluetoothSession",
							(DBusGProxyCallNotify) session_created_cb, NULL, NULL,
							G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
							G_TYPE_BOOLEAN, FALSE, G_TYPE_INVALID);
				goto out;
			}
		} else {
			show_error_dialog(GTK_WINDOW(main_dialog), BUTTONS_OK,
					  error->message, "");
		}

		/* Failed, quit main loop */
		gtk_main_quit();
		goto out;
	}

	session_proxy = dbus_g_proxy_new_for_name(connection,
						       "org.openobex",
						       path,
						       "org.openobex.Session");

	dbus_g_proxy_add_signal(session_proxy, "Connected",
				G_TYPE_INVALID);

	dbus_g_proxy_connect_signal(session_proxy, "Connected",
				    G_CALLBACK(session_connected_cb), NULL, NULL);

	dbus_g_proxy_add_signal(session_proxy, "TransferStarted",
				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);

	dbus_g_proxy_connect_signal(session_proxy, "TransferStarted",
				    G_CALLBACK(transfer_started_cb), NULL, NULL);

	dbus_g_proxy_add_signal(session_proxy, "Cancelled",
				G_TYPE_INVALID);

	dbus_g_proxy_connect_signal(session_proxy, "Cancelled",
				    G_CALLBACK(transfer_cancelled_cb), NULL, NULL);

	dbus_g_proxy_add_signal(session_proxy, "ErrorOccurred",
				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);

	dbus_g_proxy_connect_signal(session_proxy, "ErrorOccurred",
				    G_CALLBACK(error_occurred_cb), NULL, NULL);

	dbus_g_proxy_add_signal(session_proxy, "TransferProgress",
				G_TYPE_UINT64, G_TYPE_INVALID);

	dbus_g_proxy_connect_signal(session_proxy, "TransferProgress",
				    G_CALLBACK(transfer_progress_cb), NULL, NULL);

	dbus_g_proxy_add_signal(session_proxy, "TransferCompleted", 
				G_TYPE_INVALID);

	dbus_g_proxy_connect_signal(session_proxy, "TransferCompleted",
				    G_CALLBACK(transfer_completed_cb), NULL, NULL);

	dbus_g_proxy_call(session_proxy, "Connect", &error, G_TYPE_INVALID,
			  G_TYPE_INVALID);

out:
	if (error)
		g_error_free(error);
}

static void error_occurred_cb(DBusGProxy *proxy, const gchar *error_name, const gchar *error_message, gpointer user_data)
{
	gint ret;
	gboolean link_error = FALSE;
	gchar *error_text;
	const gchar *reason;

	g_message("ErrorOccurred");
	g_message("Error name: %s", error_name);
	g_message("Error message: %s", error_message);

	error_text = g_strdup_printf(_("An error occured while sending file '%s'"),
				     (gchar *)g_slist_nth_data(file_list, current_file));
	if (!strcmp(error_name, "org.openobex.Error.LinkError")) {
		/* Connection to remote device was lost */
		g_object_unref(G_OBJECT(session_proxy));
		reason = _("Connection to remote device was lost");
		link_error = TRUE;
	} else if (!strcmp(error_name, "org.openobex.Error.Forbidden")) {
		reason = _("Remote device rejected file");
	} else {
		reason = _("Unknown error");
	}
	ret = handle_file_sending_error(error_text, reason);

	if (ret != GTK_RESPONSE_CANCEL) {
		if (link_error) {
			/* Need to reestablish connection */
			dbus_g_proxy_begin_call(manager_proxy, "CreateBluetoothSession",
						(DBusGProxyCallNotify) session_created_cb, NULL, NULL,
						G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
						G_TYPE_INVALID);
		} else {
			g_idle_add((GSourceFunc) send_one, NULL);
		}
	}


	g_free(error_text);
}

static void free_mem(void)
{
	if (files_to_send)
		g_strfreev(files_to_send);
	g_free(bdaddrstr);

	if (main_dialog)
		gtk_widget_destroy (main_dialog);
	if (G_IS_OBJECT(manager_proxy))
		g_object_unref(G_OBJECT(manager_proxy));
	if (G_IS_OBJECT(session_proxy))
		g_object_unref (G_OBJECT(session_proxy));
	g_slist_free(file_list);
}

static void name_owner_changed(DBusGProxy *proxy, const char *name,
			const char *prev, const char *new, gpointer user_data)
{
	if (g_str_equal(name, "org.openobex") == TRUE && *new == '\0')
		gtk_main_quit();
}

int main(int argc, char *argv[])
{
	GOptionContext *option_context;
	GError *error = NULL;
	guint i;

	/* initialize gettext */
	bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
	textdomain(GETTEXT_PACKAGE);

	/* parse command line arguments */
	option_context = g_option_context_new("<file list>");
	g_option_context_add_main_entries(option_context, options, GETTEXT_PACKAGE);
	g_option_context_add_group(option_context, gtk_get_option_group(TRUE));
	g_option_context_parse(option_context, &argc, &argv, &error);
	g_option_context_free(option_context);
	if (error) {
		printf("Usage:\n");
		printf("  %s [--dest=BDADDR] <file list>\n", g_get_prgname());
		g_error_free(error);
		free_mem();
		return 0;
	}

	gtk_init(&argc, &argv);

	/* init DBus connection */
	connection = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
	if (!connection) {
		show_error_dialog(NULL, GTK_BUTTONS_OK,
				  _("Please verify that D-Bus is correctly installed and setup."), error->message);
		g_error_free(error);
		return 1;
	}

	main_dialog = NULL;
	from_label = NULL;
	operation_label = NULL;
	progress_bar = NULL;

	/* get a list of files to send from command-line */
	if (files_to_send)
		file_count = g_strv_length(files_to_send);
	else
		file_count = 0;
	for (i = 0; i < file_count; i++) {
		file_list = g_slist_append(file_list,
						g_strdup(*(files_to_send + i)));
	}
	/* show file chooser if no files were specified */
	if (!file_count) {
		file_list = select_files_dialog();
		file_count = g_slist_length(file_list);
	}
	/* Check if there are some files to send */
	if (!file_count) {
		free_mem();
		return 0;
	}
	/* Check for non regular and non existand files and
	 * determine total bytes to send */
	for (i = 0; i < file_count; i++) {
		GSList *item;
		gchar *path, *filename;
		gint64 size;

		item = g_slist_nth(file_list, i);
		path = (gchar *) item->data;
		filename = normalise_filename(path, &size);
		if (filename == NULL) {
			g_free(path);
			file_list = g_slist_remove_link(file_list, item);
			continue;
		}
		g_free(item->data);
		item->data = filename;
		byte_count += size;
	}

	/* determine Bluetooth device to send to */
	if (!bdaddrstr) {
		bdaddrstr = browse_device_dialog();
		if (!bdaddrstr) {
			free_mem();
			return 0;
		}
	}

	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING_UINT64,
					  G_TYPE_NONE, G_TYPE_STRING,
					  G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);
	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING,
					  G_TYPE_NONE, G_TYPE_STRING,
					  G_TYPE_STRING, G_TYPE_INVALID);
	dbus_g_object_register_marshaller(marshal_VOID__UINT64,
					  G_TYPE_NONE, G_TYPE_UINT64, G_TYPE_INVALID);

	/* init UI */
	ui_init();

	/* init DBus proxy, create Session */
	manager_proxy = dbus_g_proxy_new_for_name(connection,
						       "org.openobex",
						       "/org/openobex",
						       "org.openobex.Manager");
	dbus_g_proxy_add_signal(manager_proxy, "NameOwnerChanged",
				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
	dbus_g_proxy_connect_signal(manager_proxy, "NameOwnerChanged",
				    G_CALLBACK(name_owner_changed), NULL, NULL);

	dbus_g_proxy_begin_call(manager_proxy, "CreateBluetoothSession",
				(DBusGProxyCallNotify) session_created_cb, NULL, NULL,
				G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
				G_TYPE_INVALID);

	/* Go into main loop */
	gtk_main();

	/* done sending, free memory */
	free_mem();

	return 0;
}


[-- Attachment #4: Type: text/plain, Size: 228 bytes --]

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/

[-- Attachment #5: Type: text/plain, Size: 164 bytes --]

_______________________________________________
Bluez-devel mailing list
Bluez-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bluez-devel

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Bluez-devel] [PATCH] Updated sendto
  2008-02-23  1:30               ` Bastien Nocera
@ 2008-02-24 18:29                 ` Bastien Nocera
  2008-02-25  2:21                   ` Marcel Holtmann
  0 siblings, 1 reply; 16+ messages in thread
From: Bastien Nocera @ 2008-02-24 18:29 UTC (permalink / raw)
  To: BlueZ development

[-- Attachment #1: Type: text/plain, Size: 344 bytes --]


On Sat, 2008-02-23 at 01:30 +0000, Bastien Nocera wrote:
> On Thu, 2008-02-07 at 00:59 +0000, Bastien Nocera wrote:
> <snip>
> > And one that works with obex-data-server 0.1
> 
> Updated against current CVS, and fix a memleak when getting the adapters
> list. New main.c file attached as well, for easier review.

Updated against current CVS.

[-- Attachment #2: bluez-gnome-new-sendto-9.patch --]
[-- Type: text/x-patch, Size: 30348 bytes --]

Index: dialog.h
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/dialog.h,v
retrieving revision 1.1
diff -u -p -u -p -r1.1 dialog.h
--- dialog.h	23 Feb 2008 06:20:17 -0000	1.1
+++ dialog.h	24 Feb 2008 18:26:11 -0000
@@ -21,5 +21,7 @@
  *
  */
 
+#include <glib.h>
+
 gchar *show_browse_dialog(void);
 GSList *show_select_dialog(void);
Index: main.c
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/main.c,v
retrieving revision 1.24
diff -u -p -u -p -r1.24 main.c
--- main.c	23 Feb 2008 06:20:17 -0000	1.24
+++ main.c	24 Feb 2008 18:26:12 -0000
@@ -3,6 +3,8 @@
  *  BlueZ - Bluetooth protocol stack for Linux
  *
  *  Copyright (C) 2005-2008  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ *  Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
  *
  *
  *  This program is free software; you can redistribute it and/or modify
@@ -25,199 +27,621 @@
 #include <config.h>
 #endif
 
-#include <dbus/dbus-glib.h>
 
 #include <glib/gi18n.h>
+#include <gtk/gtk.h>
 
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <dbus/dbus-glib.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
 #include <gtk/gtk.h>
 
+#include "dbus-glue.h"
 #include "marshal.h"
-
 #include "dialog.h"
 
-static DBusGConnection *conn = NULL;
+/* DBus */
+static DBusGConnection *connection = NULL;
+static DBusGProxy *manager_proxy = NULL;
+static DBusGProxy *session_proxy = NULL;
+/* GTK */
+static GtkWidget *main_dialog, *from_label, *operation_label, *progress_bar;
+/* sending related */
+static gsize file_length = 0;
+static guint file_count = 0;
+static guint current_file = 0;
+static GSList *file_list = NULL;
+static guint byte_count = 0;
+static guint bytes_sent = 0;
+static gint64 first_transfer_time = 0;
+static gint64 last_update_time = 0;
+
+typedef enum {
+	BUTTONS_OK,
+	BUTTONS_RETRY_CANCEL,
+	BUTTONS_RETRY_SKIP_CANCEL
+} ButtonSet;
+
+/* Command line options */
+static gchar **files_to_send = NULL;
+static gchar *bdaddrstr = NULL;
+static gchar *device_name = NULL;
+
+#define DIALOG_RESPONSE_SKIP 1
+#define DIALOG_RESPONSE_RETRY 2
+
+static void error_occurred_cb(DBusGProxy *proxy, const gchar *error_name,
+			      const gchar *error_message, gpointer user_data);
+
+static const GOptionEntry options[] = {
+	{"dest", 'd', 0, G_OPTION_ARG_STRING, &bdaddrstr, 
+		"Bluetooth address of destination device", "BDADDR"},
+	{G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &files_to_send},
+	{NULL}
+};
 
-static GtkWidget *dialog;
-static GtkWidget *label_filename;
-static GtkWidget *label_status;
-static GtkWidget *progress;
+static gint64 get_system_time(void)
+{
+	struct timeval tmp;
 
-static guint64 filesize = 0;
+	gettimeofday(&tmp, NULL);
+	return (gint64)tmp.tv_usec + (gint64)tmp.tv_sec * G_GINT64_CONSTANT(1000000);
+}
 
-static void response_callback(GtkWidget *dialog,
-					gint response, gpointer user_data)
+static gboolean is_palm_device(const gchar *bdaddr)
 {
-	gtk_widget_destroy(dialog);
-
-	gtk_main_quit();
+	return (g_str_has_prefix(bdaddr, "00:04:6B")
+		|| g_str_has_prefix(bdaddr, "00:07:E0")
+		|| g_str_has_prefix(bdaddr, "00:0E:20"));
 }
 
-static void create_window(const gchar *filename)
+/* Return the local absolute filename for the file, and its size
+ * if the file exists and is stat()'able */
+static gchar *normalise_filename(const gchar *filename, gint64 *size)
 {
-	GtkWidget *vbox;
-	GtkWidget *label;
-	gchar *text;
+	char *ret;
+	struct stat file_stat;
 
-	dialog = gtk_dialog_new_with_buttons(_("File Transfer"), NULL,
-				GTK_DIALOG_NO_SEPARATOR,
-				GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
+	g_return_val_if_fail(filename != NULL, NULL);
+	g_return_val_if_fail(size != NULL, NULL);
 
-	gtk_window_set_default_size(GTK_WINDOW(dialog), 400, -1);
+	if (g_str_has_prefix(filename, "file://")) {
+		ret = g_filename_from_uri(filename, NULL, NULL);
+		if (ret == NULL)
+			return NULL;
+	} else if (filename[0] == '/') {
+		ret = g_strdup(filename);
+	} else {
+		gchar *curdir;
+
+		curdir = g_get_current_dir();
+		ret = g_build_filename(curdir, filename, NULL);
+		g_free(curdir);
+	}
 
-	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
+	if (!g_file_test(ret, G_FILE_TEST_IS_REGULAR)) {
+		g_free(ret);
+		return NULL;
+	}
 
-	vbox = gtk_vbox_new(FALSE, 6);
-	gtk_container_set_border_width(GTK_CONTAINER(vbox), 12);
-	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox);
+	if (!g_stat(ret, &file_stat)) {
+		*size = file_stat.st_size;
+	} else {
+		g_free(ret);
+		return NULL;
+	}
 
-	label = gtk_label_new(NULL);
-	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+	return ret;
+}
 
-	label_filename = label;
+static gchar *get_device_name(void)
+{
+	DBusGConnection *connection;
+	DBusGProxy *manager;
+	GError *error = NULL;
+	gchar *name, **adapters;
+	guint i;
 
-	progress = gtk_progress_bar_new();
-	gtk_box_pack_start(GTK_BOX(vbox), progress, FALSE, FALSE, 0);
+	name = NULL;
 
-	label = gtk_label_new(NULL);
-	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
-	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 6);
+	connection = dbus_g_bus_get(DBUS_BUS_SYSTEM, NULL);
+	if (connection == NULL)
+		return NULL;
+
+	manager = dbus_g_proxy_new_for_name(connection, "org.bluez",
+					    "/org/bluez", "org.bluez.Manager");
+	if (!manager) {
+		dbus_g_connection_unref(connection);
+		return NULL;
+	}
 
-	label_status = label;
+	if (!manager_list_adapters(manager, &adapters, &error)) {
+		g_object_unref(manager);
+		dbus_g_connection_unref(connection);
+		return NULL;
+	}
 
-	text = g_strdup_printf("<b>%s</b>", filename);
-	gtk_label_set_markup(GTK_LABEL(label_filename), text);
-	g_free(text);
+	for (i = 0; adapters[i] != NULL; i++) {
+		DBusGProxy *adapter;
 
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Connecting..."));
+		adapter = dbus_g_proxy_new_for_name(connection, "org.bluez",
+						    adapters[i], "org.bluez.Adapter");
+		if (dbus_g_proxy_call(adapter, "GetRemoteName", NULL,
+				      G_TYPE_STRING, bdaddrstr, G_TYPE_INVALID,
+				      G_TYPE_STRING, &name, G_TYPE_INVALID)) {
+			if (name != NULL && name[0] != '\0') {
+				g_object_unref(adapter);
+				break;
+			}
+		}
+		g_object_unref(adapter);
+	}
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, FALSE);
+	g_strfreev(adapters);
+	g_object_unref(manager);
+	dbus_g_connection_unref(connection);
 
-	g_signal_connect(G_OBJECT(dialog), "response",
-				G_CALLBACK(response_callback), NULL);
+	return name;
+}
 
-	gtk_widget_show_all(dialog);
+static gint show_error_dialog(GtkWindow *parent, ButtonSet buttons,
+			      const gchar *primary_text, const gchar *secondary_text)
+{
+	GtkWidget *dialog;
+	gchar *primary_text_markup;
+	gint ret;
+
+	primary_text_markup = g_strdup_printf("<span weight=\"bold\" size=\"larger\">%s</span>", 
+					      primary_text);
+	dialog = gtk_message_dialog_new_with_markup(parent, 0, GTK_MESSAGE_ERROR, 
+						    buttons==BUTTONS_OK? GTK_BUTTONS_OK: GTK_BUTTONS_CANCEL, 
+						    primary_text_markup);
+	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
+						 secondary_text);
+	if (buttons == BUTTONS_RETRY_SKIP_CANCEL) {
+		gtk_dialog_add_button(GTK_DIALOG(dialog), _("Skip"),
+				      DIALOG_RESPONSE_SKIP);
+	}
+	if (buttons != BUTTONS_OK) {
+		gtk_dialog_add_button(GTK_DIALOG(dialog), _("Retry"),
+				      DIALOG_RESPONSE_RETRY);
+	}
+	ret = gtk_dialog_run(GTK_DIALOG(dialog));
+
+	gtk_widget_destroy(dialog);
+	g_free(primary_text_markup);
+	return ret;
 }
 
-static void transfer_started(DBusGProxy *proxy, gchar *a, gchar *b,
-					guint64 size, gpointer user_data)
+static gint show_connection_error_dialog(GtkWindow *parent, const gchar *bdaddr,
+					 const gchar *primary_text)
 {
-	gchar *text;
+	if (!is_palm_device(bdaddrstr)) {
+		return show_error_dialog(parent,
+					 BUTTONS_RETRY_CANCEL,
+					 primary_text,
+					 _("Make sure that remote device is on and that it accepts Bluetooth connections."));
+	} else {
+		return show_error_dialog(parent,
+					 BUTTONS_RETRY_CANCEL,
+					 primary_text,
+					 _("Make sure \"Beam receive\" is enabled in the Power preferences, and Bluetooth is enabled, on your Palm device."));
+	}
+}
 
-	filesize = size;
+static gint handle_file_sending_error(const gchar *error_text,
+				      const gchar *error_secondary_text)
+{
+	ButtonSet buttons;
+	gint response_id;
 
-	text = g_strdup_printf(_("Starting transfer of %jd bytes"), size);
-	gtk_label_set_markup(GTK_LABEL(label_status), text);
-	g_free(text);
+	if (file_count > 1 && current_file < file_count - 1)
+		buttons = BUTTONS_RETRY_SKIP_CANCEL;
+	else
+		buttons = BUTTONS_RETRY_CANCEL;
+
+	response_id = show_error_dialog(GTK_WINDOW(main_dialog), buttons, 
+					error_text, error_secondary_text);
+
+	switch (response_id) {
+	case DIALOG_RESPONSE_SKIP:
+		current_file++;
+		bytes_sent += file_length;
+	case DIALOG_RESPONSE_RETRY:
+		return TRUE;
+	case GTK_RESPONSE_CANCEL:
+	default:
+		gtk_main_quit();
+		break;
+	}
+	return response_id;
+}
 
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0.0);
+static void cancel_clicked_cb(GtkWidget *button, gpointer user_data)
+{
+	gtk_main_quit();
 }
 
-static void transfer_progress(DBusGProxy *proxy,
-					guint64 bytes, gpointer user_data)
+static void ui_init(void)
 {
+	GtkWidget *vbox, *vbox_parent, *table;
+	GtkWidget *label1, *label2, *label3, *target_label;
+	GtkWidget *button;
 	gchar *text;
-	gdouble fraction;
 
-	text = g_strdup_printf(_("Transfered %jd of %jd bytes"),
-							bytes, filesize);
-	gtk_label_set_markup(GTK_LABEL(label_status), text);
+	/* initialize UI */
+
+	device_name = get_device_name();
+
+	/* main dialog */
+	main_dialog = gtk_dialog_new();
+	gtk_dialog_set_has_separator(GTK_DIALOG(main_dialog), FALSE);
+	gtk_window_set_title(GTK_WINDOW(main_dialog),
+			     _("Bluetooth file transfer"));
+	gtk_window_set_icon_name(GTK_WINDOW(main_dialog), "stock_bluetooth");
+	gtk_window_set_type_hint(GTK_WINDOW(main_dialog),
+				 GDK_WINDOW_TYPE_HINT_NORMAL);
+	gtk_window_set_position(GTK_WINDOW(main_dialog), GTK_WIN_POS_CENTER);
+	gtk_window_set_resizable(GTK_WINDOW(main_dialog), FALSE);
+	gtk_widget_set(main_dialog, "width-request", 400, NULL);
+	button = gtk_dialog_add_button(GTK_DIALOG(main_dialog),
+				       GTK_STOCK_CANCEL,
+				       GTK_RESPONSE_CANCEL);
+	g_signal_connect(G_OBJECT(button), "clicked",
+			 G_CALLBACK(cancel_clicked_cb), NULL);
+	gtk_container_set_border_width(GTK_CONTAINER(main_dialog), 6);
+
+	/* table elements */
+	from_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(from_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(from_label), PANGO_ELLIPSIZE_MIDDLE);
+	target_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(target_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(target_label), PANGO_ELLIPSIZE_END);
+	gtk_label_set_text(GTK_LABEL(target_label), device_name ? device_name : bdaddrstr);
+	label2 = gtk_label_new(NULL);
+	text = g_markup_printf_escaped("<b>%s</b>", _("From:"));
+	gtk_label_set_markup(GTK_LABEL(label2), text);
+	g_free(text);
+	label3 = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(label3), 1, 0.5);
+	text = g_markup_printf_escaped("<b>%s</b>", _("To:"));
+	gtk_label_set_markup(GTK_LABEL(label3), text);
 	g_free(text);
 
-	fraction = (gdouble) bytes / (gdouble) filesize;
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), fraction);
+	/* table */
+	table = gtk_table_new(2, 2, FALSE);
+	gtk_table_set_col_spacings(GTK_TABLE(table), 4);
+	gtk_table_set_row_spacings(GTK_TABLE(table), 4);
+	gtk_table_attach_defaults(GTK_TABLE(table), from_label, 1, 2, 0, 1);
+	gtk_table_attach_defaults(GTK_TABLE(table), target_label, 1, 2, 1, 2);
+	gtk_table_attach(GTK_TABLE(table), label2, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), label3, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+
+	/* vbox elements */
+	label1 = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(label1), 0, 0.5);
+	text = g_markup_printf_escaped("<span weight=\"bold\" size=\"larger\">%s</span>",
+				       _("Sending files via bluetooth"));
+	gtk_label_set_markup(GTK_LABEL(label1), text);
+	g_free(text);
+	progress_bar = gtk_progress_bar_new();
+	gtk_progress_bar_set_ellipsize(GTK_PROGRESS_BAR(progress_bar),
+				       PANGO_ELLIPSIZE_END);
+	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
+				  _("Connecting..."));
+	operation_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(operation_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(operation_label), PANGO_ELLIPSIZE_END);
+
+	/* vbox */
+	vbox = gtk_vbox_new(FALSE, 0);
+	gtk_box_set_spacing(GTK_BOX(vbox), 6);
+	gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
+	gtk_box_pack_start_defaults(GTK_BOX(vbox), label1);
+	gtk_box_pack_start(GTK_BOX(vbox), table, TRUE, TRUE, 9);
+	gtk_box_pack_start_defaults(GTK_BOX(vbox), progress_bar);
+	gtk_box_pack_start(GTK_BOX(vbox), operation_label, TRUE, TRUE, 2);
+	vbox_parent = gtk_bin_get_child(GTK_BIN(main_dialog));
+	gtk_box_pack_start_defaults(GTK_BOX(vbox_parent), vbox);
+
+	/* show it */
+	gtk_widget_show_all(main_dialog);
 }
 
-static void transfer_completed(DBusGProxy *proxy, gpointer user_data)
+static gboolean send_one(gpointer user_data)
 {
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Completed"));
+	const gchar *fname;
+	gint ret;
+	gchar *bname, *dirname;
+	GError *err = NULL;
+	gchar *operation_text, *operation_markup, *progressbar_text;
+
+	if ((fname = g_slist_nth_data(file_list, current_file))) {
+		/* there's a file to send */
+		dbus_g_proxy_call(session_proxy, "SendFile", &err,
+				  G_TYPE_STRING, fname, G_TYPE_INVALID, G_TYPE_INVALID);
+
+		if (err != NULL) {
+			ret = handle_file_sending_error(_("Unable to read file"),
+							err->message);
+			if (ret != GTK_RESPONSE_CANCEL) {
+				g_idle_add((GSourceFunc) send_one, NULL);
+			}
+			g_error_free(err);
+			return FALSE;
+		}
 
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 1.0);
+		if (!first_transfer_time) {
+			first_transfer_time = get_system_time();
+		}
+		bname = g_path_get_basename(fname);
+		dirname = g_path_get_dirname(fname);
 
-	dbus_g_proxy_call(proxy, "Disconnect", NULL, G_TYPE_INVALID,
-							G_TYPE_INVALID);
+		operation_text = g_strdup_printf(_("Sending %s"), bname);
+		operation_markup = g_markup_printf_escaped("<i>%s</i>", operation_text);
+		progressbar_text = g_strdup_printf(_("Sending file: %d of %d"),
+						   current_file+1,
+						   file_count);
+		gtk_label_set_text(GTK_LABEL(from_label), dirname);
+		gtk_label_set_markup(GTK_LABEL(operation_label), operation_markup);
+		gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
+					  progressbar_text);
+
+		g_free(bname);
+		g_free(dirname);
+		g_free(operation_text);
+		g_free(operation_markup);
+		g_free(progressbar_text);
+	} else {
+		/* nothing left to send, time to quit */
+		gtk_main_quit();
+	}
+	return FALSE;
+}
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, TRUE);
+static void session_connected_cb(DBusGProxy *proxy, gpointer user_data)
+{
+	/* OBEX connect has succeeded, so start sending */
+	g_idle_add((GSourceFunc) send_one, NULL);
 }
 
-static void session_connected(DBusGProxy *proxy, gpointer user_data)
+static void transfer_started_cb(DBusGProxy *proxy, const gchar *filename,
+				const gchar *local_path, guint64 byte_count, gpointer user_data)
 {
-	gchar *filename = user_data;
-	GError *error = NULL;
+	file_length = byte_count;
+}
 
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Connected"));
+static void transfer_cancelled_cb(DBusGProxy *proxy, gpointer user_data)
+{
+	gint ret;
+	gchar *error_text;
 
-	dbus_g_proxy_call(proxy, "SendFile", &error,
-				G_TYPE_STRING, filename, G_TYPE_INVALID,
-							G_TYPE_INVALID);
-
-	if (error != NULL) {
-		g_printerr("Sending of file %s failed: %s\n", filename,
-							error->message);
-		g_error_free(error);
-		gtk_main_quit();
+	error_text = g_strdup_printf(_("An error occured while sending file '%s'"),
+				     (gchar *)g_slist_nth_data(file_list, current_file));
+	ret = handle_file_sending_error(error_text,
+					_("The remote device cancelled the transfer"));
+
+	if (ret != GTK_RESPONSE_CANCEL)
+		g_idle_add((GSourceFunc) send_one, NULL);
+
+	g_free(error_text);
+}
+
+static void transfer_progress_cb(DBusGProxy *proxy, guint64 bytes_transferred, gpointer user_data)
+{
+	gdouble frac;
+	guint actual_bytes_sent;
+	gint elapsed_time;
+	gint transfer_rate;
+	gint time_remaining;
+	gint64 current_time;
+	gchar *str, *str2;
+	gchar *progressbar_text;
+
+	/* update progress bar fraction */
+	actual_bytes_sent = bytes_sent + bytes_transferred;
+	frac = (gdouble)actual_bytes_sent / byte_count;
+	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress_bar), frac);
+
+	/* update progress bar text (time remaining)
+	 * (only update once in a second) */
+	current_time = get_system_time();
+	elapsed_time = (current_time - first_transfer_time) / 1000000;
+
+	if (last_update_time) {
+		if (current_time < last_update_time + 1000000)
+			return;
+		else
+			last_update_time = current_time;
+	} else {
+		last_update_time = current_time;
 	}
+
+	if (!elapsed_time)
+		return;
+
+	transfer_rate = actual_bytes_sent / elapsed_time;
+	if (transfer_rate == 0)
+		return;
+
+	time_remaining = (byte_count - actual_bytes_sent) / transfer_rate;
+
+	if (time_remaining >= 3600) {
+		str = g_strdup_printf(_("(%d:%02d:%d Remaining)"), 
+				      time_remaining / 3600,
+				      (time_remaining % 3600) / 60,
+				      (time_remaining % 3600) % 60);
+	} else {
+		str = g_strdup_printf(_("(%d:%02d Remaining)"), 
+				      time_remaining / 60,
+				      time_remaining % 60);
+	}
+
+	/* Translators:
+	 * Sending file 1 of 3 */
+	str2 = g_strdup_printf(_("Sending file %d of %d"),
+			       current_file + 1,
+			       file_count);
+	progressbar_text = g_strconcat(str2, " ", str, NULL);
+	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
+				  progressbar_text);
+	g_free(str);
+	g_free(str2);
+	g_free(progressbar_text);
 }
 
-static void create_notify(DBusGProxy *proxy,
-				DBusGProxyCall *call, void *user_data)
+static void transfer_completed_cb(DBusGProxy *proxy, gpointer user_data)
+{
+	current_file++;
+	bytes_sent += file_length;
+
+	g_idle_add((GSourceFunc) send_one, NULL);
+}
+
+static void session_created_cb(DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data)
 {
 	GError *error = NULL;
 	const gchar *path = NULL;
 
-	if (dbus_g_proxy_end_call(proxy, call, &error,
-					DBUS_TYPE_G_OBJECT_PATH, &path,
-						G_TYPE_INVALID) == FALSE) {
-		gchar *text, *message;
-
-		if (error != NULL) {
-			message = g_strdup(error->message);
-			g_error_free(error);
-		} else
-			message = g_strdup(_("An unknown error occured"));
-
-		text = g_strdup_printf("<span foreground=\"red\">%s</span>",
-								message);
-		gtk_label_set_markup(GTK_LABEL(label_status), text);
-		g_free(text);
-
-		g_free(message);
+	if (!dbus_g_proxy_end_call(proxy, call, &error,
+				   DBUS_TYPE_G_OBJECT_PATH, &path, G_TYPE_INVALID)) {
+		const gchar *error_name = NULL;
+		gint response_id;
+
+		if (error && error->code == DBUS_GERROR_REMOTE_EXCEPTION)
+			error_name = dbus_g_error_get_name(error);
+		if (error_name && !strcmp(error_name, "org.openobex.Error.ConnectionAttemptFailed")) {
+			response_id = show_connection_error_dialog(GTK_WINDOW(main_dialog), bdaddrstr, error->message);
+			if (response_id == DIALOG_RESPONSE_RETRY) {
+				/* Try to create Session again */
+				dbus_g_proxy_begin_call(manager_proxy,
+							"CreateBluetoothSession",
+							(DBusGProxyCallNotify) session_created_cb, NULL, NULL,
+							G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+							G_TYPE_BOOLEAN, FALSE, G_TYPE_INVALID);
+				goto out;
+			}
+		} else {
+			show_error_dialog(GTK_WINDOW(main_dialog), BUTTONS_OK,
+					  error->message, "");
+		}
 
-		gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, TRUE);
-		return;
+		/* Failed, quit main loop */
+		gtk_main_quit();
+		goto out;
 	}
 
-	proxy = dbus_g_proxy_new_for_name(conn, "org.openobex",
-						path, "org.openobex.Session");
+	session_proxy = dbus_g_proxy_new_for_name(connection,
+						       "org.openobex",
+						       path,
+						       "org.openobex.Session");
 
-	dbus_g_proxy_add_signal(proxy, "Connected", G_TYPE_INVALID);
+	dbus_g_proxy_add_signal(session_proxy, "Connected",
+				G_TYPE_INVALID);
+
+	dbus_g_proxy_connect_signal(session_proxy, "Connected",
+				    G_CALLBACK(session_connected_cb), NULL, NULL);
+
+	dbus_g_proxy_add_signal(session_proxy, "TransferStarted",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);
+
+	dbus_g_proxy_connect_signal(session_proxy, "TransferStarted",
+				    G_CALLBACK(transfer_started_cb), NULL, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "Connected",
-				G_CALLBACK(session_connected), user_data, NULL);
+	dbus_g_proxy_add_signal(session_proxy, "Cancelled",
+				G_TYPE_INVALID);
+
+	dbus_g_proxy_connect_signal(session_proxy, "Cancelled",
+				    G_CALLBACK(transfer_cancelled_cb), NULL, NULL);
 
-	dbus_g_proxy_add_signal(proxy, "TransferStarted", G_TYPE_STRING,
-				G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);
+	dbus_g_proxy_add_signal(session_proxy, "ErrorOccurred",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferStarted",
-				G_CALLBACK(transfer_started), NULL, NULL);
+	dbus_g_proxy_connect_signal(session_proxy, "ErrorOccurred",
+				    G_CALLBACK(error_occurred_cb), NULL, NULL);
 
-	dbus_g_proxy_add_signal(proxy, "TransferProgress",
-						G_TYPE_UINT64, G_TYPE_INVALID);
+	dbus_g_proxy_add_signal(session_proxy, "TransferProgress",
+				G_TYPE_UINT64, G_TYPE_INVALID);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferProgress",
-				G_CALLBACK(transfer_progress), NULL, NULL);
+	dbus_g_proxy_connect_signal(session_proxy, "TransferProgress",
+				    G_CALLBACK(transfer_progress_cb), NULL, NULL);
+
+	dbus_g_proxy_add_signal(session_proxy, "TransferCompleted", 
+				G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "TransferCompleted", G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(session_proxy, "TransferCompleted",
+				    G_CALLBACK(transfer_completed_cb), NULL, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferCompleted",
-				G_CALLBACK(transfer_completed), NULL, NULL);
+	dbus_g_proxy_call(session_proxy, "Connect", &error, G_TYPE_INVALID,
+			  G_TYPE_INVALID);
 
-	dbus_g_proxy_call(proxy, "Connect", NULL, G_TYPE_INVALID,
-							G_TYPE_INVALID);
+out:
+	if (error)
+		g_error_free(error);
+}
+
+static void error_occurred_cb(DBusGProxy *proxy, const gchar *error_name, const gchar *error_message, gpointer user_data)
+{
+	gint ret;
+	gboolean link_error = FALSE;
+	gchar *error_text;
+	const gchar *reason;
+
+	g_message("ErrorOccurred");
+	g_message("Error name: %s", error_name);
+	g_message("Error message: %s", error_message);
+
+	error_text = g_strdup_printf(_("An error occured while sending file '%s'"),
+				     (gchar *)g_slist_nth_data(file_list, current_file));
+	if (!strcmp(error_name, "org.openobex.Error.LinkError")) {
+		/* Connection to remote device was lost */
+		g_object_unref(G_OBJECT(session_proxy));
+		reason = _("Connection to remote device was lost");
+		link_error = TRUE;
+	} else if (!strcmp(error_name, "org.openobex.Error.Forbidden")) {
+		reason = _("Remote device rejected file");
+	} else {
+		reason = _("Unknown error");
+	}
+	ret = handle_file_sending_error(error_text, reason);
+
+	if (ret != GTK_RESPONSE_CANCEL) {
+		if (link_error) {
+			/* Need to reestablish connection */
+			dbus_g_proxy_begin_call(manager_proxy, "CreateBluetoothSession",
+						(DBusGProxyCallNotify) session_created_cb, NULL, NULL,
+						G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+						G_TYPE_INVALID);
+		} else {
+			g_idle_add((GSourceFunc) send_one, NULL);
+		}
+	}
+
+
+	g_free(error_text);
+}
+
+static void free_mem(void)
+{
+	if (files_to_send)
+		g_strfreev(files_to_send);
+	g_free(bdaddrstr);
+
+	if (main_dialog)
+		gtk_widget_destroy (main_dialog);
+	if (G_IS_OBJECT(manager_proxy))
+		g_object_unref(G_OBJECT(manager_proxy));
+	if (G_IS_OBJECT(session_proxy))
+		g_object_unref (G_OBJECT(session_proxy));
+	g_slist_free(file_list);
 }
 
 static void name_owner_changed(DBusGProxy *proxy, const char *name,
@@ -227,102 +651,128 @@ static void name_owner_changed(DBusGProx
 		gtk_main_quit();
 }
 
-static gchar *option_device = NULL;
-
-static GOptionEntry options[] = {
-	{ "device", 0, 0, G_OPTION_ARG_STRING, &option_device,
-				N_("Remote device to use"), "ADDRESS" },
-	{ NULL },
-};
-
 int main(int argc, char *argv[])
 {
-	DBusGProxy *proxy;
+	GOptionContext *option_context;
 	GError *error = NULL;
-	GSList *filenames;
-	gchar *filename, *address;
+	guint i;
 
+	/* initialize gettext */
 	bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
 	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
 	textdomain(GETTEXT_PACKAGE);
 
-	error = NULL;
+	/* parse command line arguments */
+	option_context = g_option_context_new("<file list>");
+	g_option_context_add_main_entries(option_context, options, GETTEXT_PACKAGE);
+	g_option_context_add_group(option_context, gtk_get_option_group(TRUE));
+	g_option_context_parse(option_context, &argc, &argv, &error);
+	g_option_context_free(option_context);
+	if (error) {
+		printf("Usage:\n");
+		printf("  %s [--dest=BDADDR] <file list>\n", g_get_prgname());
+		g_error_free(error);
+		free_mem();
+		return 0;
+	}
 
-	if (gtk_init_with_args(&argc, &argv, "[FILE...]",
-				options, GETTEXT_PACKAGE, &error) == FALSE) {
-		if (error != NULL) {
-			g_printerr("%s\n", error->message);
-			g_error_free(error);
-		} else
-			g_printerr("An unknown error occurred\n");
-
-		gtk_exit(1);
-	}
-
-	gtk_window_set_default_icon_name("bluetooth");
-
-	if (argc < 2) {
-		filenames = show_select_dialog();
-		if (filenames == NULL)
-			gtk_exit(1);
-	} else
-		filenames = g_slist_append(NULL, g_strdup(argv[1]));
-
-	if (option_device == NULL) {
-		address = show_browse_dialog();
-		if (address == NULL) {
-			g_slist_free(filenames);
-			gtk_exit(1);
-		}
-	} else
-		address = g_strdup(option_device);
+	gtk_init(&argc, &argv);
+
+	/* init DBus connection */
+	connection = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
+	if (!connection) {
+		show_error_dialog(NULL, GTK_BUTTONS_OK,
+				  _("Please verify that D-Bus is correctly installed and setup."), error->message);
+		g_error_free(error);
+		return 1;
+	}
 
-	conn = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
-	if (conn == NULL) {
-		if (error != NULL) {
-			g_printerr("Connecting to session bus failed: %s\n",
-							error->message);
-			g_error_free(error);
-		} else
-			g_print("An unknown error occured\n");
+	main_dialog = NULL;
+	from_label = NULL;
+	operation_label = NULL;
+	progress_bar = NULL;
+
+	/* get a list of files to send from command-line */
+	if (files_to_send)
+		file_count = g_strv_length(files_to_send);
+	else
+		file_count = 0;
+	for (i = 0; i < file_count; i++) {
+		file_list = g_slist_append(file_list,
+						g_strdup(*(files_to_send + i)));
+	}
+	/* show file chooser if no files were specified */
+	if (!file_count) {
+		file_list = show_select_dialog();
+		file_count = g_slist_length(file_list);
+	}
+	/* Check if there are some files to send */
+	if (!file_count) {
+		free_mem();
+		return 0;
+	}
+	/* Check for non regular and non existand files and
+	 * determine total bytes to send */
+	for (i = 0; i < file_count; i++) {
+		GSList *item;
+		gchar *path, *filename;
+		gint64 size;
+
+		item = g_slist_nth(file_list, i);
+		path = (gchar *) item->data;
+		filename = normalise_filename(path, &size);
+		if (filename == NULL) {
+			g_free(path);
+			file_list = g_slist_remove_link(file_list, item);
+			continue;
+		}
+		g_free(item->data);
+		item->data = filename;
+		byte_count += size;
+	}
 
-		gtk_exit(1);
+	/* determine Bluetooth device to send to */
+	if (!bdaddrstr) {
+		bdaddrstr = show_browse_dialog();
+		if (!bdaddrstr) {
+			free_mem();
+			return 0;
+		}
 	}
 
 	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING_UINT64,
-				G_TYPE_NONE, G_TYPE_STRING,
-				G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);
-
+					  G_TYPE_NONE, G_TYPE_STRING,
+					  G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);
+	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING,
+					  G_TYPE_NONE, G_TYPE_STRING,
+					  G_TYPE_STRING, G_TYPE_INVALID);
 	dbus_g_object_register_marshaller(marshal_VOID__UINT64,
 				G_TYPE_NONE, G_TYPE_UINT64, G_TYPE_INVALID);
 
-	filename = g_slist_nth_data(filenames, 0);
-
-	create_window(filename);
+	/* init UI */
+	ui_init();
 
-	proxy = dbus_g_proxy_new_for_name(conn, "org.openobex",
-				"/org/openobex", "org.openobex.Manager");
-
-	dbus_g_proxy_add_signal(proxy, "NameOwnerChanged",
-		G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
-
-	dbus_g_proxy_connect_signal(proxy, "NameOwnerChanged",
-				G_CALLBACK(name_owner_changed), NULL, NULL);
-
-	dbus_g_proxy_begin_call(proxy, "CreateBluetoothSession",
-				create_notify, filename, NULL,
-				G_TYPE_STRING, address, G_TYPE_STRING, "opp",
+	/* init DBus proxy, create Session */
+	manager_proxy = dbus_g_proxy_new_for_name(connection,
+						       "org.openobex",
+						       "/org/openobex",
+						       "org.openobex.Manager");
+	dbus_g_proxy_add_signal(manager_proxy, "NameOwnerChanged",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(manager_proxy, "NameOwnerChanged",
+				    G_CALLBACK(name_owner_changed), NULL, NULL);
+
+	dbus_g_proxy_begin_call(manager_proxy, "CreateBluetoothSession",
+				(DBusGProxyCallNotify) session_created_cb, NULL, NULL,
+				G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
 				G_TYPE_INVALID);
 
+	/* Go into main loop */
 	gtk_main();
 
-	g_object_unref(proxy);
-
-	dbus_g_connection_unref(conn);
-
-	g_free(address);
-
-	g_slist_free(filenames);
+	/* done sending, free memory */
+	free_mem();
 
 	return 0;
 }
+

[-- Attachment #3: Type: text/plain, Size: 228 bytes --]

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/

[-- Attachment #4: Type: text/plain, Size: 164 bytes --]

_______________________________________________
Bluez-devel mailing list
Bluez-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bluez-devel

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Bluez-devel] [PATCH] Updated sendto
  2008-02-24 18:29                 ` Bastien Nocera
@ 2008-02-25  2:21                   ` Marcel Holtmann
  2008-02-25 10:56                     ` Bastien Nocera
  0 siblings, 1 reply; 16+ messages in thread
From: Marcel Holtmann @ 2008-02-25  2:21 UTC (permalink / raw)
  To: BlueZ development

Hi Bastien,

>>>
>>> And one that works with obex-data-server 0.1
>>
>> Updated against current CVS, and fix a memleak when getting the  
>> adapters
>> list. New main.c file attached as well, for easier review.
>
> Updated against current CVS.

I've been through the main.c as whole and the actual patch. As you  
saw, I took the dialogs out of it since that made sense and was a sane  
way to do it. I mentioned that I want small patches and if they are  
small and logical, I apply them most of the times immediately. Smaller  
patches are easier to review. I am not taking a big chunk blindly.

So current patch is not acceptable. It is actually bad. So first  
action must be to remove all these useless comments. An example is this:

+       /* Go into main loop */
         gtk_main();

Put comments where the code is unclear and not were everybody knows  
what it is doing. This is a perfect example of wrongly commenting code.

Second of all, I am unhappy with all this usage of gtk_main_quit() in  
various functions. Can we not just structure the code a lot more  
cleaner to avoid multiple calls of it. Besides the signal handling, I  
would expect one extra call in case we automatically wanna close the  
progress dialog.

After that, I like to see the current sendto patches to look like the  
obex-send progress bar. It should be not that complicated.

Then we go for sending multiple files after another.

Regards

Marcel


-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Bluez-devel mailing list
Bluez-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bluez-devel

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Bluez-devel] [PATCH] Updated sendto
  2008-02-25  2:21                   ` Marcel Holtmann
@ 2008-02-25 10:56                     ` Bastien Nocera
  2008-02-25 20:36                       ` Marcel Holtmann
  0 siblings, 1 reply; 16+ messages in thread
From: Bastien Nocera @ 2008-02-25 10:56 UTC (permalink / raw)
  To: BlueZ development


On Mon, 2008-02-25 at 03:21 +0100, Marcel Holtmann wrote:
> Hi Bastien,
> 
> >>>
> >>> And one that works with obex-data-server 0.1
> >>
> >> Updated against current CVS, and fix a memleak when getting the  
> >> adapters
> >> list. New main.c file attached as well, for easier review.
> >
> > Updated against current CVS.
> 
> I've been through the main.c as whole and the actual patch. As you  
> saw, I took the dialogs out of it since that made sense and was a sane  
> way to do it. I mentioned that I want small patches and if they are  
> small and logical, I apply them most of the times immediately. Smaller  
> patches are easier to review. I am not taking a big chunk blindly.
> 
> So current patch is not acceptable. It is actually bad. So first  
> action must be to remove all these useless comments. An example is this:
> 
> +       /* Go into main loop */
>          gtk_main();
> 
> Put comments where the code is unclear and not were everybody knows  
> what it is doing. This is a perfect example of wrongly commenting code.

Done locally.

> Second of all, I am unhappy with all this usage of gtk_main_quit() in  
> various functions. Can we not just structure the code a lot more  
> cleaner to avoid multiple calls of it.

I don't understand what you mean there.

>  Besides the signal handling, I  
> would expect one extra call in case we automatically wanna close the  
> progress dialog.

Why? If there's no errors, why would you want to see it's finished? What
information would be in the dialogue that could be useful?




-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Bluez-devel mailing list
Bluez-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bluez-devel

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Bluez-devel] [PATCH] Updated sendto
  2008-02-25 10:56                     ` Bastien Nocera
@ 2008-02-25 20:36                       ` Marcel Holtmann
  2008-02-26  1:18                         ` Bastien Nocera
  0 siblings, 1 reply; 16+ messages in thread
From: Marcel Holtmann @ 2008-02-25 20:36 UTC (permalink / raw)
  To: BlueZ development

Hi Bastien,

> > So current patch is not acceptable. It is actually bad. So first  
> > action must be to remove all these useless comments. An example is this:
> > 
> > +       /* Go into main loop */
> >          gtk_main();
> > 
> > Put comments where the code is unclear and not were everybody knows  
> > what it is doing. This is a perfect example of wrongly commenting code.
> 
> Done locally.

do you have an updated patch or do I have to do it by myself.

> > Second of all, I am unhappy with all this usage of gtk_main_quit() in  
> > various functions. Can we not just structure the code a lot more  
> > cleaner to avoid multiple calls of it.
> 
> I don't understand what you mean there.

What I currently got from code review is that we simply call
gtk_main_quit() instead of having a little bit better structured code.
Just stopping the mainloop at more then 1 or 2 places doesn't seem right
to me. Feel free to convince me otherwise.

> >  Besides the signal handling, I  
> > would expect one extra call in case we automatically wanna close the  
> > progress dialog.
> 
> Why? If there's no errors, why would you want to see it's finished? What
> information would be in the dialogue that could be useful?

That behavior is fine with me. However that was not what I said. So I
expect two gtk_main_quit() inside the code. One for SIGTERM and one when
we are actually done with the transfer.

Regards

Marcel



-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Bluez-devel mailing list
Bluez-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bluez-devel

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Bluez-devel] [PATCH] Updated sendto
  2008-02-25 20:36                       ` Marcel Holtmann
@ 2008-02-26  1:18                         ` Bastien Nocera
  2008-02-26  1:21                           ` Marcel Holtmann
  0 siblings, 1 reply; 16+ messages in thread
From: Bastien Nocera @ 2008-02-26  1:18 UTC (permalink / raw)
  To: BlueZ development


On Mon, 2008-02-25 at 21:36 +0100, Marcel Holtmann wrote:
> Hi Bastien,
> 
> > > So current patch is not acceptable. It is actually bad. So first  
> > > action must be to remove all these useless comments. An example is this:
> > > 
> > > +       /* Go into main loop */
> > >          gtk_main();
> > > 
> > > Put comments where the code is unclear and not were everybody knows  
> > > what it is doing. This is a perfect example of wrongly commenting code.
> > 
> > Done locally.
> 
> do you have an updated patch or do I have to do it by myself.

It was minimal changes, so I was waiting for response on the other
issues.

> > > Second of all, I am unhappy with all this usage of gtk_main_quit() in  
> > > various functions. Can we not just structure the code a lot more  
> > > cleaner to avoid multiple calls of it.
> > 
> > I don't understand what you mean there.
> 
> What I currently got from code review is that we simply call
> gtk_main_quit() instead of having a little bit better structured code.
> Just stopping the mainloop at more then 1 or 2 places doesn't seem right
> to me. Feel free to convince me otherwise.

All the uses of gtk_main_quit() could be "exit(0)" instead, but we want
to finish the mainloop and finish the cleaning up afterwards. It makes
debugging memory leaks easier, as the program can exit cleanly after
having mopped up after itself as hard as it could.

There's one instance of gtk_main_quit() that we could move (the one in
send_one shouldn't be needed, we should exit as soon as we know the
filelist is empty).

> > >  Besides the signal handling, I  
> > > would expect one extra call in case we automatically wanna close the  
> > > progress dialog.
> > 
> > Why? If there's no errors, why would you want to see it's finished? What
> > information would be in the dialogue that could be useful?
> 
> That behavior is fine with me. However that was not what I said. So I
> expect two gtk_main_quit() inside the code. One for SIGTERM and one when
> we are actually done with the transfer.

Do you just want us to exit() on error/user cancellation paths? It makes
no practical differences apart from the one mentioned above.


-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Bluez-devel mailing list
Bluez-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bluez-devel

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Bluez-devel] [PATCH] Updated sendto
  2008-02-26  1:18                         ` Bastien Nocera
@ 2008-02-26  1:21                           ` Marcel Holtmann
  2008-02-27  1:28                             ` Bastien Nocera
  0 siblings, 1 reply; 16+ messages in thread
From: Marcel Holtmann @ 2008-02-26  1:21 UTC (permalink / raw)
  To: BlueZ development

Hi Bastien,

>>>> So current patch is not acceptable. It is actually bad. So first
>>>> action must be to remove all these useless comments. An example  
>>>> is this:
>>>>
>>>> +       /* Go into main loop */
>>>>         gtk_main();
>>>>
>>>> Put comments where the code is unclear and not were everybody knows
>>>> what it is doing. This is a perfect example of wrongly commenting  
>>>> code.
>>>
>>> Done locally.
>>
>> do you have an updated patch or do I have to do it by myself.
>
> It was minimal changes, so I was waiting for response on the other
> issues.
>
>>>> Second of all, I am unhappy with all this usage of  
>>>> gtk_main_quit() in
>>>> various functions. Can we not just structure the code a lot more
>>>> cleaner to avoid multiple calls of it.
>>>
>>> I don't understand what you mean there.
>>
>> What I currently got from code review is that we simply call
>> gtk_main_quit() instead of having a little bit better structured  
>> code.
>> Just stopping the mainloop at more then 1 or 2 places doesn't seem  
>> right
>> to me. Feel free to convince me otherwise.
>
> All the uses of gtk_main_quit() could be "exit(0)" instead, but we  
> want
> to finish the mainloop and finish the cleaning up afterwards. It makes
> debugging memory leaks easier, as the program can exit cleanly after
> having mopped up after itself as hard as it could.
>
> There's one instance of gtk_main_quit() that we could move (the one in
> send_one shouldn't be needed, we should exit as soon as we know the
> filelist is empty).
>
>>>> Besides the signal handling, I
>>>> would expect one extra call in case we automatically wanna close  
>>>> the
>>>> progress dialog.
>>>
>>> Why? If there's no errors, why would you want to see it's  
>>> finished? What
>>> information would be in the dialogue that could be useful?
>>
>> That behavior is fine with me. However that was not what I said. So I
>> expect two gtk_main_quit() inside the code. One for SIGTERM and one  
>> when
>> we are actually done with the transfer.
>
> Do you just want us to exit() on error/user cancellation paths? It  
> makes
> no practical differences apart from the one mentioned above.

lets use gtk_main_quit() since the exit code doesn't really matter and  
show me an updated patch. I am still working through the whole thing.  
We need some simplifications here and there, because the code looks  
too complicated to me.

Regards

Marcel


-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Bluez-devel mailing list
Bluez-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bluez-devel

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Bluez-devel] [PATCH] Updated sendto
  2008-02-26  1:21                           ` Marcel Holtmann
@ 2008-02-27  1:28                             ` Bastien Nocera
  0 siblings, 0 replies; 16+ messages in thread
From: Bastien Nocera @ 2008-02-27  1:28 UTC (permalink / raw)
  To: BlueZ development

[-- Attachment #1: Type: text/plain, Size: 522 bytes --]


On Tue, 2008-02-26 at 02:21 +0100, Marcel Holtmann wrote:
<snip>
> lets use gtk_main_quit() since the exit code doesn't really matter and  
> show me an updated patch. I am still working through the whole thing.  
> We need some simplifications here and there, because the code looks  
> too complicated to me.

There's some bits of the code that aren't as easy to read as they could,
but it's hard to take a step back when you've used to the code.

Let me know which parts you don't like and we can rework them.

Cheers

[-- Attachment #2: bluez-gnome-new-sendto-10.patch --]
[-- Type: text/x-patch, Size: 30100 bytes --]

Index: sendto/dialog.h
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/dialog.h,v
retrieving revision 1.1
diff -u -p -r1.1 dialog.h
--- sendto/dialog.h	23 Feb 2008 06:20:17 -0000	1.1
+++ sendto/dialog.h	27 Feb 2008 01:25:10 -0000
@@ -21,5 +21,7 @@
  *
  */
 
+#include <glib.h>
+
 gchar *show_browse_dialog(void);
 GSList *show_select_dialog(void);
Index: sendto/main.c
===================================================================
RCS file: /cvsroot/bluez/gnome/sendto/main.c,v
retrieving revision 1.24
diff -u -p -r1.24 main.c
--- sendto/main.c	23 Feb 2008 06:20:17 -0000	1.24
+++ sendto/main.c	27 Feb 2008 01:25:10 -0000
@@ -3,6 +3,8 @@
  *  BlueZ - Bluetooth protocol stack for Linux
  *
  *  Copyright (C) 2005-2008  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2006-2007 Tadas Dailyda <tadas@dailyda.com>
+ *  Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
  *
  *
  *  This program is free software; you can redistribute it and/or modify
@@ -25,199 +27,618 @@
 #include <config.h>
 #endif
 
-#include <dbus/dbus-glib.h>
 
 #include <glib/gi18n.h>
+#include <gtk/gtk.h>
 
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <dbus/dbus-glib.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
 #include <gtk/gtk.h>
 
+#include "dbus-glue.h"
 #include "marshal.h"
-
 #include "dialog.h"
 
-static DBusGConnection *conn = NULL;
+#define MSEC_PER_SEC 1000000
 
-static GtkWidget *dialog;
-static GtkWidget *label_filename;
-static GtkWidget *label_status;
-static GtkWidget *progress;
-
-static guint64 filesize = 0;
+/* DBus */
+static DBusGConnection *connection = NULL;
+static DBusGProxy *manager_proxy = NULL;
+static DBusGProxy *session_proxy = NULL;
+/* GTK */
+static GtkWidget *main_dialog, *from_label, *operation_label, *progress_bar;
+/* sending related */
+static gsize file_length = 0;
+static guint file_count = 0;
+static guint current_file = 0;
+static GSList *file_list = NULL;
+static guint byte_count = 0;
+static guint bytes_sent = 0;
+static gint64 first_transfer_time = 0;
+static gint64 last_update_time = 0;
+
+typedef enum {
+	BUTTONS_OK,
+	BUTTONS_RETRY_CANCEL,
+	BUTTONS_RETRY_SKIP_CANCEL
+} ButtonSet;
+
+/* Command line options */
+static gchar **files_to_send = NULL;
+static gchar *bdaddrstr = NULL;
+static gchar *device_name = NULL;
+
+#define DIALOG_RESPONSE_SKIP 1
+#define DIALOG_RESPONSE_RETRY 2
+
+static void error_occurred_cb(DBusGProxy *proxy, const gchar *error_name,
+			      const gchar *error_message, gpointer user_data);
+
+static const GOptionEntry options[] = {
+	{"dest", 'd', 0, G_OPTION_ARG_STRING, &bdaddrstr, 
+		"Bluetooth address of destination device", "BDADDR"},
+	{G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &files_to_send},
+	{NULL}
+};
 
-static void response_callback(GtkWidget *dialog,
-					gint response, gpointer user_data)
+static gint64 get_system_time(void)
 {
-	gtk_widget_destroy(dialog);
+	struct timeval tmp;
 
-	gtk_main_quit();
+	gettimeofday(&tmp, NULL);
+	return (gint64)tmp.tv_usec + (gint64)tmp.tv_sec * G_GINT64_CONSTANT(1000000);
 }
 
-static void create_window(const gchar *filename)
+static gboolean is_palm_device(const gchar *bdaddr)
 {
-	GtkWidget *vbox;
-	GtkWidget *label;
-	gchar *text;
+	return (g_str_has_prefix(bdaddr, "00:04:6B")
+		|| g_str_has_prefix(bdaddr, "00:07:E0")
+		|| g_str_has_prefix(bdaddr, "00:0E:20"));
+}
 
-	dialog = gtk_dialog_new_with_buttons(_("File Transfer"), NULL,
-				GTK_DIALOG_NO_SEPARATOR,
-				GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
+/* Return the local absolute filename for the file, and its size
+ * if the file exists and is stat()'able */
+static gchar *normalise_filename(const gchar *filename, gint64 *size)
+{
+	char *ret;
+	struct stat file_stat;
 
-	gtk_window_set_default_size(GTK_WINDOW(dialog), 400, -1);
+	g_return_val_if_fail(filename != NULL, NULL);
+	g_return_val_if_fail(size != NULL, NULL);
 
-	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
+	if (g_str_has_prefix(filename, "file://")) {
+		ret = g_filename_from_uri(filename, NULL, NULL);
+		if (ret == NULL)
+			return NULL;
+	} else if (filename[0] == '/') {
+		ret = g_strdup(filename);
+	} else {
+		gchar *curdir;
+
+		curdir = g_get_current_dir();
+		ret = g_build_filename(curdir, filename, NULL);
+		g_free(curdir);
+	}
 
-	vbox = gtk_vbox_new(FALSE, 6);
-	gtk_container_set_border_width(GTK_CONTAINER(vbox), 12);
-	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox);
+	if (!g_file_test(ret, G_FILE_TEST_IS_REGULAR)) {
+		g_free(ret);
+		return NULL;
+	}
 
-	label = gtk_label_new(NULL);
-	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+	if (!g_stat(ret, &file_stat)) {
+		*size = file_stat.st_size;
+	} else {
+		g_free(ret);
+		return NULL;
+	}
 
-	label_filename = label;
+	return ret;
+}
 
-	progress = gtk_progress_bar_new();
-	gtk_box_pack_start(GTK_BOX(vbox), progress, FALSE, FALSE, 0);
+static gchar *get_device_name(void)
+{
+	DBusGConnection *connection;
+	DBusGProxy *manager;
+	GError *error = NULL;
+	gchar *name, **adapters;
+	guint i;
 
-	label = gtk_label_new(NULL);
-	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
-	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
-	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 6);
+	name = NULL;
 
-	label_status = label;
+	connection = dbus_g_bus_get(DBUS_BUS_SYSTEM, NULL);
+	if (connection == NULL)
+		return NULL;
+
+	manager = dbus_g_proxy_new_for_name(connection, "org.bluez",
+					    "/org/bluez", "org.bluez.Manager");
+	if (!manager) {
+		dbus_g_connection_unref(connection);
+		return NULL;
+	}
 
-	text = g_strdup_printf("<b>%s</b>", filename);
-	gtk_label_set_markup(GTK_LABEL(label_filename), text);
-	g_free(text);
+	if (!manager_list_adapters(manager, &adapters, &error)) {
+		g_object_unref(manager);
+		dbus_g_connection_unref(connection);
+		return NULL;
+	}
 
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Connecting..."));
+	for (i = 0; adapters[i] != NULL; i++) {
+		DBusGProxy *adapter;
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, FALSE);
+		adapter = dbus_g_proxy_new_for_name(connection, "org.bluez",
+						    adapters[i], "org.bluez.Adapter");
+		if (dbus_g_proxy_call(adapter, "GetRemoteName", NULL,
+				      G_TYPE_STRING, bdaddrstr, G_TYPE_INVALID,
+				      G_TYPE_STRING, &name, G_TYPE_INVALID)) {
+			if (name != NULL && name[0] != '\0') {
+				g_object_unref(adapter);
+				break;
+			}
+		}
+		g_object_unref(adapter);
+	}
 
-	g_signal_connect(G_OBJECT(dialog), "response",
-				G_CALLBACK(response_callback), NULL);
+	g_strfreev(adapters);
+	g_object_unref(manager);
+	dbus_g_connection_unref(connection);
 
-	gtk_widget_show_all(dialog);
+	return name;
 }
 
-static void transfer_started(DBusGProxy *proxy, gchar *a, gchar *b,
-					guint64 size, gpointer user_data)
+static gint show_error_dialog(GtkWindow *parent, ButtonSet buttons,
+			      const gchar *primary_text, const gchar *secondary_text)
 {
-	gchar *text;
+	GtkWidget *dialog;
+	gchar *primary_text_markup;
+	gint ret;
+
+	primary_text_markup = g_strdup_printf("<span weight=\"bold\" size=\"larger\">%s</span>", 
+					      primary_text);
+	dialog = gtk_message_dialog_new_with_markup(parent, 0, GTK_MESSAGE_ERROR, 
+						    buttons==BUTTONS_OK? GTK_BUTTONS_OK: GTK_BUTTONS_CANCEL, 
+						    primary_text_markup);
+	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
+						 secondary_text);
+	if (buttons == BUTTONS_RETRY_SKIP_CANCEL) {
+		gtk_dialog_add_button(GTK_DIALOG(dialog), _("Skip"),
+				      DIALOG_RESPONSE_SKIP);
+	}
+	if (buttons != BUTTONS_OK) {
+		gtk_dialog_add_button(GTK_DIALOG(dialog), _("Retry"),
+				      DIALOG_RESPONSE_RETRY);
+	}
+	ret = gtk_dialog_run(GTK_DIALOG(dialog));
 
-	filesize = size;
+	gtk_widget_destroy(dialog);
+	g_free(primary_text_markup);
+	return ret;
+}
 
-	text = g_strdup_printf(_("Starting transfer of %jd bytes"), size);
-	gtk_label_set_markup(GTK_LABEL(label_status), text);
-	g_free(text);
+static gint show_connection_error_dialog(GtkWindow *parent, const gchar *bdaddr,
+					 const gchar *primary_text)
+{
+	if (!is_palm_device(bdaddrstr)) {
+		return show_error_dialog(parent,
+					 BUTTONS_RETRY_CANCEL,
+					 primary_text,
+					 _("Make sure that remote device is on and that it accepts Bluetooth connections."));
+	} else {
+		return show_error_dialog(parent,
+					 BUTTONS_RETRY_CANCEL,
+					 primary_text,
+					 _("Make sure \"Beam receive\" is enabled in the Power preferences, and Bluetooth is enabled, on your Palm device."));
+	}
+}
+
+static gint handle_file_sending_error(const gchar *error_text,
+				      const gchar *error_secondary_text)
+{
+	ButtonSet buttons;
+	gint response_id;
 
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0.0);
+	if (file_count > 1 && current_file < file_count - 1)
+		buttons = BUTTONS_RETRY_SKIP_CANCEL;
+	else
+		buttons = BUTTONS_RETRY_CANCEL;
+
+	response_id = show_error_dialog(GTK_WINDOW(main_dialog), buttons, 
+					error_text, error_secondary_text);
+
+	switch (response_id) {
+	case DIALOG_RESPONSE_SKIP:
+		current_file++;
+		bytes_sent += file_length;
+	case DIALOG_RESPONSE_RETRY:
+		return TRUE;
+	case GTK_RESPONSE_CANCEL:
+	default:
+		gtk_main_quit();
+		break;
+	}
+	return response_id;
 }
 
-static void transfer_progress(DBusGProxy *proxy,
-					guint64 bytes, gpointer user_data)
+static void cancel_clicked_cb(GtkWidget *button, gpointer user_data)
 {
+	gtk_main_quit();
+}
+
+static void ui_init(void)
+{
+	GtkWidget *vbox, *vbox_parent, *table;
+	GtkWidget *label1, *label2, *label3, *target_label;
+	GtkWidget *button;
 	gchar *text;
-	gdouble fraction;
 
-	text = g_strdup_printf(_("Transfered %jd of %jd bytes"),
-							bytes, filesize);
-	gtk_label_set_markup(GTK_LABEL(label_status), text);
+	device_name = get_device_name();
+
+	/* main dialog */
+	main_dialog = gtk_dialog_new();
+	gtk_dialog_set_has_separator(GTK_DIALOG(main_dialog), FALSE);
+	gtk_window_set_title(GTK_WINDOW(main_dialog),
+			     _("Bluetooth file transfer"));
+	gtk_window_set_icon_name(GTK_WINDOW(main_dialog), "stock_bluetooth");
+	gtk_window_set_type_hint(GTK_WINDOW(main_dialog),
+				 GDK_WINDOW_TYPE_HINT_NORMAL);
+	gtk_window_set_position(GTK_WINDOW(main_dialog), GTK_WIN_POS_CENTER);
+	gtk_window_set_resizable(GTK_WINDOW(main_dialog), FALSE);
+	gtk_widget_set(main_dialog, "width-request", 400, NULL);
+	button = gtk_dialog_add_button(GTK_DIALOG(main_dialog),
+				       GTK_STOCK_CANCEL,
+				       GTK_RESPONSE_CANCEL);
+	g_signal_connect(G_OBJECT(button), "clicked",
+			 G_CALLBACK(cancel_clicked_cb), NULL);
+	gtk_container_set_border_width(GTK_CONTAINER(main_dialog), 6);
+
+	/* table elements */
+	from_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(from_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(from_label), PANGO_ELLIPSIZE_MIDDLE);
+	target_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(target_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(target_label), PANGO_ELLIPSIZE_END);
+	gtk_label_set_text(GTK_LABEL(target_label), device_name ? device_name : bdaddrstr);
+	label2 = gtk_label_new(NULL);
+	text = g_markup_printf_escaped("<b>%s</b>", _("From:"));
+	gtk_label_set_markup(GTK_LABEL(label2), text);
+	g_free(text);
+	label3 = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(label3), 1, 0.5);
+	text = g_markup_printf_escaped("<b>%s</b>", _("To:"));
+	gtk_label_set_markup(GTK_LABEL(label3), text);
 	g_free(text);
 
-	fraction = (gdouble) bytes / (gdouble) filesize;
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), fraction);
+	/* table */
+	table = gtk_table_new(2, 2, FALSE);
+	gtk_table_set_col_spacings(GTK_TABLE(table), 4);
+	gtk_table_set_row_spacings(GTK_TABLE(table), 4);
+	gtk_table_attach_defaults(GTK_TABLE(table), from_label, 1, 2, 0, 1);
+	gtk_table_attach_defaults(GTK_TABLE(table), target_label, 1, 2, 1, 2);
+	gtk_table_attach(GTK_TABLE(table), label2, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), label3, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+
+	/* vbox elements */
+	label1 = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(label1), 0, 0.5);
+	text = g_markup_printf_escaped("<span weight=\"bold\" size=\"larger\">%s</span>",
+				       _("Sending files via bluetooth"));
+	gtk_label_set_markup(GTK_LABEL(label1), text);
+	g_free(text);
+	progress_bar = gtk_progress_bar_new();
+	gtk_progress_bar_set_ellipsize(GTK_PROGRESS_BAR(progress_bar),
+				       PANGO_ELLIPSIZE_END);
+	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
+				  _("Connecting..."));
+	operation_label = gtk_label_new(NULL);
+	gtk_misc_set_alignment(GTK_MISC(operation_label), 0, 0.5);
+	gtk_label_set_ellipsize(GTK_LABEL(operation_label), PANGO_ELLIPSIZE_END);
+
+	/* vbox */
+	vbox = gtk_vbox_new(FALSE, 0);
+	gtk_box_set_spacing(GTK_BOX(vbox), 6);
+	gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
+	gtk_box_pack_start_defaults(GTK_BOX(vbox), label1);
+	gtk_box_pack_start(GTK_BOX(vbox), table, TRUE, TRUE, 9);
+	gtk_box_pack_start_defaults(GTK_BOX(vbox), progress_bar);
+	gtk_box_pack_start(GTK_BOX(vbox), operation_label, TRUE, TRUE, 2);
+	vbox_parent = gtk_bin_get_child(GTK_BIN(main_dialog));
+	gtk_box_pack_start_defaults(GTK_BOX(vbox_parent), vbox);
+
+	gtk_widget_show_all(main_dialog);
 }
 
-static void transfer_completed(DBusGProxy *proxy, gpointer user_data)
+static gboolean send_one(gpointer user_data)
 {
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Completed"));
+	const gchar *fname;
+	gint ret;
+	gchar *bname, *dirname;
+	GError *err = NULL;
+	gchar *operation_text, *operation_markup, *progressbar_text;
+
+	if ((fname = g_slist_nth_data(file_list, current_file))) {
+		/* there's a file to send */
+		dbus_g_proxy_call(session_proxy, "SendFile", &err,
+				  G_TYPE_STRING, fname, G_TYPE_INVALID, G_TYPE_INVALID);
+
+		if (err != NULL) {
+			ret = handle_file_sending_error(_("Unable to read file"),
+							err->message);
+			if (ret != GTK_RESPONSE_CANCEL) {
+				g_idle_add((GSourceFunc) send_one, NULL);
+			}
+			g_error_free(err);
+			return FALSE;
+		}
 
-	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 1.0);
+		if (!first_transfer_time) {
+			first_transfer_time = get_system_time();
+		}
+		bname = g_path_get_basename(fname);
+		dirname = g_path_get_dirname(fname);
 
-	dbus_g_proxy_call(proxy, "Disconnect", NULL, G_TYPE_INVALID,
-							G_TYPE_INVALID);
+		operation_text = g_strdup_printf(_("Sending %s"), bname);
+		operation_markup = g_markup_printf_escaped("<i>%s</i>", operation_text);
+		progressbar_text = g_strdup_printf(_("Sending file: %d of %d"),
+						   current_file+1,
+						   file_count);
+		gtk_label_set_text(GTK_LABEL(from_label), dirname);
+		gtk_label_set_markup(GTK_LABEL(operation_label), operation_markup);
+		gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
+					  progressbar_text);
+
+		g_free(bname);
+		g_free(dirname);
+		g_free(operation_text);
+		g_free(operation_markup);
+		g_free(progressbar_text);
+	} else {
+		/* nothing left to send, time to quit */
+		gtk_main_quit();
+	}
+	return FALSE;
+}
 
-	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, TRUE);
+static void session_connected_cb(DBusGProxy *proxy, gpointer user_data)
+{
+	/* OBEX connect has succeeded, so start sending */
+	g_idle_add((GSourceFunc) send_one, NULL);
 }
 
-static void session_connected(DBusGProxy *proxy, gpointer user_data)
+static void transfer_started_cb(DBusGProxy *proxy, const gchar *filename,
+				const gchar *local_path, guint64 byte_count, gpointer user_data)
 {
-	gchar *filename = user_data;
-	GError *error = NULL;
+	file_length = byte_count;
+}
 
-	gtk_label_set_markup(GTK_LABEL(label_status), _("Connected"));
+static void transfer_cancelled_cb(DBusGProxy *proxy, gpointer user_data)
+{
+	gint ret;
+	gchar *error_text;
 
-	dbus_g_proxy_call(proxy, "SendFile", &error,
-				G_TYPE_STRING, filename, G_TYPE_INVALID,
-							G_TYPE_INVALID);
-
-	if (error != NULL) {
-		g_printerr("Sending of file %s failed: %s\n", filename,
-							error->message);
-		g_error_free(error);
-		gtk_main_quit();
+	error_text = g_strdup_printf(_("An error occured while sending file '%s'"),
+				     (gchar *)g_slist_nth_data(file_list, current_file));
+	ret = handle_file_sending_error(error_text,
+					_("The remote device cancelled the transfer"));
+
+	if (ret != GTK_RESPONSE_CANCEL)
+		g_idle_add((GSourceFunc) send_one, NULL);
+
+	g_free(error_text);
+}
+
+static void transfer_progress_cb(DBusGProxy *proxy, guint64 bytes_transferred, gpointer user_data)
+{
+	gdouble frac;
+	guint actual_bytes_sent;
+	gint elapsed_time;
+	gint transfer_rate;
+	gint time_remaining;
+	gint64 current_time;
+	gchar *str, *str2;
+	gchar *progressbar_text;
+
+	/* update progress bar fraction */
+	actual_bytes_sent = bytes_sent + bytes_transferred;
+	frac = (gdouble)actual_bytes_sent / byte_count;
+	gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress_bar), frac);
+
+	/* update progress bar text (time remaining)
+	 * (only update 4 times in a second) */
+	current_time = get_system_time();
+	elapsed_time = (current_time - first_transfer_time) / MSEC_PER_SEC;
+
+	if (last_update_time) {
+		if (current_time < last_update_time + MSEC_PER_SEC / 4)
+			return;
+		else
+			last_update_time = current_time;
+	} else {
+		last_update_time = current_time;
 	}
+
+	if (!elapsed_time)
+		return;
+
+	transfer_rate = actual_bytes_sent / elapsed_time;
+	if (transfer_rate == 0)
+		return;
+
+	time_remaining = (byte_count - actual_bytes_sent) / transfer_rate;
+
+	if (time_remaining >= 3600) {
+		str = g_strdup_printf(_("(%d:%02d:%d Remaining)"), 
+				      time_remaining / 3600,
+				      (time_remaining % 3600) / 60,
+				      (time_remaining % 3600) % 60);
+	} else {
+		str = g_strdup_printf(_("(%d:%02d Remaining)"), 
+				      time_remaining / 60,
+				      time_remaining % 60);
+	}
+
+	/* Translators:
+	 * Sending file 1 of 3 */
+	str2 = g_strdup_printf(_("Sending file %d of %d"),
+			       current_file + 1,
+			       file_count);
+	progressbar_text = g_strconcat(str2, " ", str, NULL);
+	gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress_bar),
+				  progressbar_text);
+	g_free(str);
+	g_free(str2);
+	g_free(progressbar_text);
 }
 
-static void create_notify(DBusGProxy *proxy,
-				DBusGProxyCall *call, void *user_data)
+static void transfer_completed_cb(DBusGProxy *proxy, gpointer user_data)
+{
+	current_file++;
+	bytes_sent += file_length;
+
+	g_idle_add((GSourceFunc) send_one, NULL);
+}
+
+static void session_created_cb(DBusGProxy *proxy, DBusGProxyCall *call, gpointer user_data)
 {
 	GError *error = NULL;
 	const gchar *path = NULL;
 
-	if (dbus_g_proxy_end_call(proxy, call, &error,
-					DBUS_TYPE_G_OBJECT_PATH, &path,
-						G_TYPE_INVALID) == FALSE) {
-		gchar *text, *message;
-
-		if (error != NULL) {
-			message = g_strdup(error->message);
-			g_error_free(error);
-		} else
-			message = g_strdup(_("An unknown error occured"));
-
-		text = g_strdup_printf("<span foreground=\"red\">%s</span>",
-								message);
-		gtk_label_set_markup(GTK_LABEL(label_status), text);
-		g_free(text);
-
-		g_free(message);
+	if (!dbus_g_proxy_end_call(proxy, call, &error,
+				   DBUS_TYPE_G_OBJECT_PATH, &path, G_TYPE_INVALID)) {
+		const gchar *error_name = NULL;
+		gint response_id;
+
+		if (error && error->code == DBUS_GERROR_REMOTE_EXCEPTION)
+			error_name = dbus_g_error_get_name(error);
+		if (error_name && !strcmp(error_name, "org.openobex.Error.ConnectionAttemptFailed")) {
+			response_id = show_connection_error_dialog(GTK_WINDOW(main_dialog), bdaddrstr, error->message);
+			if (response_id == DIALOG_RESPONSE_RETRY) {
+				/* Try to create Session again */
+				dbus_g_proxy_begin_call(manager_proxy,
+							"CreateBluetoothSession",
+							(DBusGProxyCallNotify) session_created_cb, NULL, NULL,
+							G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+							G_TYPE_BOOLEAN, FALSE, G_TYPE_INVALID);
+				goto out;
+			}
+		} else {
+			show_error_dialog(GTK_WINDOW(main_dialog), BUTTONS_OK,
+					  error->message, "");
+		}
 
-		gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
-						GTK_RESPONSE_CLOSE, TRUE);
-		return;
+		gtk_main_quit();
+		goto out;
 	}
 
-	proxy = dbus_g_proxy_new_for_name(conn, "org.openobex",
-						path, "org.openobex.Session");
+	session_proxy = dbus_g_proxy_new_for_name(connection,
+						       "org.openobex",
+						       path,
+						       "org.openobex.Session");
 
-	dbus_g_proxy_add_signal(proxy, "Connected", G_TYPE_INVALID);
+	dbus_g_proxy_add_signal(session_proxy, "Connected",
+				G_TYPE_INVALID);
+
+	dbus_g_proxy_connect_signal(session_proxy, "Connected",
+				    G_CALLBACK(session_connected_cb), NULL, NULL);
+
+	dbus_g_proxy_add_signal(session_proxy, "TransferStarted",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);
+
+	dbus_g_proxy_connect_signal(session_proxy, "TransferStarted",
+				    G_CALLBACK(transfer_started_cb), NULL, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "Connected",
-				G_CALLBACK(session_connected), user_data, NULL);
+	dbus_g_proxy_add_signal(session_proxy, "Cancelled",
+				G_TYPE_INVALID);
+
+	dbus_g_proxy_connect_signal(session_proxy, "Cancelled",
+				    G_CALLBACK(transfer_cancelled_cb), NULL, NULL);
 
-	dbus_g_proxy_add_signal(proxy, "TransferStarted", G_TYPE_STRING,
-				G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);
+	dbus_g_proxy_add_signal(session_proxy, "ErrorOccurred",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferStarted",
-				G_CALLBACK(transfer_started), NULL, NULL);
+	dbus_g_proxy_connect_signal(session_proxy, "ErrorOccurred",
+				    G_CALLBACK(error_occurred_cb), NULL, NULL);
 
-	dbus_g_proxy_add_signal(proxy, "TransferProgress",
-						G_TYPE_UINT64, G_TYPE_INVALID);
+	dbus_g_proxy_add_signal(session_proxy, "TransferProgress",
+				G_TYPE_UINT64, G_TYPE_INVALID);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferProgress",
-				G_CALLBACK(transfer_progress), NULL, NULL);
+	dbus_g_proxy_connect_signal(session_proxy, "TransferProgress",
+				    G_CALLBACK(transfer_progress_cb), NULL, NULL);
+
+	dbus_g_proxy_add_signal(session_proxy, "TransferCompleted", 
+				G_TYPE_INVALID);
 
-	dbus_g_proxy_add_signal(proxy, "TransferCompleted", G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(session_proxy, "TransferCompleted",
+				    G_CALLBACK(transfer_completed_cb), NULL, NULL);
 
-	dbus_g_proxy_connect_signal(proxy, "TransferCompleted",
-				G_CALLBACK(transfer_completed), NULL, NULL);
+	dbus_g_proxy_call(session_proxy, "Connect", &error, G_TYPE_INVALID,
+			  G_TYPE_INVALID);
 
-	dbus_g_proxy_call(proxy, "Connect", NULL, G_TYPE_INVALID,
-							G_TYPE_INVALID);
+out:
+	if (error)
+		g_error_free(error);
+}
+
+static void error_occurred_cb(DBusGProxy *proxy, const gchar *error_name, const gchar *error_message, gpointer user_data)
+{
+	gint ret;
+	gboolean link_error = FALSE;
+	gchar *error_text;
+	const gchar *reason;
+
+	g_message("ErrorOccurred");
+	g_message("Error name: %s", error_name);
+	g_message("Error message: %s", error_message);
+
+	error_text = g_strdup_printf(_("An error occured while sending file '%s'"),
+				     (gchar *)g_slist_nth_data(file_list, current_file));
+	if (!strcmp(error_name, "org.openobex.Error.LinkError")) {
+		g_object_unref(G_OBJECT(session_proxy));
+		reason = _("Connection to remote device was lost");
+		link_error = TRUE;
+	} else if (!strcmp(error_name, "org.openobex.Error.Forbidden")) {
+		reason = _("Remote device rejected file");
+	} else {
+		reason = _("Unknown error");
+	}
+	ret = handle_file_sending_error(error_text, reason);
+
+	if (ret != GTK_RESPONSE_CANCEL) {
+		if (link_error) {
+			/* Need to reestablish connection */
+			dbus_g_proxy_begin_call(manager_proxy, "CreateBluetoothSession",
+						(DBusGProxyCallNotify) session_created_cb, NULL, NULL,
+						G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
+						G_TYPE_INVALID);
+		} else {
+			g_idle_add((GSourceFunc) send_one, NULL);
+		}
+	}
+
+
+	g_free(error_text);
+}
+
+static void free_mem(void)
+{
+	if (files_to_send)
+		g_strfreev(files_to_send);
+	g_free(bdaddrstr);
+
+	if (main_dialog)
+		gtk_widget_destroy (main_dialog);
+	if (G_IS_OBJECT(manager_proxy))
+		g_object_unref(G_OBJECT(manager_proxy));
+	if (G_IS_OBJECT(session_proxy))
+		g_object_unref (G_OBJECT(session_proxy));
+	g_slist_free(file_list);
 }
 
 static void name_owner_changed(DBusGProxy *proxy, const char *name,
@@ -227,102 +648,121 @@ static void name_owner_changed(DBusGProx
 		gtk_main_quit();
 }
 
-static gchar *option_device = NULL;
-
-static GOptionEntry options[] = {
-	{ "device", 0, 0, G_OPTION_ARG_STRING, &option_device,
-				N_("Remote device to use"), "ADDRESS" },
-	{ NULL },
-};
-
 int main(int argc, char *argv[])
 {
-	DBusGProxy *proxy;
+	GOptionContext *option_context;
 	GError *error = NULL;
-	GSList *filenames;
-	gchar *filename, *address;
+	guint i;
 
 	bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
 	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
 	textdomain(GETTEXT_PACKAGE);
 
-	error = NULL;
+	option_context = g_option_context_new("<file list>");
+	g_option_context_add_main_entries(option_context, options, GETTEXT_PACKAGE);
+	g_option_context_add_group(option_context, gtk_get_option_group(TRUE));
+	g_option_context_parse(option_context, &argc, &argv, &error);
+	g_option_context_free(option_context);
+	if (error) {
+		printf("Usage:\n");
+		printf("  %s [--dest=BDADDR] <file list>\n", g_get_prgname());
+		g_error_free(error);
+		free_mem();
+		return 0;
+	}
 
-	if (gtk_init_with_args(&argc, &argv, "[FILE...]",
-				options, GETTEXT_PACKAGE, &error) == FALSE) {
-		if (error != NULL) {
-			g_printerr("%s\n", error->message);
-			g_error_free(error);
-		} else
-			g_printerr("An unknown error occurred\n");
-
-		gtk_exit(1);
-	}
-
-	gtk_window_set_default_icon_name("bluetooth");
-
-	if (argc < 2) {
-		filenames = show_select_dialog();
-		if (filenames == NULL)
-			gtk_exit(1);
-	} else
-		filenames = g_slist_append(NULL, g_strdup(argv[1]));
-
-	if (option_device == NULL) {
-		address = show_browse_dialog();
-		if (address == NULL) {
-			g_slist_free(filenames);
-			gtk_exit(1);
-		}
-	} else
-		address = g_strdup(option_device);
+	gtk_init(&argc, &argv);
+
+	connection = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
+	if (!connection) {
+		show_error_dialog(NULL, GTK_BUTTONS_OK,
+				  _("Please verify that D-Bus is correctly installed and setup."), error->message);
+		g_error_free(error);
+		return 1;
+	}
 
-	conn = dbus_g_bus_get(DBUS_BUS_SESSION, &error);
-	if (conn == NULL) {
-		if (error != NULL) {
-			g_printerr("Connecting to session bus failed: %s\n",
-							error->message);
-			g_error_free(error);
-		} else
-			g_print("An unknown error occured\n");
+	main_dialog = NULL;
+	from_label = NULL;
+	operation_label = NULL;
+	progress_bar = NULL;
+
+	/* get a list of files to send from command-line */
+	if (files_to_send)
+		file_count = g_strv_length(files_to_send);
+	else
+		file_count = 0;
+	for (i = 0; i < file_count; i++) {
+		file_list = g_slist_append(file_list,
+						g_strdup(*(files_to_send + i)));
+	}
+	/* show file chooser if no files were specified */
+	if (!file_count) {
+		file_list = show_select_dialog();
+		file_count = g_slist_length(file_list);
+	}
+	/* Check if there are some files to send */
+	if (!file_count) {
+		free_mem();
+		return 0;
+	}
+	/* Check for non regular and non existand files and
+	 * determine total bytes to send */
+	for (i = 0; i < file_count; i++) {
+		GSList *item;
+		gchar *path, *filename;
+		gint64 size;
+
+		item = g_slist_nth(file_list, i);
+		path = (gchar *) item->data;
+		filename = normalise_filename(path, &size);
+		if (filename == NULL) {
+			g_free(path);
+			file_list = g_slist_remove_link(file_list, item);
+			continue;
+		}
+		g_free(item->data);
+		item->data = filename;
+		byte_count += size;
+	}
 
-		gtk_exit(1);
+	/* determine Bluetooth device to send to */
+	if (!bdaddrstr) {
+		bdaddrstr = show_browse_dialog();
+		if (!bdaddrstr) {
+			free_mem();
+			return 0;
+		}
 	}
 
 	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING_UINT64,
-				G_TYPE_NONE, G_TYPE_STRING,
-				G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);
-
+					  G_TYPE_NONE, G_TYPE_STRING,
+					  G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INVALID);
+	dbus_g_object_register_marshaller(marshal_VOID__STRING_STRING,
+					  G_TYPE_NONE, G_TYPE_STRING,
+					  G_TYPE_STRING, G_TYPE_INVALID);
 	dbus_g_object_register_marshaller(marshal_VOID__UINT64,
 				G_TYPE_NONE, G_TYPE_UINT64, G_TYPE_INVALID);
 
-	filename = g_slist_nth_data(filenames, 0);
-
-	create_window(filename);
+	ui_init();
 
-	proxy = dbus_g_proxy_new_for_name(conn, "org.openobex",
-				"/org/openobex", "org.openobex.Manager");
-
-	dbus_g_proxy_add_signal(proxy, "NameOwnerChanged",
-		G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
-
-	dbus_g_proxy_connect_signal(proxy, "NameOwnerChanged",
-				G_CALLBACK(name_owner_changed), NULL, NULL);
-
-	dbus_g_proxy_begin_call(proxy, "CreateBluetoothSession",
-				create_notify, filename, NULL,
-				G_TYPE_STRING, address, G_TYPE_STRING, "opp",
+	manager_proxy = dbus_g_proxy_new_for_name(connection,
+						       "org.openobex",
+						       "/org/openobex",
+						       "org.openobex.Manager");
+	dbus_g_proxy_add_signal(manager_proxy, "NameOwnerChanged",
+				G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
+	dbus_g_proxy_connect_signal(manager_proxy, "NameOwnerChanged",
+				    G_CALLBACK(name_owner_changed), NULL, NULL);
+
+	dbus_g_proxy_begin_call(manager_proxy, "CreateBluetoothSession",
+				(DBusGProxyCallNotify) session_created_cb, NULL, NULL,
+				G_TYPE_STRING, bdaddrstr, G_TYPE_STRING, "opp",
 				G_TYPE_INVALID);
 
 	gtk_main();
 
-	g_object_unref(proxy);
-
-	dbus_g_connection_unref(conn);
-
-	g_free(address);
-
-	g_slist_free(filenames);
+	free_mem();
 
 	return 0;
 }
+

[-- Attachment #3: Type: text/plain, Size: 228 bytes --]

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/

[-- Attachment #4: Type: text/plain, Size: 164 bytes --]

_______________________________________________
Bluez-devel mailing list
Bluez-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bluez-devel

^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2008-02-27  1:28 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-01-17 10:43 [Bluez-devel] [PATCH] Allow multiple files to be sent Bastien Nocera
2008-01-18 13:43 ` Bastien Nocera
2008-02-01 13:14   ` Bastien Nocera
2008-02-01 16:08     ` Bastien Nocera
2008-02-01 16:20       ` Bastien Nocera
2008-02-06 12:18         ` [Bluez-devel] [PATCH] Updated sendto Bastien Nocera
2008-02-06 13:08           ` Bastien Nocera
2008-02-07  0:59             ` Bastien Nocera
2008-02-23  1:30               ` Bastien Nocera
2008-02-24 18:29                 ` Bastien Nocera
2008-02-25  2:21                   ` Marcel Holtmann
2008-02-25 10:56                     ` Bastien Nocera
2008-02-25 20:36                       ` Marcel Holtmann
2008-02-26  1:18                         ` Bastien Nocera
2008-02-26  1:21                           ` Marcel Holtmann
2008-02-27  1:28                             ` Bastien Nocera

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox