/* GVFS cdrom audio file system driver * * Copyright (C) 2007 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * Author: David Zeuthen */ /* NOTE: since we link the libcdio libs (GPLv2) into our process space * the combined work is GPLv2. This source file, however, is LGPLv2+. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "gvfsbackendcdda.h" #include "gvfsjobopenforread.h" #include "gvfsjobread.h" #include "gvfsjobseekread.h" #include "gvfsjobqueryinfo.h" #include "gvfsjobenumerate.h" #define DO_NOT_WANT_PARANOIA_COMPATIBILITY #ifdef HAVE_PARANOIA_NEW_INCLUDES #include #else #include #endif #include /* TODO: * * - GVFS integration * - g_vfs_backend_set_display_name() needs to work post mount * * - Metadata * - Use Musicbrainz to read metadata from the net * - libmusicbrainz appear to be a dead-end: http://musicbrainz.org/doc/libmusicbrainz * - Need to provide some UI for configuring musicbrainz; think proxy, local server, * lockdown (secure facilities don't want us to randomly connect to the Interwebs) * - Ideally use libjuicer for all this * - however it is currently private to sound-juicer and brings in GTK+, gnome-vfs, gconf... * - Use metadata for file names and display_name of our Mount (using g_vfs_backend_set_display_name()) * - Also encode metadata in the WAV header so transcoding to Vorbis or MP3 Just Works(tm) * - This is already done; see create_header() in this file * - see thread on gtk-devel-list for a plan * * - Scratched discs / error conditions from paranoia * - Need to handle this better... ideally caller passes a flag when opening the file to * specify whether he wants us to try hard to get the hard result (ripping) or whether * he's fine with some noise (playback) * * - Sector cache? Might be useful to maintain a cache of previously read sectors */ /*--------------------------------------------------------------------------------------------------------------*/ typedef struct { char *artist; char *title; int duration; /* Duration in seconds */ } GVfsBackendCddaTrack; struct _GVfsBackendCdda { GVfsBackend parent_instance; GUdevClient *gudev_client; guint64 size; char *device_path; cdrom_drive_t *drive; int num_open_files; /* Metadata from CD-Text */ char *album_title; char *album_artist; char *genre; GList *tracks; /* a GList of GVfsBackendCddaTrack */ }; G_DEFINE_TYPE (GVfsBackendCdda, g_vfs_backend_cdda, G_VFS_TYPE_BACKEND) static void release_device (GVfsBackendCdda *cdda_backend) { g_free (cdda_backend->device_path); cdda_backend->device_path = NULL; if (cdda_backend->drive != NULL) { cdio_cddap_close (cdda_backend->drive); cdda_backend->drive = NULL; } } /* Metadata related functions */ static void track_free (GVfsBackendCddaTrack *track) { if (track == NULL) return; g_free (track->artist); g_free (track->title); } static void release_metadata (GVfsBackendCdda *cdda_backend) { g_free (cdda_backend->album_title); cdda_backend->album_title = NULL; g_free (cdda_backend->album_artist); cdda_backend->album_artist = NULL; g_free (cdda_backend->genre); cdda_backend->genre = NULL; g_list_free_full (cdda_backend->tracks, (GDestroyNotify) track_free); cdda_backend->tracks = NULL; } #if LIBCDIO_VERSION_NUM < 84 static char * cdtext_string_to_utf8 (const char *string) { if (string == NULL) return NULL; /* CD-text doesn't specify encoding. In case outside ascii, assume latin-1. */ return g_convert (string, -1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL); } #endif static void fetch_metadata (GVfsBackendCdda *cdda_backend) { CdIo *cdio; track_t cdtrack, last_cdtrack; const cdtext_t *cdtext; cdio = cdio_open (cdda_backend->device_path, DRIVER_UNKNOWN); if (!cdio) return; #if LIBCDIO_VERSION_NUM >= 84 cdtext = cdio_get_cdtext (cdio); #else cdtext = cdio_get_cdtext (cdio, 0); #endif /* LIBCDIO_VERSION_NUM >= 84 */ if (cdtext) { #if LIBCDIO_VERSION_NUM >= 84 cdda_backend->album_title = g_strdup (cdtext_get_const (cdtext, CDTEXT_FIELD_TITLE, 0)); cdda_backend->album_artist = g_strdup (cdtext_get_const (cdtext, CDTEXT_FIELD_PERFORMER, 0)); cdda_backend->genre = g_strdup (cdtext_get_const (cdtext, CDTEXT_FIELD_GENRE, 0)); #else cdda_backend->album_title = cdtext_string_to_utf8 (cdtext_get_const (CDTEXT_TITLE, cdtext)); cdda_backend->album_artist = cdtext_string_to_utf8 (cdtext_get_const (CDTEXT_PERFORMER, cdtext)); cdda_backend->genre = cdtext_string_to_utf8 (cdtext_get_const (CDTEXT_GENRE, cdtext)); #endif /* LIBCDIO_VERSION_NUM >= 84 */ } cdtrack = cdio_get_first_track_num(cdio); last_cdtrack = cdtrack + cdio_get_num_tracks(cdio); for ( ; cdtrack < last_cdtrack; cdtrack++ ) { GVfsBackendCddaTrack *track; track = g_new0 (GVfsBackendCddaTrack, 1); #if LIBCDIO_VERSION_NUM < 84 cdtext = cdio_get_cdtext (cdio, cdtrack); #endif /* LIBCDIO_VERSION_NUM < 84 */ if (cdtext) { #if LIBCDIO_VERSION_NUM >= 84 track->title = g_strdup (cdtext_get_const (cdtext, CDTEXT_FIELD_TITLE, cdtrack)); track->artist = g_strdup (cdtext_get_const (cdtext, CDTEXT_FIELD_PERFORMER, cdtrack)); #else track->title = cdtext_string_to_utf8 (cdtext_get_const (CDTEXT_TITLE, cdtext)); track->artist = cdtext_string_to_utf8 (cdtext_get_const (CDTEXT_PERFORMER, cdtext)); #endif /* LIBCDIO_VERSION_NUM >= 84 */ } track->duration = cdio_get_track_sec_count (cdio, cdtrack) / CDIO_CD_FRAMES_PER_SEC; cdda_backend->tracks = g_list_append (cdda_backend->tracks, track); } cdio_destroy (cdio); } static void on_uevent (GUdevClient *client, gchar *action, GUdevDevice *device, gpointer user_data) { GVfsBackendCdda *cdda_backend = G_VFS_BACKEND_CDDA (user_data); const gchar *u_dev = g_udev_device_get_device_file (device); /* we unmount ourselves if the changed device is "our's" and it either gets * removed or changed to "no media" */ if (cdda_backend->device_path == NULL || g_strcmp0 (cdda_backend->device_path, u_dev) != 0) return; if (strcmp (action, "remove") == 0 || (strcmp (action, "change") == 0 && g_udev_device_get_property_as_int (device, "ID_CDROM_MEDIA") != 1)) { g_vfs_backend_force_unmount (G_VFS_BACKEND (cdda_backend)); g_signal_handlers_disconnect_by_func (cdda_backend->gudev_client, on_uevent, cdda_backend); } } static void g_vfs_backend_cdda_finalize (GObject *object) { GVfsBackendCdda *cdda_backend = G_VFS_BACKEND_CDDA (object); //g_warning ("finalizing %p", object); release_device (cdda_backend); release_metadata (cdda_backend); if (G_OBJECT_CLASS (g_vfs_backend_cdda_parent_class)->finalize) (*G_OBJECT_CLASS (g_vfs_backend_cdda_parent_class)->finalize) (object); } static void g_vfs_backend_cdda_init (GVfsBackendCdda *cdda_backend) { GVfsBackend *backend = G_VFS_BACKEND (cdda_backend); GMountSpec *mount_spec; char *x_content_types[] = {"x-content/audio-cdda", NULL}; //g_warning ("initing %p", cdda_backend); g_vfs_backend_set_display_name (backend, "cdda"); g_vfs_backend_set_x_content_types (backend, x_content_types); // TODO: HMM: g_vfs_backend_set_user_visible (backend, FALSE); mount_spec = g_mount_spec_new ("cdda"); g_vfs_backend_set_mount_spec (backend, mount_spec); g_mount_spec_unref (mount_spec); } static void do_mount (GVfsBackend *backend, GVfsJobMount *job, GMountSpec *mount_spec, GMountSource *mount_source, gboolean is_automount) { char *fuse_name; char *display_name; const char *host; GVfsBackendCdda *cdda_backend = G_VFS_BACKEND_CDDA (backend); GError *error = NULL; GMountSpec *cdda_mount_spec; //g_warning ("do_mount %p", cdda_backend); /* setup gudev */ const char *subsystems[] = {"block", NULL}; GUdevDevice *gudev_device; cdda_backend->gudev_client = g_udev_client_new (subsystems); if (cdda_backend->gudev_client == NULL) { release_device (cdda_backend); release_metadata (cdda_backend); g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot create gudev client")); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); return; } g_signal_connect (cdda_backend->gudev_client, "uevent", G_CALLBACK (on_uevent), cdda_backend); /* setup libcdio */ host = g_mount_spec_get (mount_spec, "host"); //g_warning ("host=%s", host); if (host == NULL) { g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("No drive specified")); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); release_device (cdda_backend); release_metadata (cdda_backend); return; } cdda_backend->device_path = g_strdup_printf ("/dev/%s", host); gudev_device = g_udev_client_query_by_device_file (cdda_backend->gudev_client, cdda_backend->device_path); if (gudev_device != NULL) cdda_backend->size = g_udev_device_get_sysfs_attr_as_uint64 (gudev_device, "size") * 512; g_object_unref (gudev_device); cdda_backend->drive = cdio_cddap_identify (cdda_backend->device_path, 0, NULL); if (cdda_backend->drive == NULL) { g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot find drive %s"), cdda_backend->device_path); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); release_device (cdda_backend); release_metadata (cdda_backend); return; } fetch_metadata (cdda_backend); if (cdio_cddap_open (cdda_backend->drive) != 0) { g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Drive %s does not contain audio files"), cdda_backend->device_path); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); release_device (cdda_backend); release_metadata (cdda_backend); return; } /* Translator: %s is the device the disc is inserted into. 'cdda' is the name name of the backend and shouldn't be translated. */ fuse_name = g_strdup_printf (_("cdda mount on %s"), host); display_name = g_strdup_printf (_("Audio Disc")); g_vfs_backend_set_stable_name (backend, fuse_name); g_vfs_backend_set_display_name (backend, display_name); g_free (display_name); g_free (fuse_name); g_vfs_backend_set_icon_name (backend, "media-optical-audio"); g_vfs_backend_set_symbolic_icon_name (backend, "media-optical-symbolic"); g_vfs_job_succeeded (G_VFS_JOB (job)); cdda_mount_spec = g_mount_spec_new ("cdda"); g_mount_spec_set (cdda_mount_spec, "host", host); g_vfs_backend_set_mount_spec (backend, cdda_mount_spec); g_mount_spec_unref (cdda_mount_spec); //g_warning ("mounted %p", cdda_backend); } static gboolean try_mount (GVfsBackend *backend, GVfsJobMount *job, GMountSpec *mount_spec, GMountSource *mount_source, gboolean is_automount) { const char *host; GError *error = NULL; GMountSpec *cdda_mount_spec; //g_warning ("try_mount %p", backend); /* TODO: Hmm.. apparently we have to set the mount spec in * try_mount(); doing it in mount() won't work.. */ host = g_mount_spec_get (mount_spec, "host"); //g_warning ("tm host=%s", host); if (host == NULL) { g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("No drive specified")); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); return TRUE; } cdda_mount_spec = g_mount_spec_new ("cdda"); g_mount_spec_set (cdda_mount_spec, "host", host); g_vfs_backend_set_mount_spec (backend, cdda_mount_spec); g_mount_spec_unref (cdda_mount_spec); return FALSE; } static void do_unmount (GVfsBackend *backend, GVfsJobUnmount *job, GMountUnmountFlags flags, GMountSource *mount_source) { GError *error; GVfsBackendCdda *cdda_backend = G_VFS_BACKEND_CDDA (backend); if (cdda_backend->num_open_files > 0) { error = g_error_new (G_IO_ERROR, G_IO_ERROR_BUSY, ngettext ("File system is busy: %d open file", "File system is busy: %d open files", cdda_backend->num_open_files), cdda_backend->num_open_files); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); return; } release_device (cdda_backend); release_metadata (cdda_backend); g_signal_handlers_disconnect_by_func (cdda_backend->gudev_client, on_uevent, cdda_backend); g_vfs_job_succeeded (G_VFS_JOB (job)); //g_warning ("unmounted %p", backend); } /* returns -1 if we couldn't map */ static int get_track_num_from_name (const char *filename) { int n; char *basename; basename = g_path_get_basename (filename); if (sscanf (basename, "Track %d.wav", &n) == 1 && g_str_has_suffix (basename, ".wav")) { g_free (basename); return n; } return -1; } typedef struct { cdrom_paranoia_t *paranoia; long size; /* size of file being read */ long header_size; /* size of the header */ long content_size; /* size of content after the header */ long cursor; /* cursor into the file being read */ long first_sector; /* first sector of raw PCM audio data */ long last_sector; /* last sector of raw PCM audio data */ long sector_cursor; /* sector we're currently at */ char *header; /* header payload */ /* These following two fields are used for caching the last read sector. This * is to avoid seeking back if fewer bytes than whole sector is requested. */ long buf_at_sector_num; /* the sector that is cached */ char buf_at_sector[CDIO_CD_FRAMESIZE_RAW]; /* the data of the sector */ } ReadHandle; static void free_read_handle (ReadHandle *read_handle) { if (read_handle->paranoia != NULL) cdio_paranoia_free (read_handle->paranoia); g_free (read_handle->header); g_free (read_handle); } static char * create_header (GVfsBackendCdda *cdda_backend, long *header_size, long content_size) { char *artist; char *title; const char *software; size_t artist_len; size_t title_len; size_t software_len; char *header; char *ptr; int var; /* See http://www.saettler.com/RIFFMCI/riffmci.html for the spec. * * artist -> IART * title -> INAM * track_number -> ?? (TODO: work with GStreamer people on coordinate with the wavparse plugin) * * software -> ISFT */ //artist = g_strdup ("Homer Simpson"); //title = g_strdup ("Simpsons Jail House Rock"); /* TODO: fill in from metadata */ artist = NULL; title = NULL; software = "gvfs-cdda using libcdio " CDIO_VERSION; artist_len = 0; title_len = 0; /* ensure even length and include room for the chunk */ if (artist != NULL) artist_len = 2 * ((strlen (artist) + 2) / 2) + 8; if (title != NULL) title_len = 2 * ((strlen (title) + 2) / 2) + 8; software_len = 2 * ((strlen (software) + 2) / 2) + 8; *header_size = 44; *header_size += 12; /* for LIST INFO */ *header_size += artist_len; *header_size += title_len; *header_size += software_len; header = g_new0 (char, *header_size); ptr = header; memcpy (ptr, "RIFF", 4); ptr += 4; var = content_size + *header_size - 8; memcpy (ptr, &var, 4); ptr += 4; memcpy (ptr, "WAVE", 4); ptr += 4; memcpy (ptr, "fmt ", 4); ptr += 4; var = 16; memcpy (ptr, &var, 4); ptr += 4; var = 1; memcpy (ptr, &var, 2); ptr += 2; var = 2; memcpy (ptr, &var, 2); ptr += 2; var = 44100; memcpy (ptr, &var, 4); ptr += 4; var = 44100 * 2 * 2; memcpy (ptr, &var, 4); ptr += 4; var = 4; memcpy (ptr, &var, 2); ptr += 2; var = 16; memcpy (ptr, &var, 2); ptr += 2; memcpy (ptr, "LIST", 4); ptr += 4; var = 4 + artist_len + title_len + software_len; memcpy (ptr, &var, 4); ptr += 4; memcpy (ptr, "INFO", 4); ptr += 4; #if 0 if (artist != NULL) { memcpy (ptr, "IART", 4); var = artist_len - 8; memcpy (ptr + 4, &var, 4); strncpy (ptr + 8, artist, artist_len); ptr += artist_len; } if (title != NULL) { memcpy (ptr, "INAM", 4); var = title_len - 8; memcpy (ptr + 4, &var, 4); strncpy (ptr + 8, title, title_len); ptr += title_len; } #endif memcpy (ptr, "ISFT", 4); var = software_len - 8; memcpy (ptr + 4, &var, 4); strncpy (ptr + 8, software, software_len); ptr += software_len; memcpy (ptr, "data", 4); ptr += 4; memcpy (ptr, &content_size, 4); ptr += 4; g_free (artist); g_free (title); return header; } static void do_open_for_read (GVfsBackend *backend, GVfsJobOpenForRead *job, const char *filename) { int track_num; GError *error; ReadHandle *read_handle; GVfsBackendCdda *cdda_backend = G_VFS_BACKEND_CDDA (backend); //g_warning ("open_for_read (%s)", filename); read_handle = g_new0 (ReadHandle, 1); track_num = get_track_num_from_name (job->filename); if (track_num == -1) { error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file %s on drive %s"), job->filename, cdda_backend->device_path); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); free_read_handle (read_handle); return; } read_handle->first_sector = cdio_cddap_track_firstsector (cdda_backend->drive, track_num); read_handle->last_sector = cdio_cddap_track_lastsector (cdda_backend->drive, track_num); read_handle->sector_cursor = -1; read_handle->cursor = 0; read_handle->buf_at_sector_num = -1; read_handle->content_size = ((read_handle->last_sector - read_handle->first_sector) + 1) * CDIO_CD_FRAMESIZE_RAW; read_handle->header = create_header (cdda_backend, &(read_handle->header_size), read_handle->content_size); read_handle->size = read_handle->header_size + read_handle->content_size; read_handle->paranoia = cdio_paranoia_init (cdda_backend->drive); cdio_paranoia_modeset (read_handle->paranoia, PARANOIA_MODE_DISABLE); cdda_backend->num_open_files++; g_vfs_job_open_for_read_set_can_seek (job, TRUE); g_vfs_job_open_for_read_set_handle (job, GINT_TO_POINTER (read_handle)); g_vfs_job_succeeded (G_VFS_JOB (job)); } /* We have to pass in a callback to paranoia_read, even though we don't use it */ static void paranoia_callback (long int inpos, paranoia_cb_mode_t function) { } static void do_read (GVfsBackend *backend, GVfsJobRead *job, GVfsBackendHandle handle, char *buffer, gsize bytes_requested) { GVfsBackendCdda *cdda_backend = G_VFS_BACKEND_CDDA (backend); ReadHandle *read_handle = (ReadHandle *) handle; int bytes_read; long skip_bytes; char *readbuf; long desired_sector; int bytes_to_copy; long cursor_in_stream; //g_warning ("read (%"G_GSSIZE_FORMAT") (@ %ld)", bytes_requested, read_handle->cursor); /* header */ if (read_handle->cursor < read_handle->header_size) { skip_bytes = read_handle->cursor; bytes_read = read_handle->header_size - read_handle->cursor; readbuf = read_handle->header + skip_bytes; goto read_data_done; } /* EOF */ if (read_handle->cursor >= read_handle->size) { skip_bytes = 0; bytes_read = 0; readbuf = NULL; goto read_data_done; } cursor_in_stream = read_handle->cursor - read_handle->header_size; desired_sector = cursor_in_stream / CDIO_CD_FRAMESIZE_RAW + read_handle->first_sector; if (desired_sector == read_handle->buf_at_sector_num) { /* got it cached */ /* skip some bytes */ skip_bytes = cursor_in_stream - (desired_sector - read_handle->first_sector) * CDIO_CD_FRAMESIZE_RAW; readbuf = read_handle->buf_at_sector + skip_bytes; bytes_read = CDIO_CD_FRAMESIZE_RAW - skip_bytes; //g_warning ("read from cache for cursor @ %ld", read_handle->buf_at_sector_num); } else { /* first check that we're at the right sector */ if (desired_sector != read_handle->sector_cursor) { cdio_paranoia_seek (read_handle->paranoia, desired_sector, SEEK_SET); read_handle->sector_cursor = desired_sector; //g_warning ("seeking cursor to %ld", read_handle->sector_cursor); } /* skip some bytes */ skip_bytes = cursor_in_stream - (read_handle->sector_cursor - read_handle->first_sector) * CDIO_CD_FRAMESIZE_RAW; //g_warning ("advanced cursor to %ld", read_handle->sector_cursor); readbuf = (char *) cdio_paranoia_read (read_handle->paranoia, paranoia_callback); if (readbuf == NULL) { int errsv = errno; g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, g_io_error_from_errno (errsv), /* Translators: paranoia is the name of the cd audio reading library */ _("Error from “paranoia” on drive %s"), cdda_backend->device_path); return; } read_handle->buf_at_sector_num = read_handle->sector_cursor; memcpy (read_handle->buf_at_sector, readbuf, CDIO_CD_FRAMESIZE_RAW); read_handle->sector_cursor++; readbuf += skip_bytes; bytes_read = CDIO_CD_FRAMESIZE_RAW - skip_bytes; } read_data_done: bytes_to_copy = bytes_read; if (bytes_requested < bytes_read) bytes_to_copy = bytes_requested; read_handle->cursor += bytes_to_copy; cursor_in_stream = read_handle->cursor - read_handle->header_size; if (bytes_to_copy > 0 && readbuf != NULL) memcpy (buffer, readbuf, bytes_to_copy); g_vfs_job_read_set_size (job, bytes_to_copy); g_vfs_job_succeeded (G_VFS_JOB (job)); } static void do_seek_on_read (GVfsBackend *backend, GVfsJobSeekRead *job, GVfsBackendHandle handle, goffset offset, GSeekType type) { GVfsBackendCdda *cdda_backend = G_VFS_BACKEND_CDDA (backend); ReadHandle *read_handle = (ReadHandle *) handle; long new_offset; //g_warning ("seek_on_read (%d, %d)", (int)offset, type); switch (type) { default: case G_SEEK_SET: new_offset = offset; break; case G_SEEK_CUR: new_offset = read_handle->cursor + offset; break; case G_SEEK_END: new_offset = read_handle->size + offset; break; } if (new_offset < 0) { g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_FAILED, _("Error seeking in stream on drive %s"), cdda_backend->device_path); } else { read_handle->cursor = new_offset; g_vfs_job_seek_read_set_offset (job, offset); g_vfs_job_succeeded (G_VFS_JOB (job)); } } static void do_close_read (GVfsBackend *backend, GVfsJobCloseRead *job, GVfsBackendHandle handle) { ReadHandle *read_handle = (ReadHandle *) handle; GVfsBackendCdda *cdda_backend = G_VFS_BACKEND_CDDA (backend); //g_warning ("close ()"); free_read_handle (read_handle); cdda_backend->num_open_files--; g_vfs_job_succeeded (G_VFS_JOB (job)); } static void set_info_for_track (GVfsBackendCdda *cdda_backend, GFileInfo *info, int track_num) { char *header; long first; long last; long header_size; long content_size; GIcon *icon; GVfsBackendCddaTrack *track; first = cdio_cddap_track_firstsector (cdda_backend->drive, track_num); last = cdio_cddap_track_lastsector (cdda_backend->drive, track_num); content_size = (last - first + 1) * CDIO_CD_FRAMESIZE_RAW; header = create_header (cdda_backend, &header_size, content_size); g_free (header); //g_warning ("size=%ld for track %d", size, track_num); g_file_info_set_file_type (info, G_FILE_TYPE_REGULAR); g_file_info_set_content_type (info, "audio/x-wav"); g_file_info_set_size (info, header_size + content_size); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, TRUE); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, FALSE); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, FALSE); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, FALSE); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, FALSE); track = g_list_nth_data (cdda_backend->tracks, track_num - 1); if (track) { if (track->title) g_file_info_set_attribute_string (info, "xattr::org.gnome.audio.title", track->title); if (track->artist) g_file_info_set_attribute_string (info, "xattr::org.gnome.audio.artist", track->artist); g_file_info_set_attribute_uint64 (info, "xattr::org.gnome.audio.duration", track->duration); } icon = g_themed_icon_new ("audio-x-generic"); g_file_info_set_icon (info, icon); g_object_unref (icon); } #define SET_INFO(attr, value) if (value) g_file_info_set_attribute_string (info, attr, value); static void do_query_info (GVfsBackend *backend, GVfsJobQueryInfo *job, const char *filename, GFileQueryInfoFlags flags, GFileInfo *info, GFileAttributeMatcher *matcher) { GVfsBackendCdda *cdda_backend = G_VFS_BACKEND_CDDA (backend); int track_num; GError *error; //g_warning ("get_file_info (%s)", filename); if (strcmp (filename, "/") == 0) { GIcon *icon; g_file_info_set_display_name (info, _("Audio Disc")); /* TODO: fill in from metadata */ g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY); g_file_info_set_content_type (info, "inode/directory"); if (cdda_backend->album_title) SET_INFO ("xattr::org.gnome.audio.title", cdda_backend->album_title); if (cdda_backend->album_artist) SET_INFO ("xattr::org.gnome.audio.artist", cdda_backend->album_artist); SET_INFO ("xattr::org.gnome.audio.genre", cdda_backend->genre); g_file_info_set_size (info, 0); icon = g_themed_icon_new ("folder"); g_file_info_set_icon (info, icon); g_object_unref (icon); } else { if (*filename == '/') { g_file_info_set_name (info, filename + 1); g_file_info_set_display_name (info, filename + 1); } else { g_file_info_set_name (info, filename); g_file_info_set_display_name (info, filename); } track_num = get_track_num_from_name (filename); if (track_num == -1) { error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file")); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); return; } if (track_num > cdda_backend->drive->tracks) { error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("File doesn’t exist")); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); return; } if (! cdio_cddap_track_audiop (cdda_backend->drive, track_num)) { error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("The file does not exist or isn’t an audio track")); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); return; } set_info_for_track (cdda_backend, info, track_num); } g_vfs_job_succeeded (G_VFS_JOB (job)); } static void do_enumerate (GVfsBackend *backend, GVfsJobEnumerate *job, const char *filename, GFileAttributeMatcher *matcher, GFileQueryInfoFlags flags) { GVfsBackendCdda *cdda_backend = G_VFS_BACKEND_CDDA (backend); GFileInfo *info; GList *l; int n; //g_warning ("enumerate (%s)", filename); l = NULL; for (n = 1; n <= cdda_backend->drive->tracks; n++) { char *name; /* not audio track */ if (! cdio_cddap_track_audiop (cdda_backend->drive, n)) continue; info = g_file_info_new (); name = g_strdup_printf ("Track %d.wav", n); g_file_info_set_name (info, name); g_file_info_set_display_name (info, name); g_free (name); set_info_for_track (cdda_backend, info, n); l = g_list_append (l, info); } g_vfs_job_succeeded (G_VFS_JOB (job)); g_vfs_job_enumerate_add_infos (job, l); g_list_free_full (l, g_object_unref); g_vfs_job_enumerate_done (job); } static void do_query_fs_info (GVfsBackend *backend, GVfsJobQueryFsInfo *job, const char *filename, GFileInfo *info, GFileAttributeMatcher *attribute_matcher) { GVfsBackendCdda *cdda_backend = G_VFS_BACKEND_CDDA (backend); g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "cdda"); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE, FALSE); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, TRUE); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW, G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL); if (cdda_backend->size > 0) { g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE, cdda_backend->size); } g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, 0); g_vfs_job_succeeded (G_VFS_JOB (job)); } static void g_vfs_backend_cdda_class_init (GVfsBackendCddaClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass); gobject_class->finalize = g_vfs_backend_cdda_finalize; backend_class->try_mount = try_mount; backend_class->mount = do_mount; backend_class->unmount = do_unmount; backend_class->open_for_read = do_open_for_read; backend_class->read = do_read; backend_class->seek_on_read = do_seek_on_read; backend_class->close_read = do_close_read; backend_class->query_info = do_query_info; backend_class->query_fs_info = do_query_fs_info; backend_class->enumerate = do_enumerate; } void g_vfs_cdda_daemon_init (void) { g_set_application_name (_("Audio CD File System Service")); }