gvfs/daemon/gvfsbackendcdda.c

1023 lines
30 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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 <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 <gudev/gudev.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
#ifdef HAVE_PARANOIA_NEW_INCLUDES
#include <cdio/paranoia/paranoia.h>
#else
#include <cdio/paranoia.h>
#endif
#include <cdio/cdio.h>
/* 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 doesnt 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 isnt 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"));
}