commit 483411ee2d14ab1931032fb778fd0aee9adde762
Author: Christophe Fergeau <christophe@anevia.com>
Date:   Fri Oct 3 11:24:26 2008 +0200

    Add code to configure new ipods

diff --git a/plugins/ipod/Makefile.am b/plugins/ipod/Makefile.am
index c391390..5f10b58 100644
--- a/plugins/ipod/Makefile.am
+++ b/plugins/ipod/Makefile.am
@@ -46,7 +46,10 @@ uixml_DATA = ipod-ui.xml
 
 plugin_DATA = $(plugin_in_files:.rb-plugin.in=.rb-plugin)
 
-EXTRA_DIST = $(uixml_DATA) $(plugin_in_files)
+gladedir = $(plugindir)
+glade_DATA = ipod_init.glade
+
+EXTRA_DIST = $(uixml_DATA) $(plugin_in_files) $(glade_DATA)
 
 CLEANFILES = $(plugin_DATA)
 DISTCLEANFILES = $(plugin_DATA)
diff --git a/plugins/ipod/ipod_init.glade b/plugins/ipod/ipod_init.glade
new file mode 100644
index 0000000..4cdbec8
--- /dev/null
+++ b/plugins/ipod/ipod_init.glade
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--*- mode: xml -*-->
+<glade-interface>
+  <widget class="GtkDialog" id="ipod_init">
+    <property name="visible">True</property>
+    <property name="title" translatable="yes">iPod detected</property>
+    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+    <child internal-child="vbox">
+      <widget class="GtkVBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <child>
+          <widget class="GtkTable" id="table1">
+            <property name="visible">True</property>
+            <property name="border_width">12</property>
+            <property name="n_rows">4</property>
+            <property name="n_columns">2</property>
+            <property name="column_spacing">12</property>
+            <property name="row_spacing">12</property>
+            <child>
+              <widget class="GtkEntry" id="name_entry">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+              </widget>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">2</property>
+                <property name="top_attach">3</property>
+                <property name="bottom_attach">4</property>
+                <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkComboBox" id="model_combo">
+                <property name="visible">True</property>
+              </widget>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">2</property>
+                <property name="top_attach">2</property>
+                <property name="bottom_attach">3</property>
+                <property name="x_options">GTK_FILL</property>
+                <property name="y_options">GTK_FILL</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkLabel" id="name_label">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">_Name:</property>
+                <property name="use_underline">True</property>
+                <property name="mnemonic_widget">name_entry</property>
+              </widget>
+              <packing>
+                <property name="top_attach">3</property>
+                <property name="bottom_attach">4</property>
+                <property name="x_options">GTK_FILL</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkLabel" id="model_label">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">_Model:</property>
+                <property name="use_underline">True</property>
+                <property name="mnemonic_widget">model_combo</property>
+              </widget>
+              <packing>
+                <property name="top_attach">2</property>
+                <property name="bottom_attach">3</property>
+                <property name="x_options">GTK_FILL</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkLabel" id="text">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">Rhythmbox has detected a device that is probably an uninitialised or corrupted iPod. It must be initialised before Rhythmbox can use it, but this will destroy any song metadata already present. If you wish Rhythmbox to initialise the iPod, please fill in the information below. If the device is not an iPod, or you do not wish to initialise it, please click cancel.</property>
+                <property name="wrap">True</property>
+                <property name="selectable">True</property>
+              </widget>
+              <packing>
+                <property name="right_attach">2</property>
+                <property name="top_attach">1</property>
+                <property name="bottom_attach">2</property>
+                <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property>
+                <property name="y_options"></property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkLabel" id="dialog_header">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">&lt;span size="x-large" weight="bold"&gt;Do you want to initialize your iPod?&lt;/span&gt;</property>
+                <property name="use_markup">True</property>
+              </widget>
+              <packing>
+                <property name="right_attach">2</property>
+                <property name="x_options">GTK_FILL</property>
+                <property name="y_options">GTK_FILL</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <widget class="GtkHButtonBox" id="dialog-action_area1">
+            <property name="visible">True</property>
+            <property name="layout_style">GTK_BUTTONBOX_END</property>
+            <child>
+              <widget class="GtkButton" id="cancel_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="label">gtk-cancel</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">-6</property>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkButton" id="initialise_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="label" translatable="yes">_Initialise</property>
+                <property name="use_underline">True</property>
+                <property name="response_id">-3</property>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">GTK_PACK_END</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+</glade-interface>
diff --git a/plugins/ipod/rb-ipod-plugin.c b/plugins/ipod/rb-ipod-plugin.c
index 616d7db..7077aaf 100644
--- a/plugins/ipod/rb-ipod-plugin.c
+++ b/plugins/ipod/rb-ipod-plugin.c
@@ -224,9 +224,11 @@ rb_ipod_plugin_source_deleted (RBiPodSource *source, RBIpodPlugin *plugin)
 static RBSource *
 create_source_cb (RBRemovableMediaManager *rmm, GMount *mount, RBIpodPlugin *plugin)
 {
-	if (rb_ipod_is_mount_ipod (mount)) {
+	if (rb_ipod_is_mount_ipod (mount, RB_PLUGIN (plugin))) {
 		RBSource *src;
-		src = RB_SOURCE (rb_ipod_source_new (plugin->shell, mount));
+		src = RB_SOURCE (rb_ipod_source_new (plugin->shell,
+                                                     RB_PLUGIN (plugin),
+                                                     mount));
 
 		plugin->ipod_sources = g_list_prepend (plugin->ipod_sources, src);
 		g_signal_connect_object (G_OBJECT (src),
diff --git a/plugins/ipod/rb-ipod-source.c b/plugins/ipod/rb-ipod-source.c
index af405c8..28ccde2 100644
--- a/plugins/ipod/rb-ipod-source.c
+++ b/plugins/ipod/rb-ipod-source.c
@@ -31,6 +31,7 @@
 
 #include <errno.h>
 #include <string.h>
+#include <sys/statvfs.h>
 
 #include <glib/gi18n.h>
 #include <glib/gstdio.h>
@@ -51,6 +52,8 @@
 #include "rb-ipod-static-playlist-source.h"
 #include "rb-util.h"
 #include "rhythmdb.h"
+#include "rb-dialog.h"
+#include "rb-glade-helpers.h"
 #include "rb-cut-and-paste-code.h"
 
 #define CONF_STATE_PANED_POSITION CONF_PREFIX "/state/ipod/paned_position"
@@ -267,6 +270,7 @@ rb_ipod_source_dispose (GObject *object)
 
 RBRemovableMediaSource *
 rb_ipod_source_new (RBShell *shell,
+                    RBPlugin *plugin,
 		    GMount *mount)
 {
 	RBiPodSource *source;
@@ -276,8 +280,6 @@ rb_ipod_source_new (RBShell *shell,
 	char *name;
 	char *path;
 
-	g_assert (rb_ipod_is_mount_ipod (mount));
-
 	volume = g_mount_get_volume (mount);
 	path = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
 	g_object_unref (volume);
@@ -964,7 +966,7 @@ rb_ipod_get_itunesdb_path (GMount *mount)
 	if (root != NULL) {
 		mount_point = g_file_get_path (root);
 		if (mount_point != NULL) {
-			result = itdb_get_itunes_dir (mount_point);
+			result = itdb_get_itunesdb_path (mount_point);
 		}
 
 		g_free (mount_point);
@@ -974,6 +976,281 @@ rb_ipod_get_itunesdb_path (GMount *mount)
 	return result;
 }
 
+enum {
+      COL_INFO = 0,
+};
+
+static gchar *ipod_info_to_string (const Itdb_IpodInfo *info)
+{
+	if (info->capacity >= 1) {   /* size in GB */
+		return g_strdup_printf ("%2.0f GB %s", info->capacity,
+					itdb_info_get_ipod_model_name_string (info->ipod_model));
+	} else if (info->capacity > 0) {   /* size in MB */
+		return g_strdup_printf ("%3.0f MB %s", info->capacity * 1024,
+					itdb_info_get_ipod_model_name_string (info->ipod_model));
+	} else {   /* no capacity information available */
+		return g_strdup_printf ("%s",
+					itdb_info_get_ipod_model_name_string (info->ipod_model));
+	}
+}
+
+static void
+set_cell (GtkCellLayout   *cell_layout,
+	  GtkCellRenderer *cell,
+	  GtkTreeModel    *tree_model,
+	  GtkTreeIter     *iter,
+	  gpointer         data)
+{
+	gboolean header;
+	gchar *text;
+	Itdb_IpodInfo *info;
+
+	gtk_tree_model_get (tree_model, iter, COL_INFO, &info, -1);
+	g_return_if_fail (info);
+
+	header = gtk_tree_model_iter_has_child (tree_model, iter);
+
+	if (header) {
+		text = g_strdup (
+				 itdb_info_get_ipod_generation_string (info->ipod_generation));
+	} else {
+		text = ipod_info_to_string (info);
+	}
+
+	g_object_set (cell,
+		      "sensitive", !header,
+		      "text", text,
+		      NULL);
+	g_free (text);
+}
+
+static gboolean
+model_equals (gconstpointer a, gconstpointer b)
+{
+	const Itdb_IpodInfo *lhs = (const Itdb_IpodInfo *)a;
+	const Itdb_IpodInfo *rhs = (const Itdb_IpodInfo *)b;
+
+	return !((lhs->capacity == rhs->capacity) 
+		 && (lhs->ipod_model == rhs->ipod_model) 
+		 && (lhs->ipod_generation == rhs->ipod_generation));
+}
+
+static double 
+get_ipod_capacity (const char *mountpoint)
+{
+	GFile *root;
+	GFileInfo *info;
+	guint64 capacity;
+
+	root = g_file_new_for_path (mountpoint);
+	info = g_file_query_filesystem_info (root,
+					     G_FILE_ATTRIBUTE_FILESYSTEM_SIZE,
+					     NULL, NULL); 
+	g_object_unref (G_OBJECT (root));
+	if (info == NULL) {
+		return 0;
+	}
+	capacity = g_file_info_get_attribute_uint64 (info,
+						     G_FILE_ATTRIBUTE_FILESYSTEM_SIZE);
+	capacity += (1000*1000*500 - 1);
+	capacity -= (capacity % (1000*1000*500));
+	return (double)capacity/(1000.0*1000*1000);
+}
+
+static GHashTable *
+build_model_table (const char *mount_path)
+{
+	const Itdb_IpodInfo *table;
+	const Itdb_IpodInfo *model_info;
+	GHashTable *models;
+	gdouble ipod_capacity;
+
+	ipod_capacity = get_ipod_capacity (mount_path);
+	models = g_hash_table_new_full (g_int_hash, g_int_equal,
+					NULL, (GDestroyNotify)g_list_free);
+	table = itdb_info_get_ipod_info_table ();
+	for (model_info = table;
+	     model_info->model_number != NULL;
+	     model_info++) {
+		GList *infos;
+
+		infos = g_hash_table_lookup (models,
+					     &model_info->ipod_generation);
+		if (g_list_find_custom (infos, model_info, model_equals)) {
+			continue;
+		}
+		if (model_info->capacity == ipod_capacity) {
+			/* Steal the key from the hash table since we don't 
+			 * want 'infos' to be g_list_freed by the hash
+			 * table destroy notify function
+			 */
+			g_hash_table_steal (models,
+					    &model_info->ipod_generation);
+			infos = g_list_prepend (infos, (gpointer)model_info);
+
+			g_hash_table_insert (models, 
+					     (gpointer)&model_info->ipod_generation, 
+					     infos);
+		}
+	}
+
+	return models;
+}
+
+struct FillModelContext {
+	GtkWidget *combo;
+	GtkTreeStore *store;
+	const Itdb_IpodInfo *ipod_info;
+};
+
+static void 
+fill_one_generation (gpointer key, gpointer value, gpointer data)
+{
+	GList *infos;
+	GList *it;
+	Itdb_IpodGeneration generation;
+	gboolean first;
+	GtkTreeIter iter;
+	struct FillModelContext *ctx;
+
+	ctx = (struct FillModelContext *)data;
+	infos = (GList *)value;
+	generation = *(Itdb_IpodGeneration*)key;
+
+	first = TRUE;
+	for (it = infos; it != NULL; it = it->next) {
+		const Itdb_IpodInfo *info;
+		GtkTreeIter iter_child;
+
+		info = (const Itdb_IpodInfo *)it->data;
+		g_assert (info->ipod_generation == generation);
+
+		if (first) {
+			gtk_tree_store_append (ctx->store, &iter, NULL);
+			gtk_tree_store_set (ctx->store, &iter, 
+					    COL_INFO, info, -1);
+			first = FALSE;
+		}
+		gtk_tree_store_append (ctx->store, &iter_child, &iter);
+		gtk_tree_store_set (ctx->store, &iter_child, 
+				    COL_INFO, info, -1);
+		if (info == ctx->ipod_info) {
+			gtk_combo_box_set_active_iter (GTK_COMBO_BOX (ctx->combo), 
+						       &iter_child);
+		}
+	}
+}
+
+static void
+fill_model_combo (GtkWidget *combo, const char *mount_path)
+{
+	GHashTable *models;
+	Itdb_Device *device;
+	GtkTreeStore *store;
+	const Itdb_IpodInfo *ipod_info;
+	GtkCellRenderer *renderer;
+	struct FillModelContext ctx;
+
+	device = itdb_device_new ();
+	itdb_device_set_mountpoint (device, mount_path);
+	itdb_device_read_sysinfo (device);
+	ipod_info = itdb_device_get_ipod_info (device);
+	itdb_device_free (device);
+
+	store = gtk_tree_store_new (1, G_TYPE_POINTER);
+	gtk_combo_box_set_model (GTK_COMBO_BOX (combo), GTK_TREE_MODEL (store));
+
+	ctx.combo = combo;
+	ctx.store = store;
+	ctx.ipod_info = ipod_info;
+	models = build_model_table (mount_path);
+	g_hash_table_foreach (models, fill_one_generation, &ctx);
+	g_hash_table_destroy (models);
+	g_object_unref (store);
+
+	gtk_cell_layout_clear (GTK_CELL_LAYOUT (combo));
+
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, FALSE);
+	gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (combo),
+					    renderer,
+					    set_cell,
+					    NULL, NULL);
+}
+
+static gboolean
+show_ipod_first_time_dialog (GMount *mount, RBPlugin *plugin)
+{
+	/* could be an uninitialised iPod, ask the user */
+	GladeXML *xml;
+	GtkWidget *dialog;
+	GtkWidget *widget;
+	GtkTreeModel *tree_model;
+	GtkTreeIter iter;
+	int response;
+	char *glade_file;
+	char *mountpoint;
+	const Itdb_IpodInfo *info;
+	char *ipod_name;
+	GFile *root;
+	GError *error = NULL;
+
+	root = g_mount_get_root (mount);
+	if (root == NULL) {
+		return FALSE;      
+	}
+	mountpoint = g_file_get_path (root);
+	g_object_unref (G_OBJECT (root));
+
+	if (mountpoint == NULL) {
+		return FALSE;
+	}
+
+	/* create message dialog with model-number combo box
+	 * and asking whether they want to initialise the iPod
+	 */
+	glade_file = rb_plugin_find_file (plugin, "ipod_init.glade");
+	xml = glade_xml_new (glade_file, NULL, NULL);
+	g_free (glade_file);
+	dialog = glade_xml_get_widget (xml, "ipod_init");
+	widget = glade_xml_get_widget (xml, "model_combo");
+	fill_model_combo (widget, mountpoint);
+
+	rb_debug ("showing init dialog for ipod mount on '%s'", mountpoint);
+
+	response = gtk_dialog_run (GTK_DIALOG (dialog));
+	if (response != GTK_RESPONSE_ACCEPT) {
+		gtk_widget_destroy (dialog);
+		g_free (mountpoint);
+		return FALSE;
+	}
+
+	/* get model number and name */
+	tree_model = gtk_combo_box_get_model (GTK_COMBO_BOX (widget));
+	gtk_combo_box_get_active_iter (GTK_COMBO_BOX (widget), &iter);
+	gtk_tree_model_get (tree_model, &iter, COL_INFO, &info, -1);
+	widget = glade_xml_get_widget (xml, "name_entry");
+	ipod_name = g_strdup (gtk_entry_get_text (GTK_ENTRY (widget)));
+
+	gtk_widget_destroy (dialog);
+
+	rb_debug ("attempting to init ipod on '%s', with model '%s' and name '%s'",
+		  mountpoint, info->model_number, ipod_name);
+	if (!itdb_init_ipod (mountpoint, info->model_number, ipod_name, &error)) {
+		rb_error_dialog (NULL, _("Unable to initialise new iPod"), "%s", error->message);
+
+		g_free (mountpoint);
+		g_free (ipod_name);
+		g_error_free (error);
+		return FALSE;
+	}
+
+	g_free (mountpoint);
+	g_free (ipod_name);
+
+	return TRUE;
+}
+
 static gboolean
 rb_ipod_mount_has_ipod_db (GMount *mount)
 {
@@ -993,10 +1270,11 @@ rb_ipod_mount_has_ipod_db (GMount *mount)
 }
 
 gboolean
-rb_ipod_is_mount_ipod (GMount *mount)
+rb_ipod_is_mount_ipod (GMount *mount, RBPlugin *plugin)
 {
 #ifdef HAVE_HAL
 	gchar *udi;
+        gboolean result;
 	GVolume *volume;
 
 	volume = g_mount_get_volume (mount);
@@ -1006,18 +1284,24 @@ rb_ipod_is_mount_ipod (GMount *mount)
 	udi = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_HAL_UDI);
 	g_object_unref (volume);
 
-	if (udi != NULL) {
-		gboolean result;
+        if (udi == NULL) {
+                return FALSE;
+        }
 
-		result = hal_udi_is_ipod (udi);
-		g_free (udi);
-		if (result == FALSE) {
-			return FALSE;
-		}
-	}
+        result = hal_udi_is_ipod (udi);
+        g_free (udi);
+        if (result == FALSE) {
+            return FALSE;
+        }
 #endif
 
-	return rb_ipod_mount_has_ipod_db (mount);
+        if (!rb_ipod_mount_has_ipod_db (mount)) {
+                if (!show_ipod_first_time_dialog (mount, plugin)) {
+                        return FALSE;
+                }
+        }
+
+        return TRUE;
 }
 
 #ifdef HAVE_HAL
diff --git a/plugins/ipod/rb-ipod-source.h b/plugins/ipod/rb-ipod-source.h
index 24a24c8..352c518 100644
--- a/plugins/ipod/rb-ipod-source.h
+++ b/plugins/ipod/rb-ipod-source.h
@@ -32,6 +32,7 @@
 
 #include "rb-shell.h"
 #include "rb-removable-media-source.h"
+#include "rb-plugin.h"
 #include "rhythmdb.h"
 
 G_BEGIN_DECLS
@@ -53,11 +54,14 @@ typedef struct
 	RBRemovableMediaSourceClass parent;
 } RBiPodSourceClass;
 
-RBRemovableMediaSource *	rb_ipod_source_new		(RBShell *shell, GMount *mount);
+RBRemovableMediaSource *rb_ipod_source_new		(RBShell *shell,
+                                                         RBPlugin *plugin,
+                                                         GMount *mount);
 GType			rb_ipod_source_get_type		(void);
 GType                   rb_ipod_source_register_type    (GTypeModule *module);
 
-gboolean		rb_ipod_is_mount_ipod		(GMount *mount);
+gboolean		rb_ipod_is_mount_ipod		(GMount *mount,
+                                                         RBPlugin*plugin);
 
 void			rb_ipod_source_new_playlist	(RBiPodSource *source);
 void			rb_ipod_source_remove_playlist	(RBiPodSource *ipod_source,
