diff options
Diffstat (limited to 'trunk/daemon/gvfsbackendcdda.c')
-rw-r--r-- | trunk/daemon/gvfsbackendcdda.c | 1035 |
1 files changed, 1035 insertions, 0 deletions
diff --git a/trunk/daemon/gvfsbackendcdda.c b/trunk/daemon/gvfsbackendcdda.c new file mode 100644 index 00000000..c97aa447 --- /dev/null +++ b/trunk/daemon/gvfsbackendcdda.c @@ -0,0 +1,1035 @@ +/* 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., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: David Zeuthen <davidz@redhat.com> + */ + +/* NOTE: since we link the libcdio libs (GPLv2) into our process space + * the combined work is GPLv2. This source file, however, is LGPLv2+. + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> + +#include <glib/gstdio.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <libhal.h> +#include <dbus/dbus.h> + +#include "gvfsbackendcdda.h" +#include "gvfsjobopenforread.h" +#include "gvfsjobread.h" +#include "gvfsjobseekread.h" +#include "gvfsjobqueryinfo.h" +#include "gvfsjobenumerate.h" + +#define DO_NOT_WANT_PARANOIA_COMPATIBILITY +#include <cdio/paranoia.h> +#include <cdio/cdio.h> + +/* TODO: + * + * - GVFS integration + * - g_vfs_backend_set_display_name() needs to work post mount + * + * - Metadata + * - Use CD-Text to read metadata from the physical disc + * - http://en.wikipedia.org/wiki/CD-Text + * - libcdio can do this + * - 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; + + DBusConnection *dbus_connection; + LibHalContext *hal_ctx; + char *hal_udi; + + 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_foreach (cdda_backend->tracks, (GFunc) track_free, NULL); + g_list_free (cdda_backend->tracks); + cdda_backend->tracks = NULL; +} + +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; + + cdtext = cdio_get_cdtext(cdio, 0); + if (cdtext) { + cdda_backend->album_title = g_strdup (cdtext_get (CDTEXT_TITLE, cdtext)); + cdda_backend->album_artist = g_strdup (cdtext_get (CDTEXT_PERFORMER, cdtext)); + cdda_backend->genre = g_strdup (cdtext_get (CDTEXT_GENRE, cdtext)); + } + + 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); + cdtext = cdio_get_cdtext(cdio, cdtrack); + if (cdtext) { + track->title = g_strdup (cdtext_get (CDTEXT_TITLE, cdtext)); + track->artist = g_strdup (cdtext_get (CDTEXT_PERFORMER, cdtext)); + } + 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 +find_udi_for_device (GVfsBackendCdda *cdda_backend) +{ + int num_devices; + char **devices; + int n; + + cdda_backend->hal_udi = NULL; + + devices = libhal_manager_find_device_string_match (cdda_backend->hal_ctx, + "block.device", + cdda_backend->device_path, + &num_devices, + NULL); + if (devices != NULL) + { + for (n = 0; n < num_devices && cdda_backend->hal_udi == NULL; n++) + { + char *udi = devices[n]; + LibHalPropertySet *ps; + + ps = libhal_device_get_all_properties (cdda_backend->hal_ctx, udi, NULL); + if (ps != NULL) + { + if (libhal_ps_get_bool (ps, "block.is_volume")) + { + cdda_backend->hal_udi = g_strdup (udi); + cdda_backend->size = libhal_ps_get_uint64 (ps, "volume.size"); + } + } + + libhal_free_property_set (ps); + } + } + libhal_free_string_array (devices); + + /*g_warning ("found udi '%s'", cdda_backend->hal_udi);*/ +} + +static void +_hal_device_removed (LibHalContext *hal_ctx, const char *udi) +{ + GVfsBackendCdda *cdda_backend; + + cdda_backend = G_VFS_BACKEND_CDDA (libhal_ctx_get_user_data (hal_ctx)); + + if (cdda_backend->hal_udi != NULL && strcmp (udi, cdda_backend->hal_udi) == 0) + { + /*g_warning ("we have been removed!");*/ + /* TODO: need a cleaner way to force unmount ourselves */ + exit (1); + } +} + +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; + DBusError dbus_error; + + //g_warning ("do_mount %p", cdda_backend); + + /* setup libhal */ + + dbus_error_init (&dbus_error); + cdda_backend->dbus_connection = dbus_bus_get_private (DBUS_BUS_SYSTEM, &dbus_error); + if (dbus_error_is_set (&dbus_error)) + { + release_device (cdda_backend); + release_metadata (cdda_backend); + dbus_error_free (&dbus_error); + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot connect to the system bus")); + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + return; + } + + cdda_backend->hal_ctx = libhal_ctx_new (); + if (cdda_backend->hal_ctx == NULL) + { + release_device (cdda_backend); + release_metadata (cdda_backend); + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot create libhal context")); + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + return; + } + + _g_dbus_connection_integrate_with_main (cdda_backend->dbus_connection); + libhal_ctx_set_dbus_connection (cdda_backend->hal_ctx, cdda_backend->dbus_connection); + + if (!libhal_ctx_init (cdda_backend->hal_ctx, &dbus_error)) + { + release_device (cdda_backend); + release_metadata (cdda_backend); + dbus_error_free (&dbus_error); + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot initialize libhal")); + g_vfs_job_failed_from_error (G_VFS_JOB (job), error); + g_error_free (error); + return; + } + + libhal_ctx_set_device_removed (cdda_backend->hal_ctx, _hal_device_removed); + libhal_ctx_set_user_data (cdda_backend->hal_ctx, 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); + + find_udi_for_device (cdda_backend); + + 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 */ + 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_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) +{ + 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_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 (GVfsBackendCdda *cdda_backend, const char *filename) +{ + int n; + char *basename; + + basename = g_path_get_basename (filename); + if (sscanf (basename, "Track %d.wav", &n) == 1) + { + 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 (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; + } + + 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 (cdda_backend, 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 || new_offset >= read_handle->size) + { + 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"); + SET_INFO ("xattr::org.gnome.audio.title", cdda_backend->album_title); + 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 + { + g_file_info_set_name (info, filename); + g_file_info_set_display_name (info, filename); + + track_num = get_track_num_from_name (cdda_backend, 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_foreach (l, (GFunc) g_object_unref, NULL); + g_list_free (l); + + 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_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 Filesystem Service")); +} |