On Wed, 2007-12-19 at 16:43 -0500, David Zeuthen wrote: > Another thought: we probably need a way to learn more about the GDrive, > GVolume and GMount objects than just their names and icons. First, cf. > the network applet use, we need to know if the mount is networked or not > and if it is, what hostname is it using. Simple proposal
Attached are some unfinished patches that illustrate this idea; notes - flags are replaced by textual strings (notice the resemblance to mime types?) - content sniffing happens in a thread when done async - no actual sniffing is done; should be trivial to add though - needs work in the drive classification bits. - There's some other stuff I wanted to propose regardless of this - get_id() on GDrive,Volume,Mount: needed to pass these around - model,vendor on GDrive; some apps (notably the cd burner ones) will need this for user selection when having multiple drives Anyway, with this patch it should be simple to abuse the .desktop file format and add something x-content/video to e.g. Totem's and mplayer's desktop files. Then we can easily add machinery to the Preferred Applications capplet for choosing the default video player and we can finally get rid of this ugly entry boxes http://people.freedesktop.org/~david/g-v-m-prefs.png Ditto for the other x-content/* formats. This is a problem we haven't been able to solve for three years... If you like this idea, here are the next steps I'm going to do - Finish the patch - Propose x-content/* and x-drive/* types as a fd.o spec on xdg-list - Make the directory sniffing code plug-in based (I expect we want to update this code rather frequently, it's a bit like shared-mime-info) Anyway, the whole idea may be crack (I don't think so though), but you can't accuse me of not thinking in a "user document" centric way with this :-) David
Index: gunixmount.c =================================================================== --- gunixmount.c (revision 6163) +++ gunixmount.c (working copy) @@ -178,6 +178,14 @@ } static char * +g_unix_mount_get_id (GMount *mount) +{ + GUnixMount *unix_mount = G_UNIX_MOUNT (mount); + + return g_strdup (unix_mount->mount_path); +} + +static char * g_unix_mount_get_name (GMount *mount) { GUnixMount *unix_mount = G_UNIX_MOUNT (mount); @@ -393,6 +401,7 @@ { iface->get_root = g_unix_mount_get_root; iface->get_name = g_unix_mount_get_name; + iface->get_id = g_unix_mount_get_id; iface->get_icon = g_unix_mount_get_icon; iface->get_uuid = g_unix_mount_get_uuid; iface->get_drive = g_unix_mount_get_drive; Index: gdrive.c =================================================================== --- gdrive.c (revision 6163) +++ gdrive.c (working copy) @@ -146,13 +146,35 @@ } /** + * g_drive_get_id: + * @drive: a #GDrive. + * + * Get an opaque textual identifier that can be used to pass a + * reference to @drive between processes. + * + * Returns: a string containing @drive's id. The returned string + * should be freed when no longer needed. + **/ +char * +g_drive_get_id (GDrive *drive) +{ + GDriveIface *iface; + + g_return_val_if_fail (G_IS_DRIVE (drive), NULL); + + iface = G_DRIVE_GET_IFACE (drive); + + return (* iface->get_id) (drive); +} + +/** * g_drive_get_name: * @drive: a #GDrive. * * Gets the name of @drive. * - * Returns: a string containing @drive's name. The returned - * string should be freed when no longer needed. + * Returns: a string containing @drive's name. The returned string + * should be freed when no longer needed. **/ char * g_drive_get_name (GDrive *drive) @@ -167,6 +189,50 @@ } /** + * g_drive_get_vendor: + * @drive: a #GDrive. + * + * Gets the vendor of @drive. + * + * Returns: a string containing @drive's vendor or %NULL if no such + * name could be found. The returned string should be freed when no + * longer needed. + **/ +char * +g_drive_get_vendor (GDrive *drive) +{ + GDriveIface *iface; + + g_return_val_if_fail (G_IS_DRIVE (drive), NULL); + + iface = G_DRIVE_GET_IFACE (drive); + + return (* iface->get_vendor) (drive); +} + +/** + * g_drive_get_model: + * @drive: a #GDrive. + * + * Gets the model of @drive. + * + * Returns: a string containing @drive's model or %NULL if no such + * name could be found. The returned string should be freed when no + * longer needed. + **/ +char * +g_drive_get_model (GDrive *drive) +{ + GDriveIface *iface; + + g_return_val_if_fail (G_IS_DRIVE (drive), NULL); + + iface = G_DRIVE_GET_IFACE (drive); + + return (* iface->get_model) (drive); +} + +/** * g_drive_get_icon: * @drive: a #GDrive. * @@ -468,5 +534,65 @@ return (* iface->poll_for_media_finish) (drive, result, error); } +/** + * g_drive_get_classification: + * @drive: a #GDrive. + * + * A #GDrive is an abstraction of a storage device connected to the + * system. As many popular consumer electronic devices, such as + * phones, music and video players, GPS devices and so on appear to + * the operating system as nothing more than storage devices it is + * useful to be able to classify these based on vendor/model and other + * out of band data. + * + * Classification of a @drive is defined by one or more well-known + * textual strings being attached to the drive. This function returns + * this list. + * + * Returns: a #NULL terminated array of classifications. + **/ +const char * const * +g_drive_get_classification (GDrive *drive) +{ + GDriveIface *iface; + + g_return_val_if_fail (G_IS_DRIVE (drive), NULL); + + iface = G_DRIVE_GET_IFACE (drive); + + return (* iface->get_classification) (drive); +} + +/** + * g_drive_has_classification: + * @drive: a #GDrive. + * @classification: a textual string such as #G_DRIVE_CLASSIFICATION_BOOK_READER or <literal>x-drive/phone<literal>. + * + * A #GDrive is an abstraction of a storage device connected to the + * system. As many popular consumer electronic devices, such as + * phones, music and video players, GPS devices and so on appear to + * the operating system as nothing more than storage devices it is + * useful to be able to classify these based on vendor/model and other + * out of band data. + * + * Classification of a @drive is defined by one or more well-known + * textual strings being attached to the drive. This function returns + * this list. + * + * Returns: %TRUE only if @drive has @classification. + **/ +gboolean +g_drive_has_classification (GDrive *drive, + const char *classification) +{ + GDriveIface *iface; + + g_return_val_if_fail (G_IS_DRIVE (drive), FALSE); + + iface = G_DRIVE_GET_IFACE (drive); + + return (* iface->has_classification) (drive, classification); +} + #define __G_DRIVE_C__ #include "gioaliasdef.c" Index: gdrive.h =================================================================== --- gdrive.h (revision 6163) +++ gdrive.h (working copy) @@ -46,7 +46,10 @@ * @changed: Signal emitted when the drive is changed. * @disconnected: The removed signal that is emitted when the #GDrive have been disconnected. If the recipient is holding references to the object they should release them so the object can be finalized. * @eject_button: Signal emitted when the physical eject button (if any) of a drive have been pressed. + * @get_id: Returns an opaque textual identifier that can be used to pass a reference to a #GDrive between processes. * @get_name: Returns the name for the given #GDrive. + * @get_vendor: Returns the name of the vendor of the #GDrive or %NULL if no such name can be found. + * @get_model: Returns the name of the model of the #GDrive or %NULL if no such name can be found. * @get_icon: Returns a #GIcon for the given #GDrive. * @has_volumes: Returns %TRUE if the #GDrive has mountable volumes. * @get_volumes: Returns a list #GList of #GVolume for the #GDrive. @@ -59,6 +62,8 @@ * @eject_finish: Finishes an eject operation. * @poll_for_media: Poll for media insertion/removal on a #GDrive. * @poll_for_media_finish: Finishes a media poll operation. + * @get_classification: Gets the classification of a #GDrive. + * @has_classification: Determine if a #GDrive has a classification. * * Interface for creating #GDrive implementations. */ @@ -74,7 +79,10 @@ void (*eject_button) (GDrive *drive); /* Virtual Table */ + char * (*get_id) (GDrive *drive); char * (*get_name) (GDrive *drive); + char * (*get_vendor) (GDrive *drive); + char * (*get_model) (GDrive *drive); GIcon * (*get_icon) (GDrive *drive); gboolean (*has_volumes) (GDrive *drive); GList * (*get_volumes) (GDrive *drive); @@ -97,11 +105,18 @@ gboolean (*poll_for_media_finish) (GDrive *drive, GAsyncResult *result, GError **error); + + const char * const * (*get_classification) (GDrive *drive); + gboolean (*has_classification) (GDrive *drive, + const char *classification); }; GType g_drive_get_type (void) G_GNUC_CONST; +char * g_drive_get_id (GDrive *drive); char * g_drive_get_name (GDrive *drive); +char * g_drive_get_vendor (GDrive *drive); +char * g_drive_get_model (GDrive *drive); GIcon * g_drive_get_icon (GDrive *drive); gboolean g_drive_has_volumes (GDrive *drive); GList * g_drive_get_volumes (GDrive *drive); @@ -125,6 +140,87 @@ GAsyncResult *result, GError **error); +const char * const *g_drive_get_classification (GDrive *drive); +gboolean g_drive_has_classification (GDrive *drive, + const char *classification); + +/** + * G_DRIVE_CLASSIFICATION_OPTICAL: + * + * The #GDrive represents an optical drive. + */ +#define G_DRIVE_CLASSIFICATION_OPTICAL "x-drive/optical" + +/** + * G_DRIVE_CLASSIFICATION_OPTICAL_BURN: + * + * The #GDrive represents an optical drive with recording capabilities. + */ +#define G_DRIVE_CLASSIFICATION_OPTICAL_BURN "x-drive/optical-burn" + +/** + * G_DRIVE_CLASSIFICATION_CARD_READER: + * + * The #GDrive represents a memory card reader. + */ +#define G_DRIVE_CLASSIFICATION_CARD_READER "x-drive/card-reader" + +/** + * G_DRIVE_CLASSIFICATION_STILL_CAMERA: + * + * The #GDrive represents a digital still camera. + */ +#define G_DRIVE_CLASSIFICATION_STILL_CAMERA "x-drive/still-camera" + +/** + * G_DRIVE_CLASSIFICATION_VIDEO_CAMERA: + * + * The #GDrive represents a video camera. + */ +#define G_DRIVE_CLASSIFICATION_VIDEO_CAMERA "x-drive/video-camera" + +/** + * G_DRIVE_CLASSIFICATION_LOCATION_DEVICE: + * + * The #GDrive represents a device used for storing location traces using e.g. GPS technology. + */ +#define G_DRIVE_CLASSIFICATION_LOCATION_DEVICE "x-drive/location-device" + +/** + * G_DRIVE_CLASSIFICATION_AUDIO_PLAYER: + * + * The #GDrive represents a device capable of audio playback. + */ +#define G_DRIVE_CLASSIFICATION_AUDIO_PLAYER "x-drive/audio-player" + +/** + * G_DRIVE_CLASSIFICATION_PHOTO_PLAYER: + * + * The #GDrive represents a device capable of viewing photos. + */ +#define G_DRIVE_CLASSIFICATION_PHOTO_PLAYER "x-drive/photo-player" + +/** + * G_DRIVE_CLASSIFICATION_VIDEO_PLAYER: + * + * The #GDrive represents a device capable of video playback. + */ +#define G_DRIVE_CLASSIFICATION_VIDEO_PLAYER "x-drive/video-player" + +/** + * G_DRIVE_CLASSIFICATION_EBOOK_READER: + * + * The #GDrive represents a device used for storing and presenting electronic book content. + */ +#define G_DRIVE_CLASSIFICATION_EBOOK_READER "x-drive/ebook-reader" + +/** + * G_DRIVE_CLASSIFICATION_PHONE: + * + * The #GDrive represents a mobile phone. + */ +#define G_DRIVE_CLASSIFICATION_PHONE "x-drive/phone" + G_END_DECLS #endif /* __G_DRIVE_H__ */ Index: gunixvolume.c =================================================================== --- gunixvolume.c (revision 6163) +++ gunixvolume.c (working copy) @@ -191,6 +191,13 @@ } static char * +g_unix_volume_get_id (GVolume *volume) +{ + GUnixVolume *unix_volume = G_UNIX_VOLUME (volume); + return g_strdup (unix_volume->mount_path); +} + +static char * g_unix_volume_get_name (GVolume *volume) { GUnixVolume *unix_volume = G_UNIX_VOLUME (volume); @@ -405,6 +412,7 @@ g_unix_volume_volume_iface_init (GVolumeIface *iface) { iface->get_name = g_unix_volume_get_name; + iface->get_id = g_unix_volume_get_id; iface->get_icon = g_unix_volume_get_icon; iface->get_uuid = g_unix_volume_get_uuid; iface->get_drive = g_unix_volume_get_drive; Index: gmount.c =================================================================== --- gmount.c (revision 6163) +++ gmount.c (working copy) @@ -156,6 +156,28 @@ } /** + * g_mount_get_id: + * @mount: a #GMount. + * + * Get an opaque textual identifier that can be used to pass a + * reference to @mount between processes. + * + * Returns: a string containing @mount's id. The returned string + * should be freed when no longer needed. + **/ +char * +g_mount_get_id (GMount *mount) +{ + GMountIface *iface; + + g_return_val_if_fail (G_IS_MOUNT (mount), NULL); + + iface = G_MOUNT_GET_IFACE (mount); + + return (* iface->get_id) (mount); +} + +/** * g_mount_get_name: * @mount: a #GMount. * @@ -441,5 +463,239 @@ return (* iface->eject_finish) (mount, result, error); } +/** + * g_mount_guess_content_type: + * @mount: a #GMount. + * @error: return location for error or %NULL to ignore. + * + * Tries to guess the type of content stored on @mount. Returns one or + * more textual identifiers such as #G_MOUNT_CONTENT_PHOTO, + * #G_MOUNT_CONTENT_MUSIC or other well-known content types. + * + * This function may do I/O and thus may take a long time to + * complete. For the async version, see + * g_mount_guess_content_type_async(). + * + * Returns: a %NULL terminated array of content types or %NULL on + * error. Caller should free this array with g_strfreev() when done + * with it. + **/ +char ** +g_mount_guess_content_type (GMount *mount, + GError **error) +{ + GMountIface *iface; + + g_return_val_if_fail (G_IS_MOUNT (mount), NULL); + + iface = G_MOUNT_GET_IFACE (mount); + + if (iface->guess_content_type == NULL) + { + if (error != NULL) + *error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("mount doesn't implement guess_content_type")); + return NULL; + } + + return (* iface->guess_content_type) (mount, error); +} + +/** + * g_mount_guess_content_type_async: + * @mount: a #GMount. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: a #GAsyncReadyCallback. + * @user_data: user data passed to @callback. + * + * This is an asynchronous version of g_mount_guess_content_type(), + * and is finished by calling g_mount_guess_content_type_finish() with + * the @mount and #GAsyncResults data returned in the @callback. + */ +void +g_mount_guess_content_type_async (GMount *mount, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GMountIface *iface; + + g_return_if_fail (G_IS_MOUNT (mount)); + + iface = G_MOUNT_GET_IFACE (mount); + + if (iface->guess_content_type_async == NULL) + { + g_simple_async_report_error_in_idle (G_OBJECT (mount), + callback, user_data, + G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("mount doesn't implement guess_content_type_async")); + + return; + } + + (* iface->guess_content_type_async) (mount, cancellable, callback, user_data); +} + +/** + * g_mount_guess_content_type_finish: + * @mount: a #GMount. + * @result: a #GAsyncResult. + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * + * Finishes guessing content types of @mount. If any errors occured + * during the operation, @error will be set to contain the errors and + * %FALSE will be returned. + * + * Returns: a %NULL terminated array of content types or %NULL on + * error. Caller should free this array with g_strfreev() when done + * with it. + **/ +char ** +g_mount_guess_content_type_finish (GMount *mount, + GAsyncResult *result, + GError **error) +{ + GMountIface *iface; + + g_return_val_if_fail (G_IS_MOUNT (mount), NULL); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL); + + if (G_IS_SIMPLE_ASYNC_RESULT (result)) + { + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + } + + iface = G_MOUNT_GET_IFACE (mount); + return (* iface->guess_content_type_finish) (mount, result, error); +} + + +/** + * g_mount_guess_content_type_for_mount_root: + * @mount_root: a #GFile. + * @error: return location for error or %NULL to ignore. + * + * Like g_mount_guess_content_type() but analyzes a given sub + * directory instead. + * + * Returns: a %NULL terminated array of content types or %NULL on + * error. Caller should free this array with g_strfreev() when done + * with it. + **/ +char ** +g_mount_guess_content_type_for_mount_root (GFile *mount_root, + GError **error) +{ + char **ret; + GPtrArray *types; + + types = g_ptr_array_new (); + + /* TODO: actually analyze mount_root and add content types as needed */ + + g_ptr_array_add (types, g_strdup (G_MOUNT_CONTENT_PHOTO)); + g_ptr_array_add (types, g_strdup (G_MOUNT_CONTENT_EBOOK)); + g_ptr_array_add (types, g_strdup (G_MOUNT_CONTENT_MUSIC)); + + if (types->len == 0) + { + ret = NULL; + g_ptr_array_free (types, TRUE); + } + else + { + g_ptr_array_add (types, NULL); + ret = (char **) g_ptr_array_free (types, FALSE); + } + + return ret; +} + +typedef struct { + char **guessed_content_type; +} GuessContentData; + +static void +guess_content_thread (GSimpleAsyncResult *res, + GObject *object, + GCancellable *cancellable) +{ + GuessContentData *op; + GError *error = NULL; + + op = g_simple_async_result_get_op_res_gpointer (res); + + op->guessed_content_type = g_mount_guess_content_type_for_mount_root (G_FILE (object), &error); + + if (error != NULL) + { + g_simple_async_result_set_from_error (res, error); + g_error_free (error); + } +} + +/** + * g_mount_guess_content_type_async: + * @mount_root: a #GFile. + * @cancellable: optional #GCancellable object, %NULL to ignore. + * @callback: a #GAsyncReadyCallback. + * @user_data: user data passed to @callback. + * + * Like g_mount_guess_content_type_async() but analyzes a given sub + * directory instead. + */ +void +g_mount_guess_content_type_for_mount_root_async (GFile *mount_root, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + GuessContentData *op; + + op = g_new0 (GuessContentData, 1); + res = g_simple_async_result_new (G_OBJECT (mount_root), + callback, + user_data, + g_mount_guess_content_type_for_mount_root_async); + g_simple_async_result_set_op_res_gpointer (res, op, g_free); + + g_simple_async_result_run_in_thread (res, guess_content_thread, G_PRIORITY_DEFAULT, cancellable); + g_object_unref (res); +} + +/** + * g_mount_guess_content_type_for_mount_root_finish: + * @mount_root: a #GFile. + * @result: a #GAsyncResult. + * @error: a #GError location to store the error occuring, or %NULL to + * ignore. + * + * Like g_mount_guess_content_type_finish() but analyzes a given sub + * directory instead. + * + * Returns: a %NULL terminated array of content types or %NULL on + * error. Caller should free this array with g_strfreev() when done + * with it. + **/ +char ** +g_mount_guess_content_type_for_mount_root_finish (GFile *mount_root, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + GuessContentData *op; + + g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == g_mount_guess_content_type_for_mount_root_async); + + op = g_simple_async_result_get_op_res_gpointer (simple); + return op->guessed_content_type; +} + + #define __G_MOUNT_C__ #include "gioaliasdef.c" Index: gvolume.c =================================================================== --- gvolume.c (revision 6163) +++ gvolume.c (working copy) @@ -138,6 +138,28 @@ } /** + * g_volume_get_id: + * @volume: a #GVolume. + * + * Get an opaque textual identifier that can be used to pass a + * reference to @volume between processes. + * + * Returns: a string containing @volume's id. The returned string + * should be freed when no longer needed. + **/ +char * +g_volume_get_id (GVolume *volume) +{ + GVolumeIface *iface; + + g_return_val_if_fail (G_IS_VOLUME (volume), NULL); + + iface = G_VOLUME_GET_IFACE (volume); + + return (* iface->get_id) (volume); +} + +/** * g_volume_get_name: * @volume: a #GVolume. * Index: gmount.h =================================================================== --- gmount.h (revision 6163) +++ gmount.h (working copy) @@ -62,6 +62,7 @@ * @unmounted: The unmounted signal that is emitted when the #GMount have been unmounted. If the recipient is holding references to the object they should release them so the object can be finalized. * @get_root: Gets a #GFile to the root directory of the #GMount. * @get_name: Gets a string containing the name of the #GMount. + * @get_id: Returns an opaque textual identifier that can be used to pass a reference to a #GMount between processes. * @get_icon: Gets a #GIcon for the #GMount. * @get_uuid: Gets the UUID for the #GMount. The reference is typically based on the file system UUID for the mount in question and should be considered an opaque string. Returns %NULL if there is no UUID available. * @get_volume: Gets a #GVolume the mount is located on. Returns %NULL if the #GMount is not associated with a #GVolume. @@ -72,6 +73,9 @@ * @unmount_finish: Finishes an unmounting operation. * @eject: Starts ejecting a #GMount. * @eject_finish: Finishes an eject operation. + * @guess_content_type: Synchronously guesses the content type of content stored on the #GMount. + * @guess_content_type_async: Asynchronously guesses the content type of content stored on the #GMount. + * @guess_content_type_finish: Finishes guessing the content type of content stored on the #GMount. * * Interface for implementing operations for mounts. **/ @@ -88,6 +92,7 @@ GFile * (*get_root) (GMount *mount); char * (*get_name) (GMount *mount); + char * (*get_id) (GMount *mount); GIcon * (*get_icon) (GMount *mount); char * (*get_uuid) (GMount *mount); GVolume * (*get_volume) (GMount *mount); @@ -108,12 +113,25 @@ gboolean (*eject_finish) (GMount *mount, GAsyncResult *result, GError **error); + + char ** (*guess_content_type) (GMount *mount, + GError **error); + + void (*guess_content_type_async) (GMount *mount, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + + char ** (*guess_content_type_finish) (GMount *mount, + GAsyncResult *result, + GError **error); }; GType g_mount_get_type (void) G_GNUC_CONST; GFile * g_mount_get_root (GMount *mount); char * g_mount_get_name (GMount *mount); +char * g_mount_get_id (GMount *mount); GIcon * g_mount_get_icon (GMount *mount); char * g_mount_get_uuid (GMount *mount); GVolume * g_mount_get_volume (GMount *mount); @@ -135,6 +153,90 @@ GAsyncResult *result, GError **error); +char ** g_mount_guess_content_type (GMount *mount, + GError **error); + +void g_mount_guess_content_type_async (GMount *mount, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +char ** g_mount_guess_content_type_finish (GMount *mount, + GAsyncResult *result, + GError **error); + + +char ** g_mount_guess_content_type_for_mount_root (GFile *mount_root, + GError **error); + +void g_mount_guess_content_type_for_mount_root_async (GFile *mount_root, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +char ** g_mount_guess_content_type_for_mount_root_finish (GFile *mount_root, + GAsyncResult *result, + GError **error); + +/** + * G_MOUNT_CONTENT_AUDIO_DISC: + * + * The #GMount contains files representing audio tracks on a Compact Disc. + */ +#define G_MOUNT_CONTENT_AUDIO_DISC "x-content/audio-disc" + +/** + * G_MOUNT_CONTENT_PHOTO: + * + * The #GMount contains photos such as a <ulink + * url="http://en.wikipedia.org/wiki/Design_rule_for_Camera_File_system">DCF + * file system</ulink>. + */ +#define G_MOUNT_CONTENT_PHOTO "x-content/photo" + +/** + * G_MOUNT_CONTENT_VIDEO: + * + * The #GMount contains video files. + */ +#define G_MOUNT_CONTENT_VIDEO "x-content/video" + +/** + * G_MOUNT_CONTENT_MUSIC: + * + * The #GMount contains music. + */ +#define G_MOUNT_CONTENT_MUSIC "x-content/music" + +/** + * G_MOUNT_CONTENT_EBOOK: + * + * The #GMount contains electronic books. + */ +#define G_MOUNT_CONTENT_EBOOK "x-content/ebook" + +/** + * G_MOUNT_CONTENT_LOCATION_DATA: + * + * The #GMount contains location data captured via e.g. a GPS device. + */ +#define G_MOUNT_CONTENT_LOCATION_DATA "x-content/location-data" + +/** + * G_MOUNT_CONTENT_OPERATING_SYSTEM: + * + * The #GMount contains an operating system. + */ +#define G_MOUNT_CONTENT_OPERATING_SYSTEM "x-content/operating-system" + +/** + * G_MOUNT_CONTENT_BACKUP: + * + * The #GMount contains backup files. + */ +#define G_MOUNT_CONTENT_BACKUP "x-content/backup" + + G_END_DECLS #endif /* __G_MOUNT_H__ */ Index: gvolume.h =================================================================== --- gvolume.h (revision 6163) +++ gvolume.h (working copy) @@ -45,6 +45,7 @@ * @changed: Changed signal that is emitted when the volume's state has changed. * @removed: The removed signal that is emitted when the #GVolume have been removed. If the recipient is holding references to the object they should release them so the object can be finalized. * @get_name: Gets a string containing the name of the #GVolume. + * @get_id: Returns an opaque textual identifier that can be used to pass a reference to a #GVolume between processes. * @get_icon: Gets a #GIcon for the #GVolume. * @get_uuid: Gets the UUID for the #GVolume. The reference is typically based on the file system UUID for the mount in question and should be considered an opaque string. Returns %NULL if there is no UUID available. * @get_drive: Gets a #GDrive the volume is located on. Returns %NULL if the #GVolume is not associated with a #GDrive. @@ -72,6 +73,7 @@ /* Virtual Table */ char * (*get_name) (GVolume *volume); + char * (*get_id) (GVolume *volume); GIcon * (*get_icon) (GVolume *volume); char * (*get_uuid) (GVolume *volume); GDrive * (*get_drive) (GVolume *volume); @@ -98,6 +100,7 @@ GType g_volume_get_type (void) G_GNUC_CONST; char * g_volume_get_name (GVolume *volume); +char * g_volume_get_id (GVolume *volume); GIcon * g_volume_get_icon (GVolume *volume); char * g_volume_get_uuid (GVolume *volume); GDrive * g_volume_get_drive (GVolume *volume);
Index: hal/ghalmount.c =================================================================== --- hal/ghalmount.c (revision 1053) +++ hal/ghalmount.c (working copy) @@ -597,6 +597,17 @@ } static char * +g_hal_mount_get_id (GMount *mount) +{ + GHalMount *hal_mount = G_HAL_MOUNT (mount); + + if (hal_mount->device != NULL) + return g_strdup (hal_device_get_udi (hal_mount->device)); + else + return g_strdup (hal_mount->mount_path); +} + +static char * g_hal_mount_get_name (GMount *mount) { GHalMount *hal_mount = G_HAL_MOUNT (mount); @@ -901,10 +912,146 @@ } static void +_add_from_drive (GMount *mount, char ***content_types) +{ + unsigned int n, m; + GDrive *drive; + GPtrArray *additions; + + additions = g_ptr_array_new (); + + drive = g_mount_get_drive (mount); + if (drive != NULL) { + const char * const * dc; + + dc = g_drive_get_classification (drive); + + for (n = 0; dc[n] != NULL; n++) { + const char *c = dc[n]; + if (strcmp (G_DRIVE_CLASSIFICATION_STILL_CAMERA, c) == 0) + g_ptr_array_add (additions, g_strdup (G_MOUNT_CONTENT_PHOTO)); + else if (strcmp (G_DRIVE_CLASSIFICATION_VIDEO_CAMERA, c) == 0) + g_ptr_array_add (additions, g_strdup (G_MOUNT_CONTENT_VIDEO)); + else if (strcmp (G_DRIVE_CLASSIFICATION_AUDIO_PLAYER, c) == 0) + g_ptr_array_add (additions, g_strdup (G_MOUNT_CONTENT_MUSIC)); + else if (strcmp (G_DRIVE_CLASSIFICATION_PHOTO_PLAYER, c) == 0) + g_ptr_array_add (additions, g_strdup (G_MOUNT_CONTENT_PHOTO)); + else if (strcmp (G_DRIVE_CLASSIFICATION_EBOOK_READER, c) == 0) + g_ptr_array_add (additions, g_strdup (G_MOUNT_CONTENT_EBOOK)); + else if (strcmp (G_DRIVE_CLASSIFICATION_LOCATION_DEVICE, c) == 0) + g_ptr_array_add (additions, g_strdup (G_MOUNT_CONTENT_LOCATION_DATA)); + } + } + + if (additions->len > 0) + { + for (n = 0; (*content_types)[n] != NULL; n++) + { + gboolean have_it_already = FALSE; + for (m = 0; m < additions->len; m++) + { + if (strcmp (g_ptr_array_index (additions, m), (*content_types)[n]) == 0) + { + have_it_already = TRUE; + break; + } + } + if (!have_it_already) + g_ptr_array_add (additions, (*content_types)[n]); + } + g_free (*content_types); + + g_ptr_array_add (additions, NULL); + *content_types = (char **) g_ptr_array_free (additions, FALSE); + } +} + +static char ** +g_hal_mount_guess_content_type (GMount *mount, + GError **error) +{ + char **res; + GFile *mount_root; + GError *error2 = NULL; + + mount_root = g_mount_get_root (mount); + res = g_mount_guess_content_type_for_mount_root (mount_root, &error2); + if (error2 != NULL) + { + g_propagate_error (error, error2); + return NULL; + } + + _add_from_drive (mount, &res); + g_object_unref (mount_root); + + return res; +} + +typedef struct +{ + GAsyncReadyCallback callback; + gpointer user_data; + GObject *object; +} GuessContentData; + +static void +_guess_content_type_async_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GuessContentData *data = user_data; + data->callback (data->object, res, data->user_data); + g_object_unref (data->object); + g_free (data); +} + +static void +g_hal_mount_guess_content_type_async (GMount *mount, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GFile *mount_root; + GuessContentData *data; + + data = g_new0 (GuessContentData, 1); + data->callback = callback; + data->user_data = user_data; + data->object = g_object_ref (mount); + + mount_root = g_mount_get_root (mount); + g_mount_guess_content_type_for_mount_root_async (mount_root, cancellable, _guess_content_type_async_cb, data); +} + +static char ** +g_hal_mount_guess_content_type_finish (GMount *mount, + GAsyncResult *result, + GError **error) +{ + GObject *source_object; + char **content_types; + GError *error2 = NULL; + + source_object = g_async_result_get_source_object (result); + content_types = g_mount_guess_content_type_for_mount_root_finish (G_FILE (source_object), result, &error2); + if (error2 != NULL) + { + g_propagate_error (error, error2); + return NULL; + } + + _add_from_drive (mount, &content_types); + + return content_types; +} + +static void g_hal_mount_mount_iface_init (GMountIface *iface) { iface->get_root = g_hal_mount_get_root; iface->get_name = g_hal_mount_get_name; + iface->get_id = g_hal_mount_get_id; iface->get_icon = g_hal_mount_get_icon; iface->get_uuid = g_hal_mount_get_uuid; iface->get_drive = g_hal_mount_get_drive; @@ -915,6 +1062,9 @@ iface->unmount_finish = g_hal_mount_unmount_finish; iface->eject = g_hal_mount_eject; iface->eject_finish = g_hal_mount_eject_finish; + iface->guess_content_type = g_hal_mount_guess_content_type; + iface->guess_content_type_async = g_hal_mount_guess_content_type_async; + iface->guess_content_type_finish = g_hal_mount_guess_content_type_finish; } void Index: hal/ghalvolume.c =================================================================== --- hal/ghalvolume.c (revision 1053) +++ hal/ghalvolume.c (working copy) @@ -485,6 +485,14 @@ } static char * +g_hal_volume_get_id (GVolume *volume) +{ + GHalVolume *hal_volume = G_HAL_VOLUME (volume); + /* need to append the name since we may have multiple instances with the same udi (mixed disc case) */ + return g_strdup_printf ("%s_%s", hal_device_get_udi (hal_volume->device), hal_volume->name); +} + +static char * g_hal_volume_get_uuid (GVolume *volume) { GHalVolume *hal_volume = G_HAL_VOLUME (volume); @@ -802,6 +810,7 @@ g_hal_volume_volume_iface_init (GVolumeIface *iface) { iface->get_name = g_hal_volume_get_name; + iface->get_id = g_hal_volume_get_id; iface->get_icon = g_hal_volume_get_icon; iface->get_uuid = g_hal_volume_get_uuid; iface->get_drive = g_hal_volume_get_drive; Index: hal/ghaldrive.c =================================================================== --- hal/ghaldrive.c (revision 1053) +++ hal/ghaldrive.c (working copy) @@ -45,6 +45,7 @@ char *name; char *icon; char *device_path; + char **classification; gboolean can_eject; gboolean can_poll_for_media; @@ -84,6 +85,7 @@ } g_free (drive->device_path); + g_strfreev (drive->classification); if (drive->device != NULL) g_object_unref (drive->device); if (drive->pool != NULL) @@ -217,6 +219,13 @@ return s; } +static gboolean +_optical_can_write (HalDevice *d) +{ + /* TODO: maybe there's a better heuristic than this */ + return hal_device_get_property_int (d, "storage.cdrom.write_speed") > 0; +} + char * _drive_get_icon (HalDevice *d) { @@ -241,8 +250,7 @@ } else if (strcmp (drive_type, "cdrom") == 0) { - /* TODO: maybe there's a better heuristic than this */ - if (hal_device_get_property_int (d, "storage.cdrom.write_speed") > 0) + if (_optical_can_write (d)) s = g_strdup ("drive-optical-recorder"); else s = g_strdup ("drive-optical"); @@ -269,6 +277,9 @@ static void _do_update_from_hal (GHalDrive *d) { + unsigned int n; + char *classification[16]; + d->name = _drive_get_description (d->device); d->icon = _drive_get_icon (d->device); @@ -287,13 +298,79 @@ d->can_poll_for_media = FALSE; d->can_eject = FALSE; } + + /* map hal capabilities to x-drive types - be CAREFUL about extending this; see len + * of capabilities array above + */ + + n = 0; + if (hal_device_has_capability (d->device, "storage.cdrom")) + { + classification[n++] = g_strdup (G_DRIVE_CLASSIFICATION_OPTICAL); + if (_optical_can_write (d->device)) + classification[n++] = g_strdup (G_DRIVE_CLASSIFICATION_OPTICAL_BURN); + } + if (hal_device_has_capability (d->device, "portable_audio_player")) + classification[n++] = g_strdup (G_DRIVE_CLASSIFICATION_AUDIO_PLAYER); + + if (hal_device_has_capability (d->device, "portable_video_player")) /* TODO: get into hal spec */ + classification[n++] = g_strdup (G_DRIVE_CLASSIFICATION_VIDEO_PLAYER); + + if (hal_device_has_capability (d->device, "portable_photo_player")) /* TODO: get into hal spec */ + classification[n++] = g_strdup (G_DRIVE_CLASSIFICATION_PHOTO_PLAYER); + + if (hal_device_has_capability (d->device, "location_device")) /* TODO: get into hal spec */ + classification[n++] = g_strdup (G_DRIVE_CLASSIFICATION_LOCATION_DEVICE); + + /* TODO: fill in other classification */ + + if (n == 0) + { + d->classification = NULL; + } + else + { + classification[n] = NULL; + d->classification = g_strdupv (classification); + } + } +static +gboolean _g_strv_equal (char **a, char **b) +{ + unsigned int n; + + if (a == NULL && b != NULL) + return FALSE; + + if (a != NULL && b == NULL) + return FALSE; + + if (a == NULL && b == NULL) + return TRUE; + + /* both a and b are not NULL */ + + if (g_strv_length (a) != g_strv_length (b)) + return FALSE; + + /* both a and b has same length */ + for (n = 0; a[n] != NULL; n++) + { + if (strcmp (a[n], b[n]) != 0) + return FALSE; + } + + return TRUE; +} + static void _update_from_hal (GHalDrive *d, gboolean emit_changed) { char *old_name; char *old_icon; + char **old_classification; gboolean old_uses_removable_media; gboolean old_has_media; gboolean old_is_media_check_automatic; @@ -302,6 +379,7 @@ old_name = g_strdup (d->name); old_icon = g_strdup (d->icon); + old_classification = g_strdupv (d->classification); old_uses_removable_media = d->uses_removable_media; old_has_media = d->has_media; old_is_media_check_automatic = d->is_media_check_automatic; @@ -310,11 +388,14 @@ g_free (d->name); g_free (d->icon); + g_strfreev (d->classification); _do_update_from_hal (d); if (emit_changed) { - if (old_uses_removable_media != d->uses_removable_media || + + if (!_g_strv_equal (d->classification, old_classification) || + old_uses_removable_media != d->uses_removable_media || old_has_media != d->has_media || old_is_media_check_automatic != d->is_media_check_automatic || old_can_poll_for_media != d->can_poll_for_media || @@ -331,6 +412,7 @@ } g_free (old_name); g_free (old_icon); + g_strfreev (old_classification); } static void @@ -444,6 +526,40 @@ return g_strdup (hal_drive->name); } +static char * +g_hal_drive_get_id (GDrive *drive) +{ + GHalDrive *hal_drive = G_HAL_DRIVE (drive); + + return g_strdup (hal_device_get_udi (hal_drive->device)); +} + +static char * +g_hal_drive_get_vendor (GDrive *drive) +{ + const char *vendor; + GHalDrive *hal_drive = G_HAL_DRIVE (drive); + + vendor = hal_device_get_property_string (hal_drive->device, "storage.vendor"); + if (strlen (vendor) == 0) + return NULL; + else + return g_strdup (vendor); +} + +static char * +g_hal_drive_get_model (GDrive *drive) +{ + const char *model; + GHalDrive *hal_drive = G_HAL_DRIVE (drive); + + model = hal_device_get_property_string (hal_drive->device, "storage.model"); + if (strlen (model) == 0) + return NULL; + else + return g_strdup (model); +} + static GList * g_hal_drive_get_volumes (GDrive *drive) { @@ -832,11 +948,42 @@ return TRUE; } +static +const char * const * g_hal_drive_get_classification (GDrive *drive) +{ + GHalDrive *hal_drive = G_HAL_DRIVE (drive); + return (const char * const * ) hal_drive->classification; +} + +static +gboolean g_hal_drive_has_classification (GDrive *drive, + const char *classification) +{ + unsigned int n; + GHalDrive *hal_drive = G_HAL_DRIVE (drive); + + if (hal_drive->classification == NULL) + return FALSE; + + for (n = 0; hal_drive->classification != NULL; n++) + { + if (strcmp (hal_drive->classification[n], classification) == 0) + return TRUE; + } + + return FALSE; +} + + + static void g_hal_drive_drive_iface_init (GDriveIface *iface) { iface->get_name = g_hal_drive_get_name; + iface->get_id = g_hal_drive_get_id; + iface->get_vendor = g_hal_drive_get_vendor; + iface->get_model = g_hal_drive_get_model; iface->get_icon = g_hal_drive_get_icon; iface->has_volumes = g_hal_drive_has_volumes; iface->get_volumes = g_hal_drive_get_volumes; @@ -849,6 +996,8 @@ iface->eject_finish = g_hal_drive_eject_finish; iface->poll_for_media = g_hal_drive_poll_for_media; iface->poll_for_media_finish = g_hal_drive_poll_for_media_finish; + iface->get_classification = g_hal_drive_get_classification; + iface->has_classification = g_hal_drive_has_classification; } void
Index: src/nautilus-places-sidebar.c =================================================================== --- src/nautilus-places-sidebar.c (revision 13536) +++ src/nautilus-places-sidebar.c (working copy) @@ -492,11 +492,59 @@ return FALSE; } + static void +guessed_content_type_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GError *error; + char **guessed_content_type; + + error = NULL; + guessed_content_type = g_mount_guess_content_type_finish (G_MOUNT (source_object), res, &error); + if (error != NULL) { + g_warning ("Unabled to guess content type for mount: %s", error->message); + g_error_free (error); + } else { + if (guessed_content_type != NULL) { + int n; + for (n = 0; guessed_content_type[n] != NULL; n++) { + g_warning ("content %d: '%s'", n, guessed_content_type[n]); + } + g_strfreev (guessed_content_type); + } + } +} + +static void mount_added_callback (GVolumeMonitor *volume_monitor, GMount *mount, NautilusPlacesSidebar *sidebar) { +#if 0 + char **cs; + GError *error = NULL; + + cs = g_mount_guess_content_type (mount, &error); + if (cs != NULL) { + int n; + for (n = 0; cs[n] != NULL; n++) { + g_warning ("content %d: '%s'", n, cs[n]); + } + g_strfreev (cs); + } else { + if (error != NULL) { + g_warning ("error guessing content type: %s", error->message); + g_error_free (error); + } + } +#endif + g_mount_guess_content_type_async (mount, + NULL, + guessed_content_type_callback, + NULL); + update_places (sidebar); } @@ -553,6 +601,16 @@ GDrive *drive, NautilusPlacesSidebar *sidebar) { + const char * const *cs; + + cs = g_drive_get_classification (drive); + if (cs != NULL) { + int n; + for (n = 0; cs[n] != NULL; n++) { + g_warning ("c %d: '%s'", n, cs[n]); + } + } + update_places (sidebar); }
_______________________________________________ gtk-devel-list mailing list gtk-devel-list@gnome.org http://mail.gnome.org/mailman/listinfo/gtk-devel-list