summaryrefslogtreecommitdiffstats
path: root/src/ipod-device.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ipod-device.c')
-rw-r--r--src/ipod-device.c1748
1 files changed, 1748 insertions, 0 deletions
diff --git a/src/ipod-device.c b/src/ipod-device.c
new file mode 100644
index 0000000..4731c70
--- /dev/null
+++ b/src/ipod-device.c
@@ -0,0 +1,1748 @@
+/* Time-stamp: <2005-10-10 01:23:29 jcs>
+|
+| Copyright (C) 2005 Jorg Schuler <jcsjcs at users sourceforge net>
+| Part of the gtkpod project.
+|
+| URL: http://www.gtkpod.org/
+| URL: http://gtkpod.sourceforge.net/
+|
+|
+| The source is taken from libipoddevice, CVS version October 8 2005
+| (http://64.14.94.162/index.php/Libipoddevice).
+|
+| I decided not to make libgpod dependent on libipoddevice because
+| the latter depends on libraries not widely available yet (libhal >=
+| 0.5.2, glib >= 2.8). It is planned to replace these files with a
+| libipoddevice dependence at some later time.
+|
+| The following changes were done:
+|
+| - libhal becomes optional (see #if HAVE_LIBHAL sections)
+| - g_mkdir_with_parents() is provided if not available (glib < 2.8)
+| - publicly available functions were renamed from ipod_device_...()
+| to itdb_device_...()
+|
+| Because of these changes only a limited amount of functionality is
+| available. See ipod-device.h for summary.
+|
+|
+|
+|
+| $Id$
+*/
+/* ex: set ts=4: */
+/***************************************************************************
+* ipod-device.c
+* Copyright (C) 2005 Novell
+* Written by Aaron Bockover <aaron@aaronbock.net>
+****************************************************************************/
+
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2.1 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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
+ */
+
+
+/* JCS: Change from ipod_device... to itdb_device for public functions */
+#define ipod_device_get_type itdb_device_get_type
+#define ipod_device_new itdb_device_new
+#define ipod_device_rescan_disk itdb_device_rescan_disk
+#define ipod_device_eject itdb_device_eject
+#define ipod_device_reboot itdb_device_reboot
+#define ipod_device_debug itdb_device_debug
+#define ipod_device_save itdb_device_save
+#define ipod_device_list_devices itdb_device_list_devices
+#define ipod_device_list_device_udis itdb_device_list_device_udis
+
+
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <glib/gstdio.h>
+
+#include "hal-common.h"
+#include "ipod-device.h"
+
+#define GB 1024
+
+typedef struct _IpodModel {
+ const gchar *model_number;
+ const guint64 capacity;
+ guint model_type;
+ guint generation;
+} IpodModel;
+
+static const IpodModel ipod_model_table [] = {
+ /* Handle idiots who hose their iPod file system, or
+ lucky people with iPods we don't yet know about*/
+ {"Invalid", 0, MODEL_TYPE_INVALID, UNKNOWN_GENERATION},
+ {"Unknown", 0, MODEL_TYPE_UNKNOWN, UNKNOWN_GENERATION},
+
+ /* First Generation */
+ {"8513", 5 * GB, MODEL_TYPE_REGULAR, FIRST_GENERATION},
+ {"8541", 5 * GB, MODEL_TYPE_REGULAR, FIRST_GENERATION},
+ {"8697", 5 * GB, MODEL_TYPE_REGULAR, FIRST_GENERATION},
+ {"8709", 10 * GB, MODEL_TYPE_REGULAR, FIRST_GENERATION},
+
+ /* Second Generation */
+ {"8737", 10 * GB, MODEL_TYPE_REGULAR, SECOND_GENERATION},
+ {"8740", 10 * GB, MODEL_TYPE_REGULAR, SECOND_GENERATION},
+ {"8738", 20 * GB, MODEL_TYPE_REGULAR, SECOND_GENERATION},
+ {"8741", 20 * GB, MODEL_TYPE_REGULAR, SECOND_GENERATION},
+
+ /* Third Generation */
+ {"8976", 10 * GB, MODEL_TYPE_REGULAR, THIRD_GENERATION},
+ {"8946", 15 * GB, MODEL_TYPE_REGULAR, THIRD_GENERATION},
+ {"9460", 15 * GB, MODEL_TYPE_REGULAR, THIRD_GENERATION},
+ {"9244", 20 * GB, MODEL_TYPE_REGULAR, THIRD_GENERATION},
+ {"8948", 30 * GB, MODEL_TYPE_REGULAR, THIRD_GENERATION},
+ {"9245", 40 * GB, MODEL_TYPE_REGULAR, THIRD_GENERATION},
+
+
+ /* Fourth Generation */
+ {"9282", 20 * GB, MODEL_TYPE_REGULAR, FOURTH_GENERATION},
+ {"9787", 25 * GB, MODEL_TYPE_REGULAR_U2, FOURTH_GENERATION},
+ {"9268", 40 * GB, MODEL_TYPE_REGULAR, FOURTH_GENERATION},
+ {"A079", 20 * GB, MODEL_TYPE_COLOR, FOURTH_GENERATION},
+ {"A127", 20 * GB, MODEL_TYPE_COLOR_U2, FOURTH_GENERATION},
+ {"9830", 60 * GB, MODEL_TYPE_COLOR, FOURTH_GENERATION},
+
+ /* First Generation Mini */
+ {"9160", 4 * GB, MODEL_TYPE_MINI, FIRST_GENERATION},
+ {"9436", 4 * GB, MODEL_TYPE_MINI_BLUE, FIRST_GENERATION},
+ {"9435", 4 * GB, MODEL_TYPE_MINI_PINK, FIRST_GENERATION},
+ {"9434", 4 * GB, MODEL_TYPE_MINI_GREEN, FIRST_GENERATION},
+ {"9437", 4 * GB, MODEL_TYPE_MINI_GOLD, FIRST_GENERATION},
+
+ /* Second Generation Mini */
+ {"9800", 4 * GB, MODEL_TYPE_MINI, SECOND_GENERATION},
+ {"9802", 4 * GB, MODEL_TYPE_MINI_BLUE, SECOND_GENERATION},
+ {"9804", 4 * GB, MODEL_TYPE_MINI_PINK, SECOND_GENERATION},
+ {"9806", 4 * GB, MODEL_TYPE_MINI_GREEN, SECOND_GENERATION},
+ {"9801", 6 * GB, MODEL_TYPE_MINI, SECOND_GENERATION},
+ {"9803", 6 * GB, MODEL_TYPE_MINI_BLUE, SECOND_GENERATION},
+ {"9805", 6 * GB, MODEL_TYPE_MINI_PINK, SECOND_GENERATION},
+ {"9807", 6 * GB, MODEL_TYPE_MINI_GREEN, SECOND_GENERATION},
+
+ /* Photo / Fourth Generation */
+ {"9829", 30 * GB, MODEL_TYPE_COLOR, FOURTH_GENERATION},
+ {"9585", 40 * GB, MODEL_TYPE_COLOR, FOURTH_GENERATION},
+ {"9586", 60 * GB, MODEL_TYPE_COLOR, FOURTH_GENERATION},
+ {"9830", 60 * GB, MODEL_TYPE_COLOR, FOURTH_GENERATION},
+
+ /* Shuffle / Fourth Generation */
+ {"9724", GB / 2, MODEL_TYPE_SHUFFLE, FOURTH_GENERATION},
+ {"9725", GB, MODEL_TYPE_SHUFFLE, FOURTH_GENERATION},
+
+ /* Nano / Fourth Generation */
+ {"A004", GB * 2, MODEL_TYPE_NANO_WHITE, FOURTH_GENERATION},
+ {"A099", GB * 2, MODEL_TYPE_NANO_BLACK, FOURTH_GENERATION},
+ {"A005", GB * 4, MODEL_TYPE_NANO_WHITE, FOURTH_GENERATION},
+ {"A107", GB * 4, MODEL_TYPE_NANO_BLACK, FOURTH_GENERATION},
+
+ /* HP iPods, need contributions for this table */
+ {"E436", 40 * GB, MODEL_TYPE_REGULAR, FOURTH_GENERATION},
+
+ {NULL, 0, 0, 0}
+};
+
+static const gchar *ipod_model_name_table [] = {
+ "Invalid",
+ "Unknown",
+ "Color",
+ "Color U2",
+ "Grayscale",
+ "Grayscale U2",
+ "Mini (Silver)",
+ "Mini (Blue)",
+ "Mini (Pink)",
+ "Mini (Green)",
+ "Mini (Gold)",
+ "Shuffle",
+ "Nano (White)",
+ "Nano (Black)",
+ NULL
+};
+
+#define g_free_if_not_null(o) \
+ if(o != NULL) { \
+ g_free(o); \
+ o = NULL; \
+ }
+
+static const gchar *sysinfo_field_names [] = {
+ "pszSerialNumber",
+ "ModelNumStr",
+ "visibleBuildID",
+ NULL
+};
+
+static gchar *sysinfo_arr_get_dup(gchar *arr[], const gchar *key)
+{
+ gint i = 0;
+
+ for(i = 0; sysinfo_field_names[i] != NULL; i++) {
+ if(g_strcasecmp(sysinfo_field_names[i], key) == 0)
+ return g_strdup(arr[i]);
+ }
+
+ return NULL;
+}
+
+
+#if ((GTK_MAJOR_VERSION <= 2) && (GTK_MINOR_VERSION < 8))
+/**
+ * g_mkdir_with_parents:
+ * @pathname: a pathname in the GLib file name encoding
+ * @mode: permissions to use for newly created directories
+ *
+ * Create a directory if it doesn't already exist. Create intermediate
+ * parent directories as needed, too.
+ *
+ * Returns: 0 if the directory already exists, or was successfully
+ * created. Returns -1 if an error occurred, with errno set.
+ *
+ * Since: 2.8 (copied from GLIB version 2.8 - JCS)
+ */
+int
+g_mkdir_with_parents (const gchar *pathname,
+ int mode);
+int
+g_mkdir_with_parents (const gchar *pathname,
+ int mode)
+{
+ gchar *fn, *p;
+
+ if (pathname == NULL || *pathname == '\0')
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ fn = g_strdup (pathname);
+
+ if (g_path_is_absolute (fn))
+ p = (gchar *) g_path_skip_root (fn);
+ else
+ p = fn;
+
+ do
+ {
+ while (*p && !G_IS_DIR_SEPARATOR (*p))
+ p++;
+
+ if (!*p)
+ p = NULL;
+ else
+ *p = '\0';
+
+ if (!g_file_test (fn, G_FILE_TEST_EXISTS))
+ {
+ if (g_mkdir (fn, mode) == -1)
+ {
+ int errno_save = errno;
+ g_free (fn);
+ errno = errno_save;
+ return -1;
+ }
+ }
+ else if (!g_file_test (fn, G_FILE_TEST_IS_DIR))
+ {
+ g_free (fn);
+ errno = ENOTDIR;
+ return -1;
+ }
+ if (p)
+ {
+ *p++ = G_DIR_SEPARATOR;
+ while (*p && G_IS_DIR_SEPARATOR (*p))
+ p++;
+ }
+ }
+ while (p);
+
+ g_free (fn);
+
+ return 0;
+}
+#endif
+
+
+static void ipod_device_class_init(IpodDeviceClass *klass);
+static void ipod_device_init(IpodDevice *sp);
+static void ipod_device_finalize(GObject *object);
+
+gchar *ipod_device_read_device_info_string(FILE *fd);
+void ipod_device_write_device_info_string(gchar *str, FILE *fd);
+
+gboolean ipod_device_reload(IpodDevice *device);
+void ipod_device_construct_paths(IpodDevice *device);
+gboolean ipod_device_info_load(IpodDevice *device);
+guint ipod_device_detect_model(IpodDevice *device);
+gboolean ipod_device_detect_volume_info(IpodDevice *device);
+LibHalContext *ipod_device_hal_initialize(void);
+void ipod_device_detect_volume_used(IpodDevice *device);
+guint64 ipod_device_dir_size(const gchar *path);
+gboolean ipod_device_has_open_fd(IpodDevice *device);
+gboolean ipod_device_read_sysinfo(IpodDevice *device);
+gboolean ipod_device_detect_writeable(IpodDevice *device);
+void ipod_device_restore_reboot_preferences(IpodDevice *device);
+
+struct IpodDevicePrivate {
+ LibHalContext *hal_context;
+
+ /* Paths */
+ gchar *device_path;
+ gchar *mount_point;
+ gchar *control_path;
+ gchar *hal_volume_id;
+
+ gchar *adv_capacity;
+ guint model_index;
+
+ /* DeviceInfo Fields (All Devices) */
+ gchar *device_name;
+ gchar *user_name;
+ gchar *host_name;
+
+ /* Volume Size/Usage */
+ guint64 volume_size;
+ guint64 volume_available;
+ guint64 volume_used;
+
+ /* System Info */
+ gchar *serial_number;
+ gchar *model_number;
+ gchar *firmware_version;
+
+ gchar *volume_uuid;
+ gchar *volume_label;
+
+ /* Fresh from the factory/restore? */
+ gboolean is_new;
+
+ /* Safety */
+ gboolean is_ipod;
+ gboolean can_write;
+};
+
+static GObjectClass *parent_class = NULL;
+
+/* GObject Class Specific Methods */
+
+GType
+ipod_device_get_type()
+{
+ static GType type = 0;
+
+ if(type == 0) {
+ static const GTypeInfo our_info = {
+ sizeof (IpodDeviceClass),
+ NULL,
+ NULL,
+ (GClassInitFunc)ipod_device_class_init,
+ NULL,
+ NULL,
+ sizeof (IpodDevice),
+ 0,
+ (GInstanceInitFunc)ipod_device_init,
+ };
+
+ type = g_type_register_static(G_TYPE_OBJECT,
+ "IpodDevice", &our_info, 0);
+ }
+
+ return type;
+}
+
+static void
+ipod_device_get_property(GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ IpodDevice *device = IPOD_DEVICE(object);
+
+ switch(prop_id) {
+ case PROP_HAL_CONTEXT:
+ g_value_set_pointer(value, device->priv->hal_context);
+ break;
+ case PROP_HAL_VOLUME_ID:
+ g_value_set_string(value, device->priv->hal_volume_id);
+ break;
+ case PROP_MOUNT_POINT:
+ g_value_set_string(value, device->priv->mount_point);
+ break;
+ case PROP_DEVICE_PATH:
+ g_value_set_string(value, device->priv->device_path);
+ break;
+ case PROP_CONTROL_PATH:
+ g_value_set_string(value, device->priv->control_path);
+ break;
+ case PROP_DEVICE_MODEL:
+ g_value_set_uint(value,
+ ipod_model_table[device->priv->model_index].model_type);
+ break;
+ case PROP_DEVICE_MODEL_STRING:
+ g_value_set_string(value,
+ ipod_model_name_table[ipod_model_table[
+ device->priv->model_index].model_type]);
+ break;
+ case PROP_DEVICE_GENERATION:
+ g_value_set_uint(value,
+ ipod_model_table[device->priv->model_index].generation);
+ break;
+ case PROP_ADVERTISED_CAPACITY:
+ g_value_set_string(value, device->priv->adv_capacity);
+ break;
+ case PROP_DEVICE_NAME:
+ g_value_set_string(value, device->priv->device_name);
+ break;
+ case PROP_USER_NAME:
+ g_value_set_string(value, device->priv->user_name);
+ break;
+ case PROP_HOST_NAME:
+ g_value_set_string(value, device->priv->host_name);
+ break;
+ case PROP_VOLUME_SIZE:
+ g_value_set_uint64(value, device->priv->volume_size);
+ break;
+ case PROP_VOLUME_AVAILABLE:
+ g_value_set_uint64(value, device->priv->volume_available);
+ break;
+ case PROP_VOLUME_USED:
+ g_value_set_uint64(value, device->priv->volume_used);
+ break;
+ case PROP_IS_IPOD:
+ g_value_set_boolean(value, device->priv->is_ipod);
+ break;
+ case PROP_IS_NEW:
+ g_value_set_boolean(value, device->priv->is_new);
+ break;
+ case PROP_SERIAL_NUMBER:
+ g_value_set_string(value, device->priv->serial_number);
+ break;
+ case PROP_MODEL_NUMBER:
+ g_value_set_string(value, device->priv->model_number);
+ break;
+ case PROP_FIRMWARE_VERSION:
+ g_value_set_string(value, device->priv->firmware_version);
+ break;
+ case PROP_VOLUME_UUID:
+ g_value_set_string(value, device->priv->volume_uuid);
+ break;
+ case PROP_VOLUME_LABEL:
+ g_value_set_string(value, device->priv->volume_label);
+ break;
+ case PROP_CAN_WRITE:
+ g_value_set_boolean(value, device->priv->can_write);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+ipod_device_set_property(GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ IpodDevice *device = IPOD_DEVICE(object);
+ const gchar *str;
+ gchar **volumes;
+ gint volume_count;
+
+ switch(prop_id) {
+ case PROP_MOUNT_POINT:
+ case PROP_DEVICE_PATH:
+ case PROP_HAL_VOLUME_ID:
+ str = g_value_get_string(value);
+ volumes = libhal_manager_find_device_string_match(
+ device->priv->hal_context, "block.device", str,
+ &volume_count, NULL);
+
+ if(volume_count == 0) {
+ libhal_free_string_array(volumes);
+ volumes = libhal_manager_find_device_string_match(
+ device->priv->hal_context, "volume.mount_point",
+ str, &volume_count, NULL);
+
+ if(volume_count >= 1)
+ str = volumes[0];
+ } else {
+ str = volumes[0];
+ }
+#if HAVE_LIBHAL
+ g_free_if_not_null(device->priv->hal_volume_id);
+ device->priv->hal_volume_id = g_strdup(str);
+#else
+/* JCS for libgpod */
+ g_free (device->priv->mount_point);
+ device->priv->mount_point = g_strdup (str);
+/* end JCS for libgpod */
+#endif
+ device->priv->is_ipod = ipod_device_reload(device);
+ libhal_free_string_array(volumes);
+ break;
+ case PROP_DEVICE_NAME:
+ str = g_value_get_string(value);
+ g_free_if_not_null(device->priv->device_name);
+ device->priv->device_name = g_strdup(str);
+ break;
+ case PROP_USER_NAME:
+ str = g_value_get_string(value);
+ g_free_if_not_null(device->priv->user_name);
+ device->priv->user_name = g_strdup(str);
+ break;
+ case PROP_HOST_NAME:
+ str = g_value_get_string(value);
+ g_free_if_not_null(device->priv->host_name);
+ device->priv->host_name = g_strdup(str);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+ipod_device_class_init(IpodDeviceClass *klass)
+{
+ GParamSpec *hal_context_param;
+ GParamSpec *hal_volume_id_param;
+ GParamSpec *mount_point_param;
+ GParamSpec *device_path_param;
+ GParamSpec *control_path_param;
+ GParamSpec *device_model_param;
+ GParamSpec *device_model_string_param;
+ GParamSpec *device_name_param;
+ GParamSpec *user_name_param;
+ GParamSpec *host_name_param;
+ GParamSpec *volume_size_param;
+ GParamSpec *volume_available_param;
+ GParamSpec *volume_used_param;
+ GParamSpec *is_ipod_param;
+ GParamSpec *is_new_param;
+ GParamSpec *serial_number_param;
+ GParamSpec *model_number_param;
+ GParamSpec *firmware_version_param;
+ GParamSpec *volume_uuid_param;
+ GParamSpec *volume_label_param;
+ GParamSpec *can_write_param;
+ GParamSpec *device_generation_param;
+ GParamSpec *advertised_capacity_param;
+
+ GObjectClass *class = G_OBJECT_CLASS(klass);
+
+ parent_class = g_type_class_peek_parent(klass);
+ class->finalize = ipod_device_finalize;
+
+ hal_context_param = g_param_spec_pointer("hal-context", "HAL Context",
+ "LibHalContext handle", G_PARAM_READABLE);
+
+ hal_volume_id_param = g_param_spec_string("hal-volume-id", "HAL Volume ID",
+ "Volume ID of a device in HAL",
+ NULL, G_PARAM_READWRITE);
+
+ mount_point_param = g_param_spec_string("mount-point", "Mount Point",
+ "Where iPod is mounted (parent of an iPod_Control directory)",
+ NULL, G_PARAM_READWRITE);
+
+ device_path_param = g_param_spec_string("device-path", "Device Path",
+ "Path to raw iPod Device (/dev/sda2, for example)",
+ NULL, G_PARAM_READWRITE);
+
+ control_path_param = g_param_spec_string("control-path",
+ "iPod_Control Path","Full path to iPod_Control",
+ NULL, G_PARAM_READABLE);
+
+ device_model_param = g_param_spec_uint("device-model", "Device Model",
+ "Type of iPod (Regular, Photo, Shuffle)", 0, 2, 0, G_PARAM_READABLE);
+
+ device_model_string_param = g_param_spec_string("device-model-string",
+ "Device Model String", "String type of iPod (Regular, Shuffle)",
+ NULL, G_PARAM_READABLE);
+
+ device_name_param = g_param_spec_string("device-name", "Device Name",
+ "The user-assigned name of their iPod", NULL, G_PARAM_READWRITE);
+
+ user_name_param = g_param_spec_string("user-name", "User Name",
+ "On Windows, Maybe Mac, the user account owning the iPod",
+ NULL, G_PARAM_READWRITE);
+
+ host_name_param = g_param_spec_string("host-name", "Host Name",
+ "On Windows, Maybe Mac, the host/computer name owning the iPod",
+ NULL, G_PARAM_READWRITE);
+
+ volume_size_param = g_param_spec_uint64("volume-size", "Volume Size",
+ "Total size of the iPod's hard drive", 0, G_MAXLONG, 0,
+ G_PARAM_READABLE);
+
+ volume_available_param = g_param_spec_uint64("volume-available",
+ "Volume Available", "Available space on the iPod",
+ 0, G_MAXLONG, 0, G_PARAM_READABLE);
+
+ volume_used_param = g_param_spec_uint64("volume-used", "Volume Used",
+ "How much space has been used", 0, G_MAXLONG, 0, G_PARAM_READABLE);
+
+ is_ipod_param = g_param_spec_boolean("is-ipod", "Is iPod",
+ "If all device checks are okay, then this is true",
+ FALSE, G_PARAM_READABLE);
+
+ is_new_param = g_param_spec_boolean("is-new", "Is iPod",
+ "If device is fresh from factory/restore, this is true",
+ FALSE, G_PARAM_READABLE);
+
+ serial_number_param = g_param_spec_string("serial-number", "Serial Number",
+ "Serial Number of the iPod",
+ NULL, G_PARAM_READABLE);
+
+ model_number_param = g_param_spec_string("model-number", "Model Number",
+ "Model Number of the iPod",
+ NULL, G_PARAM_READABLE);
+
+ firmware_version_param = g_param_spec_string("firmware-version",
+ "Firmware Version", "iPod Firmware Version",
+ NULL, G_PARAM_READABLE);
+
+ volume_uuid_param = g_param_spec_string("volume-uuid", "Volume UUID",
+ "Volume UUID of the iPod",
+ NULL, G_PARAM_READABLE);
+
+ volume_label_param = g_param_spec_string("volume-label", "Volume Label",
+ "Volume Label of the iPod",
+ NULL, G_PARAM_READABLE);
+
+ can_write_param = g_param_spec_boolean("can-write", "Can Write",
+ "True if device can be written to (mounted read/write)",
+ FALSE, G_PARAM_READABLE);
+
+ advertised_capacity_param = g_param_spec_string("advertised-capacity",
+ "Advertised Capacity", "Apple Marketed/Advertised Capacity String",
+ NULL, G_PARAM_READABLE);
+
+ device_generation_param = g_param_spec_uint("device-generation",
+ "Generation", "Generation of the iPod",
+ 0, G_MAXUINT, 0, G_PARAM_READABLE);
+
+ class->set_property = ipod_device_set_property;
+ class->get_property = ipod_device_get_property;
+ g_object_class_install_property(class, PROP_HAL_CONTEXT,
+ hal_context_param);
+
+ g_object_class_install_property(class, PROP_HAL_VOLUME_ID,
+ hal_volume_id_param);
+
+ g_object_class_install_property(class, PROP_MOUNT_POINT,
+ mount_point_param);
+
+ g_object_class_install_property(class, PROP_DEVICE_PATH,
+ device_path_param);
+
+ g_object_class_install_property(class, PROP_CONTROL_PATH,
+ control_path_param);
+
+ g_object_class_install_property(class, PROP_DEVICE_MODEL,
+ device_model_param);
+
+ g_object_class_install_property(class, PROP_DEVICE_MODEL_STRING,
+ device_model_string_param);
+
+ g_object_class_install_property(class, PROP_DEVICE_NAME,
+ device_name_param);
+
+ g_object_class_install_property(class, PROP_USER_NAME,
+ user_name_param);
+
+ g_object_class_install_property(class, PROP_HOST_NAME,
+ host_name_param);
+
+ g_object_class_install_property(class, PROP_VOLUME_SIZE,
+ volume_size_param);
+
+ g_object_class_install_property(class, PROP_VOLUME_AVAILABLE,
+ volume_available_param);
+
+ g_object_class_install_property(class, PROP_VOLUME_USED,
+ volume_used_param);
+
+ g_object_class_install_property(class, PROP_IS_IPOD, is_ipod_param);
+
+ g_object_class_install_property(class, PROP_IS_NEW, is_new_param);
+
+ g_object_class_install_property(class, PROP_SERIAL_NUMBER,
+ serial_number_param);
+
+ g_object_class_install_property(class, PROP_MODEL_NUMBER,
+ model_number_param);
+
+ g_object_class_install_property(class, PROP_FIRMWARE_VERSION,
+ firmware_version_param);
+
+ g_object_class_install_property(class, PROP_VOLUME_UUID,
+ volume_uuid_param);
+
+ g_object_class_install_property(class, PROP_VOLUME_LABEL,
+ volume_label_param);
+
+ g_object_class_install_property(class, PROP_CAN_WRITE,
+ can_write_param);
+
+ g_object_class_install_property(class, PROP_DEVICE_GENERATION,
+ device_generation_param);
+
+ g_object_class_install_property(class, PROP_ADVERTISED_CAPACITY,
+ advertised_capacity_param);
+}
+
+static void
+ipod_device_init(IpodDevice *device)
+{
+ device->priv = g_new0(IpodDevicePrivate, 1);
+
+ device->priv->hal_context = ipod_device_hal_initialize();
+
+ device->priv->hal_volume_id = NULL;
+ device->priv->mount_point = NULL;
+ device->priv->device_path = NULL;
+ device->priv->control_path = NULL;
+ device->priv->device_name = NULL;
+ device->priv->user_name = NULL;
+ device->priv->host_name = NULL;
+ device->priv->adv_capacity = NULL;
+ device->priv->serial_number = NULL;
+ device->priv->model_number = NULL;
+ device->priv->firmware_version = NULL;
+ device->priv->volume_uuid = NULL;
+ device->priv->volume_label = NULL;
+
+ device->priv->volume_size = 0;
+ device->priv->volume_available = 0;
+ device->priv->volume_used = 0;
+
+ device->priv->is_new = FALSE;
+ device->priv->can_write = FALSE;
+}
+
+static void
+ipod_device_finalize(GObject *object)
+{
+ IpodDevice *device = IPOD_DEVICE(object);
+
+ /* Free private members, etc. */
+ g_free_if_not_null(device->priv->hal_volume_id);
+ g_free_if_not_null(device->priv->device_path);
+ g_free_if_not_null(device->priv->mount_point);
+ g_free_if_not_null(device->priv->control_path);
+ g_free_if_not_null(device->priv->device_name);
+ g_free_if_not_null(device->priv->user_name);
+ g_free_if_not_null(device->priv->host_name);
+ g_free_if_not_null(device->priv->adv_capacity);
+ g_free_if_not_null(device->priv->serial_number);
+ g_free_if_not_null(device->priv->model_number);
+ g_free_if_not_null(device->priv->firmware_version);
+ g_free_if_not_null(device->priv->volume_uuid);
+ g_free_if_not_null(device->priv->volume_label);
+
+ if(device->priv->hal_context != NULL) {
+ libhal_ctx_shutdown(device->priv->hal_context, NULL);
+ libhal_ctx_free(device->priv->hal_context);
+ }
+ g_free(device->priv);
+ G_OBJECT_CLASS(parent_class)->finalize(object);
+}
+
+/* PRIVATE METHODS */
+
+LibHalContext *
+ipod_device_hal_initialize()
+{
+#if HAVE_LIBHAL
+ LibHalContext *hal_context;
+ DBusError error;
+ DBusConnection *dbus_connection;
+ char **devices;
+ gint device_count;
+
+ hal_context = libhal_ctx_new();
+ if(hal_context == NULL)
+ return NULL;
+
+ dbus_error_init(&error);
+ dbus_connection = dbus_bus_get(DBUS_BUS_SYSTEM, &error);
+ if(dbus_error_is_set(&error)) {
+ dbus_error_free(&error);
+ libhal_ctx_free(hal_context);
+ return NULL;
+ }
+
+ libhal_ctx_set_dbus_connection(hal_context, dbus_connection);
+
+ if(!libhal_ctx_init(hal_context, &error)) {
+ libhal_ctx_free(hal_context);
+ return NULL;
+ }
+
+ devices = libhal_get_all_devices(hal_context, &device_count, NULL);
+ if(devices == NULL) {
+ libhal_ctx_shutdown(hal_context, NULL);
+ libhal_ctx_free(hal_context);
+ hal_context = NULL;
+ return NULL;
+ }
+
+ libhal_free_string_array(devices);
+
+ return hal_context;
+#else
+ return NULL;
+#endif
+}
+
+gboolean
+ipod_device_reload(IpodDevice *device)
+{
+ g_return_val_if_fail(IS_IPOD_DEVICE(device), FALSE);
+
+ device->priv->model_index = 0;
+
+ if(!ipod_device_detect_volume_info(device))
+ return FALSE;
+
+ ipod_device_construct_paths(device);
+
+ device->priv->is_new = !ipod_device_info_load(device);
+
+ ipod_device_detect_volume_used(device);
+ ipod_device_detect_writeable(device);
+ ipod_device_read_sysinfo(device);
+ ipod_device_detect_model(device);
+ ipod_device_restore_reboot_preferences(device);
+
+ return device->priv->model_index != 0;
+}
+
+void
+ipod_device_construct_paths(IpodDevice *device)
+{
+ int len;
+
+ g_return_if_fail(IS_IPOD_DEVICE(device));
+ g_return_if_fail(device->priv->mount_point != NULL);
+
+ len = strlen(device->priv->mount_point);
+ if(device->priv->mount_point[len - 1] == '/')
+ device->priv->mount_point[len - 1] = '\0';
+
+ if(strlen(device->priv->mount_point) == 0)
+ return;
+
+ device->priv->control_path = g_strdup_printf("%s/%s",
+ device->priv->mount_point, "iPod_Control/");
+}
+
+gchar *
+ipod_device_read_device_info_string(FILE *fd)
+{
+ gshort length;
+ gunichar2 *utf16;
+ gchar *utf8;
+
+ fread(&length, 1, sizeof(gshort), fd);
+
+ if(length <= 0)
+ return NULL;
+
+ utf16 = (gunichar2 *)g_malloc(length * sizeof(gunichar2));
+ fread(utf16, sizeof(gunichar2), length, fd);
+
+ if(utf16 == NULL)
+ return NULL;
+
+ utf8 = g_utf16_to_utf8(utf16, length, NULL, NULL, NULL);
+
+ g_free(utf16);
+ utf16 = NULL;
+
+ return utf8;
+}
+
+void
+ipod_device_write_device_info_string(gchar *str, FILE *fd)
+{
+ gunichar2 *unistr;
+ gshort length;
+
+ if(str == NULL)
+ return;
+
+ length = strlen(str);
+ unistr = g_utf8_to_utf16(str, length, NULL, NULL, NULL);
+
+ length = length > 0x198 ? 0x198 : length;
+
+ fwrite(&length, 2, 1, fd);
+ fwrite(unistr, 2, length, fd);
+
+ g_free(unistr);
+}
+
+gboolean
+ipod_device_read_sysinfo(IpodDevice *device)
+{
+ gchar *field_values[sizeof(sysinfo_field_names) + 1];
+ gchar *tmp, *path, buf[512];
+ gint i, name_len;
+ FILE *fd;
+
+ g_return_val_if_fail(IS_IPOD_DEVICE(device), FALSE);
+
+ path = g_strdup_printf("%sDevice/SysInfo",
+ device->priv->control_path);
+
+ fd = fopen(path, "r");
+ if(fd == NULL) {
+ g_free(path);
+ return FALSE;
+ }
+
+ while(fgets(buf, sizeof(buf), fd)) {
+ buf[strlen(buf) - 1] = '\0';
+
+ for(i = 0; sysinfo_field_names[i] != NULL; i++) {
+ name_len = strlen(sysinfo_field_names[i]);
+ if(strncasecmp(buf, sysinfo_field_names[i], name_len) == 0) {
+ field_values[i] = strdup(buf + name_len + 2);
+ if(strncasecmp(field_values[i], "0x", 2) == 0) {
+ if((tmp = strstr(field_values[i], "(")) != NULL) {
+ field_values[i] = tmp + 1;
+ field_values[i][strlen(field_values[i]) - 1] = '\0';
+ }
+ }
+
+ field_values[i] = g_strdup(field_values[i]);
+ }
+ }
+ }
+
+ fclose(fd);
+
+ device->priv->serial_number = sysinfo_arr_get_dup(field_values,
+ "pszSerialNumber");
+ device->priv->model_number = sysinfo_arr_get_dup(field_values,
+ "ModelNumStr");
+ device->priv->firmware_version = sysinfo_arr_get_dup(field_values,
+ "visibleBuildID");
+
+ g_free(path);
+
+ return TRUE;
+}
+
+gboolean
+ipod_device_info_load(IpodDevice *device)
+{
+ gchar *path;
+ FILE *fd;
+
+ g_return_val_if_fail(IS_IPOD_DEVICE(device), FALSE);
+
+ path = g_strdup_printf("%siTunes/DeviceInfo",
+ device->priv->control_path);
+
+ fd = fopen(path, "r");
+ if(fd == NULL) {
+ g_free(path);
+ return FALSE;
+ }
+
+ device->priv->device_name = ipod_device_read_device_info_string(fd);
+ if(device->priv->device_name == NULL)
+ device->priv->device_name = g_strdup("iPod");
+
+ fseek(fd, 0x200, SEEK_SET);
+ device->priv->user_name = ipod_device_read_device_info_string(fd);
+
+ fseek(fd, 0x400, SEEK_SET);
+ device->priv->host_name = ipod_device_read_device_info_string(fd);
+
+ fclose(fd);
+ g_free(path);
+
+ return TRUE;
+}
+
+gboolean
+ipod_device_detect_writeable(IpodDevice *device)
+{
+ FILE *fp;
+ gchar *itunes_dir, *music_dir, *itunesdb_path;
+ struct stat finfo;
+
+ g_return_val_if_fail(IS_IPOD_DEVICE(device), FALSE);
+
+ device->priv->can_write = FALSE;
+
+ itunes_dir = g_strdup_printf("%siTunes", device->priv->control_path);
+
+ if(!g_file_test(itunes_dir, G_FILE_TEST_IS_DIR)) {
+ if(g_mkdir_with_parents(itunes_dir, 0755) != 0) {
+ g_free(itunes_dir);
+ return FALSE;
+ }
+ }
+
+ itunesdb_path = g_strdup_printf("%s/iTunesDB", itunes_dir);
+
+ if((fp = fopen(itunesdb_path, "a+")) != NULL) {
+ device->priv->can_write = TRUE;
+ fclose(fp);
+
+ memset(&finfo, 0, sizeof(finfo));
+ if(stat(itunesdb_path, &finfo) == 0) {
+ if(finfo.st_size == 0) {
+ unlink(itunesdb_path);
+ }
+ }
+ } else {
+ g_free(itunes_dir);
+ g_free(itunesdb_path);
+ return FALSE;
+ }
+
+ music_dir = g_strdup_printf("%sMusic", device->priv->control_path);
+
+ if(!g_file_test(music_dir, G_FILE_TEST_IS_DIR)) {
+ device->priv->can_write = g_mkdir_with_parents(music_dir, 0755) == 0;
+ }
+
+ g_free(itunes_dir);
+ g_free(itunesdb_path);
+ g_free(music_dir);
+
+ return device->priv->can_write;
+}
+
+gint
+ipod_device_get_model_index_from_table(const gchar *_model_num);
+gint
+ipod_device_get_model_index_from_table(const gchar *_model_num)
+{
+ gint i;
+ gchar *model_num = g_strdup(_model_num);
+ gchar *p = model_num;
+
+ if(isalpha(model_num[0]))
+ *p++;
+
+ for(i = 2; ipod_model_table[i].model_number != NULL; i++) {
+ if(g_strncasecmp(p, ipod_model_table[i].model_number, 4) == 0) {
+ g_free(model_num);
+ return i;
+ }
+ }
+
+ g_free(model_num);
+ return 1;
+}
+
+guint
+ipod_device_detect_model(IpodDevice *device)
+{
+ gint i;
+ guint64 adv, act;
+ gint cap;
+
+ g_return_val_if_fail(IS_IPOD_DEVICE(device), 0);
+
+ device->priv->model_index = 0;
+
+ /* Shuffle! */
+ if(device->priv->model_number == NULL) {
+ for(i = 2; ipod_model_table[i].model_number != NULL; i++) {
+ if(ipod_model_table[i].model_type != MODEL_TYPE_SHUFFLE)
+ continue;
+
+ cap = ipod_model_table[i].capacity;
+ adv = cap * 1048576;
+ act = device->priv->volume_size;
+
+ if((adv - act) / 1048576 < 50) {
+ device->priv->model_index = i;
+ device->priv->model_number = g_strdup_printf("M%s",
+ ipod_model_table[i].model_number);
+
+ device->priv->adv_capacity = g_strdup_printf("%d %s",
+ cap < 1024 ? cap : cap / 1024,
+ cap < 1024 ? "MB" : "GB");
+ break;
+ }
+ }
+ } else {
+ /* Anything Else */
+ device->priv->model_index =
+ ipod_device_get_model_index_from_table(device->priv->model_number);
+
+ cap = ipod_model_table[device->priv->model_index].capacity;
+
+ device->priv->adv_capacity = g_strdup_printf("%d %s",
+ cap < 1024 ? cap : cap / 1024,
+ cap < 1024 ? "MB" : "GB");
+ }
+
+ return device->priv->model_index;
+}
+
+void
+ipod_device_restore_reboot_preferences(IpodDevice *device)
+{
+ gchar *backup_prefs, *prefs;
+
+ backup_prefs = g_strdup_printf("%s/.Reboot_Preferences",
+ device->priv->control_path);
+ prefs = g_strdup_printf("%s/Device/Preferences",
+ device->priv->control_path);
+
+ g_return_if_fail(IS_IPOD_DEVICE(device));
+
+ if(g_file_test(backup_prefs, G_FILE_TEST_EXISTS)) {
+ unlink(prefs);
+ g_rename(backup_prefs, prefs);
+ }
+}
+
+gboolean
+ipod_device_detect_volume_info(IpodDevice *device)
+{
+#if HAVE_LIBHAL
+ LibHalContext *hal_context;
+ gchar **volumes;
+ gchar *hd_mount_point, *hd_device_path;
+ gchar *hd_hal_id = NULL, *maybe_hd_hal_id = NULL;
+ gint volume_count, i;
+
+ g_return_val_if_fail(IS_IPOD_DEVICE(device), FALSE);
+ hal_context = device->priv->hal_context;
+
+ g_free_if_not_null(device->priv->device_path);
+ g_free_if_not_null(device->priv->mount_point);
+ device->priv->volume_size = 0;
+
+ if(!libhal_device_exists(hal_context, device->priv->hal_volume_id, NULL)) {
+ /* For testing/debugging... we don't have a real device, but
+ the location may be a directory containing an iPod image */
+ if(g_strncasecmp(device->priv->hal_volume_id, "/dev/", 5) == 0 ||
+ device->priv->hal_volume_id[0] != '/')
+ return FALSE;
+
+ g_free_if_not_null(device->priv->mount_point);
+ device->priv->mount_point = g_strdup(device->priv->hal_volume_id);
+
+ g_free_if_not_null(device->priv->hal_volume_id);
+ g_free_if_not_null(device->priv->device_path);
+
+ /* Let's find out about the disk drive containing our image */
+
+ volumes = libhal_manager_find_device_string_match(hal_context,
+ "info.category", "volume", &volume_count, NULL);
+
+ for(i = 0; i < volume_count; i++) {
+ if(!libhal_device_property_exists(hal_context,
+ volumes[i], "volume.is_mounted", NULL) ||
+ !libhal_device_get_property_bool(hal_context,
+ volumes[i], "volume.is_mounted", NULL)) {
+ continue;
+ }
+
+ hd_mount_point = libhal_device_get_property_string(hal_context,
+ volumes[i], "volume.mount_point", NULL);
+
+ if(g_strncasecmp(hd_mount_point, device->priv->mount_point,
+ strlen(hd_mount_point)) != 0)
+ continue;
+
+ if(g_strcasecmp(hd_mount_point, "/") == 0)
+ maybe_hd_hal_id = volumes[i];
+ else
+ hd_hal_id = volumes[i];
+ }
+
+ if(hd_hal_id == NULL && maybe_hd_hal_id != NULL)
+ hd_hal_id = maybe_hd_hal_id;
+
+ if(hd_hal_id == NULL) {
+ libhal_free_string_array(volumes);
+ return FALSE;
+ }
+
+ if(!libhal_device_exists(hal_context, hd_hal_id, NULL)) {
+ libhal_free_string_array(volumes);
+ return FALSE;
+ }
+
+ hd_device_path = libhal_device_get_property_string(hal_context,
+ hd_hal_id, "block.device", NULL);
+
+ device->priv->hal_volume_id = g_strdup(hd_hal_id);
+ device->priv->device_path = g_strdup(hd_device_path);
+ device->priv->volume_size = libhal_device_get_property_uint64(
+ hal_context, hd_hal_id, "volume.size", NULL);
+
+ libhal_free_string_array(volumes);
+
+ return TRUE;
+ }
+
+ if(!libhal_device_property_exists(hal_context,
+ device->priv->hal_volume_id, "volume.is_mounted", NULL)
+ || !libhal_device_get_property_bool(hal_context,
+ device->priv->hal_volume_id, "volume.is_mounted", NULL)) {
+ return FALSE;
+ }
+
+ if(libhal_device_property_exists(hal_context, device->priv->hal_volume_id,
+ "block.device", NULL))
+ device->priv->device_path = libhal_device_get_property_string(
+ hal_context, device->priv->hal_volume_id, "block.device", NULL);
+
+ if(libhal_device_property_exists(hal_context, device->priv->hal_volume_id,
+ "volume.mount_point", NULL))
+ device->priv->mount_point = libhal_device_get_property_string(
+ hal_context, device->priv->hal_volume_id,
+ "volume.mount_point", NULL);
+
+ if(libhal_device_property_exists(hal_context, device->priv->hal_volume_id,
+ "volume.size", NULL))
+ device->priv->volume_size = libhal_device_get_property_uint64(
+ hal_context, device->priv->hal_volume_id, "volume.size", NULL);
+
+ if(libhal_device_property_exists(hal_context, device->priv->hal_volume_id,
+ "volume.uuid", NULL)) {
+ device->priv->volume_uuid = libhal_device_get_property_string(
+ hal_context, device->priv->hal_volume_id,
+ "volume.uuid", NULL);
+
+ if(strlen(device->priv->volume_uuid) == 0) {
+ g_free(device->priv->volume_uuid);
+ device->priv->volume_uuid = NULL;
+ }
+ }
+
+ if(libhal_device_property_exists(hal_context, device->priv->hal_volume_id,
+ "volume.label", NULL))
+ device->priv->volume_label = libhal_device_get_property_string(
+ hal_context, device->priv->hal_volume_id,
+ "volume.label", NULL);
+#endif
+ return TRUE;
+}
+
+void
+ipod_device_detect_volume_used(IpodDevice *device)
+{
+ device->priv->volume_used =
+ ipod_device_dir_size(device->priv->mount_point);
+ device->priv->volume_available = device->priv->volume_size -
+ device->priv->volume_used;
+}
+
+void
+_ipod_device_dir_size(const gchar *path, guint64 *total_size);
+void
+_ipod_device_dir_size(const gchar *path, guint64 *total_size)
+{
+ GDir *dir;
+ const gchar *next_path;
+ gchar *fullpath;
+ struct stat finfo;
+
+ if((dir = g_dir_open(path, 0, NULL)) == NULL)
+ return;
+
+ while((next_path = g_dir_read_name(dir)) != NULL) {
+ fullpath = g_strdup_printf("%s/%s", path, next_path);
+
+ if(g_file_test(fullpath, G_FILE_TEST_IS_DIR))
+ _ipod_device_dir_size(fullpath, total_size);
+ else
+ *total_size += stat(fullpath, &finfo) == 0 ? finfo.st_size : 0;
+
+ g_free(fullpath);
+ fullpath = NULL;
+ }
+
+ g_dir_close(dir);
+}
+
+guint64
+ipod_device_dir_size(const gchar *path)
+{
+ guint64 retsize, *size = g_new(guint64, 1);
+ *size = 0;
+ _ipod_device_dir_size(path, size);
+ retsize = *size;
+ g_free(size);
+ return retsize;
+}
+
+gboolean
+ipod_device_has_open_fd(IpodDevice *device)
+{
+ GDir *dir, *fddir;
+ gchar *fdpath, *fdidpath, *realpath;
+ const gchar *procpath, *fdid;
+
+ g_return_val_if_fail(IS_IPOD_DEVICE(device), FALSE);
+
+ if((dir = g_dir_open("/proc", 0, NULL)) == NULL)
+ return FALSE;
+
+ while((procpath = g_dir_read_name(dir)) != NULL) {
+ if(atoi(procpath) <= 0)
+ continue;
+
+ fdpath = g_strdup_printf("/proc/%s/fd", procpath);
+
+ if(!g_file_test(fdpath, G_FILE_TEST_IS_DIR)) {
+ g_free(fdpath);
+ continue;
+ }
+
+ if((fddir = g_dir_open(fdpath, 0, NULL)) == NULL) {
+ g_free(fdpath);
+ continue;
+ }
+
+ while((fdid = g_dir_read_name(fddir)) != NULL) {
+ fdidpath = g_strdup_printf("%s/%s", fdpath, fdid);
+ realpath = g_file_read_link(fdidpath, NULL);
+
+ if(realpath == NULL) {
+ g_free(fdidpath);
+ continue;
+ }
+
+ if(g_strncasecmp(realpath, device->priv->mount_point,
+ strlen(device->priv->mount_point)) == 0) {
+ g_dir_close(fddir);
+ g_dir_close(dir);
+ g_free(realpath);
+ g_free(fdidpath);
+ return TRUE;
+ }
+
+ g_free(realpath);
+ g_free(fdidpath);
+ }
+
+ g_dir_close(fddir);
+ }
+
+ g_dir_close(dir);
+
+ return FALSE;
+}
+
+#if HAVE_LIBHAL
+/* modded from g-v-m */
+static int
+ipod_device_run_command(IpodDevice *device, const char *command,
+ GError **error_out)
+{
+ char *path;
+ const char *inptr, *start;
+ GError *error = NULL;
+ GString *exec;
+ char *argv[4];
+ int status = 0;
+
+ exec = g_string_new(NULL);
+
+ /* perform s/%d/device/, s/%m/mount_point/ and s/%h/udi/ */
+ start = inptr = command;
+ while((inptr = strchr (inptr, '%')) != NULL) {
+ g_string_append_len(exec, start, inptr - start);
+ inptr++;
+ switch (*inptr) {
+ case 'd':
+ g_string_append(exec, device->priv->device_path ?
+ device->priv->device_path : "");
+ break;
+ case 'm':
+ if(device->priv->mount_point) {
+ path = g_shell_quote(device->priv->mount_point);
+ g_string_append(exec, path);
+ g_free(path);
+ } else {
+ g_string_append(exec, "\"\"");
+ }
+ break;
+ case 'h':
+ g_string_append(exec, device->priv->hal_volume_id);
+ break;
+ case '%':
+ g_string_append_c(exec, '%');
+ break;
+ default:
+ g_string_append_c(exec, '%');
+ if(*inptr)
+ g_string_append_c(exec, *inptr);
+ break;
+ }
+
+ if(*inptr)
+ inptr++;
+ start = inptr;
+ }
+
+ g_string_append(exec, start);
+
+ argv[0] = "/bin/sh";
+ argv[1] = "-c";
+ argv[2] = exec->str;
+ argv[3] = NULL;
+
+ g_spawn_sync(g_get_home_dir(), argv, NULL, 0, NULL,
+ NULL, NULL, NULL, &status, &error);
+
+ if(error != NULL)
+ g_propagate_error(error_out, error);
+
+ g_string_free(exec, TRUE);
+
+ return status;
+}
+#endif
+
+/* PUBLIC METHODS */
+
+IpodDevice *
+ipod_device_new(gchar *hal_volume_id)
+{
+ IpodDevice *device = g_object_new(TYPE_IPOD_DEVICE,
+ "hal-volume-id", hal_volume_id, NULL);
+
+ if(!device->priv->is_ipod) {
+ g_object_unref(device);
+ return NULL;
+ }
+
+ return device;
+}
+
+gboolean
+ipod_device_rescan_disk(IpodDevice *device)
+{
+ g_return_val_if_fail(IS_IPOD_DEVICE(device), FALSE);
+ ipod_device_detect_volume_used(device);
+ return TRUE;
+}
+
+guint
+ipod_device_eject(IpodDevice *device, GError **error_out)
+{
+#if HAVE_LIBHAL
+ gint exit_status;
+ GError *error = NULL;
+
+ g_return_val_if_fail(IS_IPOD_DEVICE(device), EJECT_ERROR);
+ g_return_val_if_fail(device->priv->is_ipod, EJECT_ERROR);
+
+ if(ipod_device_has_open_fd(device))
+ return EJECT_BUSY;
+
+#ifdef ASSUME_SUBMOUNT
+ sync();
+
+ exit_status = ipod_device_run_command(device, EJECT_COMMAND, &error);
+ if(error) {
+ g_propagate_error(error_out, error);
+ return EJECT_ERROR;
+ }
+
+ return exit_status == 0 ? EJECT_OK : EJECT_ERROR;
+#endif
+
+ exit_status = ipod_device_run_command(device, UNMOUNT_COMMAND, &error);
+
+ if(!error && exit_status == 0) {
+ exit_status = ipod_device_run_command(device, EJECT_COMMAND, &error);
+ if(error) {
+ g_propagate_error(error_out, error);
+ return EJECT_ERROR;
+ }
+
+ return exit_status == 0 ? EJECT_OK : EJECT_ERROR;
+ }
+
+ if(error)
+ g_propagate_error(error_out, error);
+
+ return EJECT_ERROR;
+}
+
+guint
+ipod_device_reboot(IpodDevice *device, GError **error_out)
+{
+ gchar *sysinfo, *prefs, *backup_prefs, *devpath;
+ gboolean can_eject = TRUE;
+
+ g_return_val_if_fail(IS_IPOD_DEVICE(device), EJECT_ERROR);
+ g_return_val_if_fail(device->priv->is_ipod, EJECT_ERROR);
+
+ devpath = g_strdup_printf("%s/Device/", device->priv->control_path);
+ sysinfo = g_strdup_printf("%sSysInfo", devpath);
+ prefs = g_strdup_printf("%sPreferences", devpath);
+ backup_prefs = g_strdup_printf("%s/.Reboot_Preferences",
+ device->priv->control_path);
+
+ if(g_file_test(sysinfo, G_FILE_TEST_EXISTS))
+ can_eject = unlink(sysinfo) == 0;
+
+ if(g_file_test(prefs, G_FILE_TEST_EXISTS) && can_eject) {
+ if(g_file_test(backup_prefs, G_FILE_TEST_EXISTS))
+ unlink(backup_prefs);
+
+ g_rename(prefs, backup_prefs);
+ unlink(prefs);
+
+ can_eject = TRUE;
+ }
+
+ if(can_eject)
+ can_eject = rmdir(devpath) == 0;
+
+ g_free(devpath);
+ g_free(sysinfo);
+ g_free(prefs);
+ g_free(backup_prefs);
+
+ if(can_eject)
+ return ipod_device_eject(device, error_out);
+
+ g_propagate_error(error_out, g_error_new(g_quark_from_string("UNLINK"),
+ errno, "Could not remove file to initiate iPod reboot"));
+#endif
+ return EJECT_ERROR;
+}
+
+GList *
+_ipod_device_list_devices(gboolean create_device);
+GList *
+_ipod_device_list_devices(gboolean create_device)
+{
+ LibHalContext *hal_context;
+ GList *finalDevices = NULL;
+ gchar **ipods, **volumes;
+ gint ipod_count, volume_count, i, j;
+ IpodDevice *ipod;
+ gboolean validIpod = FALSE;
+
+ hal_context = ipod_device_hal_initialize();
+ if(hal_context == NULL)
+ return NULL;
+
+ ipods = libhal_manager_find_device_string_match(hal_context,
+ "info.product", "iPod", &ipod_count, NULL);
+
+ for(i = 0; i < ipod_count; i++) {
+ volumes = libhal_manager_find_device_string_match(hal_context,
+ "info.parent", ipods[i], &volume_count, NULL);
+
+ for(j = 0; j < volume_count; j++) {
+ if(!libhal_device_property_exists(hal_context,
+ volumes[j], "volume.is_mounted", NULL)
+ || !libhal_device_get_property_bool(hal_context,
+ volumes[j], "volume.is_mounted", NULL))
+ continue;
+
+ if(!create_device) {
+ finalDevices = g_list_append(finalDevices,
+ g_strdup(volumes[j]));
+ continue;
+ }
+
+ if((ipod = ipod_device_new(volumes[j])) == NULL)
+ continue;
+
+ g_object_get(ipod, "is-ipod", &validIpod, NULL);
+ if(validIpod)
+ finalDevices = g_list_append(finalDevices, ipod);
+ }
+ }
+
+ libhal_ctx_shutdown(hal_context, NULL);
+ libhal_ctx_free(hal_context);
+
+ return finalDevices;
+}
+
+GList *
+ipod_device_list_devices()
+{
+ return _ipod_device_list_devices(TRUE);
+}
+
+GList *
+ipod_device_list_device_udis()
+{
+ return _ipod_device_list_devices(FALSE);
+}
+
+gboolean
+ipod_device_save(IpodDevice *device, GError **error_out)
+{
+ FILE *fd;
+ gchar *path, *itunes_dir;
+ gchar bs = 0;
+ GError *error = NULL;
+
+ g_return_val_if_fail(IS_IPOD_DEVICE(device), FALSE);
+
+ itunes_dir = g_strdup_printf("%siTunes", device->priv->control_path);
+ path = g_strdup_printf("%s/DeviceInfo", itunes_dir);
+
+ if(!g_file_test(itunes_dir, G_FILE_TEST_IS_DIR)) {
+ if(g_mkdir_with_parents(itunes_dir, 0744) != 0) {
+ if(error_out != NULL) {
+ error = g_error_new(g_quark_from_static_string("IPOD_DEVICE"),
+ ERROR_SAVE, "Could not create iTunes Directory: %s",
+ itunes_dir);
+ g_propagate_error(error_out, error);
+ }
+
+ g_free(path);
+ g_free(itunes_dir);
+
+ return FALSE;
+ }
+ }
+
+ fd = fopen(path, "w+");
+ if(fd == NULL) {
+ if(error_out != NULL) {
+ error = g_error_new(g_quark_from_static_string("IPOD_DEVICE"),
+ ERROR_SAVE, "Could not save DeviceInfo file: %s", path);
+ g_propagate_error(error_out, error);
+ }
+
+ g_free(path);
+ g_free(itunes_dir);
+
+ return FALSE;
+ }
+
+ ipod_device_write_device_info_string(device->priv->device_name, fd);
+
+ fseek(fd, 0x200, SEEK_SET);
+ ipod_device_write_device_info_string(device->priv->user_name, fd);
+
+ fseek(fd, 0x400, SEEK_SET);
+ ipod_device_write_device_info_string(device->priv->host_name, fd);
+
+ fseek(fd, 0X5FF, SEEK_SET);
+ fwrite(&bs, 1, 1, fd);
+
+ fclose(fd);
+
+ g_free(path);
+ g_free(itunes_dir);
+
+ return TRUE;
+}
+
+void
+ipod_device_debug(IpodDevice *device)
+{
+ static const gchar *generation_names [] = {
+ "Unknown",
+ "First",
+ "Second",
+ "Third",
+ "Fourth"
+ };
+
+ gchar *device_path, *mount_point, *control_path, *hal_id;
+ gchar *model_number, *adv_capacity, *model_string;
+ guint model, generation;
+ gboolean is_new, can_write;
+ gchar *serial_number, *firmware_version;
+ guint64 volume_size, volume_used, volume_available;
+ gchar *volume_uuid, *volume_label;
+ gchar *device_name, *user_name, *host_name;
+
+ g_return_if_fail(IS_IPOD_DEVICE(device));
+
+ g_object_get(device,
+ "device-path", &device_path,
+ "mount-point", &mount_point,
+ "control-path", &control_path,
+ "hal-volume-id", &hal_id,
+ "model-number", &model_number,
+ "device-model", &model,
+ "device-model-string", &model_string,
+ "device-generation", &generation,
+ "advertised-capacity", &adv_capacity,
+ "is-new", &is_new,
+ "can-write", &can_write,
+ "serial-number", &serial_number,
+ "firmware-version", &firmware_version,
+ "volume-size", &volume_size,
+ "volume-used", &volume_used,
+ "volume-available", &volume_available,
+ "volume_uuid", &volume_uuid,
+ "volume-label", &volume_label,
+ "device-name", &device_name,
+ "user-name", &user_name,
+ "host-name", &host_name,
+ NULL);
+
+ g_printf("Path Info\n");
+ g_printf(" Device Path: %s\n", device_path);
+ g_printf(" Mount Point: %s\n", mount_point);
+ g_printf(" Control Path: %s\n", control_path);
+ g_printf(" HAL ID: %s\n", hal_id);
+
+ g_printf("Device Info\n");
+ g_printf(" Model Number: %s\n", model_number);
+ g_printf(" Device Model: %s\n", model_string);
+ g_printf(" iPod Generation: %s\n", generation_names[generation]);
+ g_printf(" Adv. Capacity: %s\n", adv_capacity);
+ g_printf(" Is New: %s\n", is_new ? "YES" : "NO");
+ g_printf(" Writeable: %s\n", can_write ? "YES" : "NO");
+ g_printf(" Serial Number: %s\n", serial_number);
+ g_printf(" Firmware Version: %s\n", firmware_version);
+ g_printf("Volume Info\n");
+ g_printf(" Volume Size: %lld\n", volume_size);
+ g_printf(" Volume Used: %lld\n", volume_used);
+ g_printf(" Available %lld\n", volume_available);
+ g_printf(" UUID: %s\n", volume_uuid);
+ g_printf(" Label %s\n", volume_label);
+ g_printf("User-Provided Info\n");
+ g_printf(" Device Name: %s\n", device_name);
+ g_printf(" User Name: %s\n", user_name);
+ g_printf(" Host Name: %s\n", host_name);
+
+ g_printf("\n");
+ fflush(stdout);
+}