From: Richard Kettlewell Date: Wed, 9 Jun 2010 19:23:09 +0000 (+0100) Subject: Don't keep audio files open except when they are actually being read X-Git-Tag: branchpoint-5.1~77 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/commitdiff_plain/fdb4e27ac67437dd655dc4b70613b977eeabccb1?hp=28e9141a9e7e4a30e6cd384ada7c87edab7d8efb Don't keep audio files open except when they are actually being read right that moment ("hands-off reading"). Automatically rescan when anything is mounted or unmounted. Both are intended to improve compatibility with removable storage. --- diff --git a/CHANGES.html b/CHANGES.html index ed1e9b4..53c9fa0 100644 --- a/CHANGES.html +++ b/CHANGES.html @@ -68,6 +68,26 @@ span.command {

This file documents recent user-visible changes to DisOrder.

+

Changes up to version 5.1

+ +
+ +

Removable Device Support

+ +
+ +

The server will now automatically initiate a rescan when a filesystem is + mounted or unmounted. (Use the mount_rescan option if you want to + suppress this behavior.)

+ +

The server takes care not to hold audio files open unnecessarily, so + that devices can be unmounted even if tracks from them are currently being + buffered.

+ +
+ +
+

Changes up to version 5.0

diff --git a/configure.ac b/configure.ac index b0290c9..69cdea3 100644 --- a/configure.ac +++ b/configure.ac @@ -522,7 +522,6 @@ if test $want_server = yes; then AC_CHECK_HEADERS([db.h],[:],[ missing_headers="$missing_headers $ac_header" ]) - AC_CHECK_HEADERS([FLAC/file_decoder.h]) fi AC_CHECK_HEADERS([dlfcn.h gcrypt.h \ getopt.h iconv.h langinfo.h \ @@ -547,6 +546,18 @@ AC_C_BIGENDIAN AC_CHECK_TYPES([struct sockaddr_in6],,,[AC_INCLUDES_DEFAULT #include ]) +# Figure out how we'll check for devices being mounted and unmounted +AC_CACHE_CHECK([for list of mounted filesystems],[rjk_cv_mtab],[ + if test -e /etc/mtab; then + rjk_cv_mtab=/etc/mtab + else + rjk_cv_mtab=none + fi +]) +if test $rjk_cv_mtab != none; then + AC_DEFINE_UNQUOTED([PATH_MTAB],["$rjk_cv_mtab"],[path to file containing mount list]) +fi + # enable -Werror when we check for certain characteristics: old_CFLAGS="${CFLAGS}" @@ -639,7 +650,7 @@ if test ! -z "$missing_functions"; then fi # Functions we can take or leave -AC_CHECK_FUNCS([fls]) +AC_CHECK_FUNCS([fls getfsstat]) if test $want_server = yes; then # had better be version 3 or later diff --git a/doc/disorder_config.5.in b/doc/disorder_config.5.in index a5b55ba..0c8f7c8 100644 --- a/doc/disorder_config.5.in +++ b/doc/disorder_config.5.in @@ -325,7 +325,6 @@ For \fBapi oss\fR the possible values are: .RS .TP 8 .B pcm - Output level for the audio device. This is probably what you want and is the default. .TP @@ -457,6 +456,10 @@ The default is 0. .IP For \fBapi coreaudio\fR, volume setting is not currently supported. .TP +.B mount_rescan yes\fR|\fBno +Determines whether mounts and unmounts will cause an automatic rescan. +The default is \fByes\fR. +.TP .B multicast_loop yes\fR|\fBno Determines whether multicast packets are loop backed to the sending host. The default is \fByes\fR. diff --git a/lib/Makefile.am b/lib/Makefile.am index be12f1c..ff0eb8b 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -52,6 +52,7 @@ libdisorder_a_SOURCES=charset.c charset.h \ heap.h \ hex.c hex.h \ hostname.c hostname.h \ + hreader.c hreader.h \ ifreq.c ifreq.h \ inputline.c inputline.h \ kvp.c kvp.h \ diff --git a/lib/configuration.c b/lib/configuration.c index 45f2329..0b173d4 100644 --- a/lib/configuration.c +++ b/lib/configuration.c @@ -1080,6 +1080,7 @@ static const struct conf conf[] = { { C(lock), &type_boolean, validate_any }, { C(mail_sender), &type_string, validate_any }, { C(mixer), &type_string, validate_any }, + { C(mount_rescan), &type_boolean, validate_any }, { C(multicast_loop), &type_boolean, validate_any }, { C(multicast_ttl), &type_integer, validate_non_negative }, { C(namepart), &type_namepart, validate_any }, @@ -1373,6 +1374,7 @@ static struct config *config_default(void) { c->sox_generation = DEFAULT_SOX_GENERATION; c->playlist_max = INT_MAX; /* effectively no limit */ c->playlist_lock_timeout = 10; /* 10s */ + c->mount_rescan = 1; /* Default stopwords */ if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords)) exit(1); diff --git a/lib/configuration.h b/lib/configuration.h index 617d929..22e107d 100644 --- a/lib/configuration.h +++ b/lib/configuration.h @@ -290,7 +290,10 @@ struct config { /** @brief Maximum bias */ long new_bias; - + + /** @brief Rescan on (un)mount */ + int mount_rescan; + /* derived values: */ int nparts; /* number of distinct name parts */ char **parts; /* name part list */ diff --git a/lib/hreader.c b/lib/hreader.c new file mode 100644 index 0000000..bdf3c07 --- /dev/null +++ b/lib/hreader.c @@ -0,0 +1,114 @@ +/* + * This file is part of DisOrder + * Copyright (C) 2010 Richard Kettlewell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/** @file lib/hreader.c + * @brief Hands-off reader - read files without keeping them open + */ +#include +#include "hreader.h" +#include "mem.h" +#include +#include +#include +#include +#include + +static int hreader_fill(struct hreader *h, off_t offset); + +int hreader_init(const char *path, struct hreader *h) { + struct stat sb; + if(stat(path, &sb) < 0) + return -1; + memset(h, 0, sizeof *h); + h->path = xstrdup(path); + h->size = sb.st_size; + h->bufsize = 65536; + h->buffer = xmalloc_noptr(h->bufsize); + return 0; +} + +void hreader_close(struct hreader *h) { + xfree(h->path); + xfree(h->buffer); +} + +int hreader_read(struct hreader *h, void *buffer, size_t n) { + int r = hreader_pread(h, buffer, n, h->read_offset); + if(r > 0) + h->read_offset += r; + return r; +} + +int hreader_pread(struct hreader *h, void *buffer, size_t n, off_t offset) { + size_t bytes_read = 0; + + while(bytes_read < n) { + // If the desired byte range is outside the file, fetch new contents + if(offset < h->buf_offset || offset >= h->buf_offset + (off_t)h->bytes) { + int r = hreader_fill(h, offset); + if(r < 0) + return -1; /* disaster! */ + else if(r == 0) + break; /* end of file */ + } + // Figure out how much we can read this time round + size_t left = h->bytes - (offset - h->buf_offset); + // Truncate the read if we don't want that much + if(left > (n - bytes_read)) + left = n - bytes_read; + memcpy((char *)buffer + bytes_read, + h->buffer + (offset - h->buf_offset), + left); + offset += left; + bytes_read += left; + } + return bytes_read; +} + +static int hreader_fill(struct hreader *h, off_t offset) { + int fd = open(h->path, O_RDONLY); + if(fd < 0) + return -1; + int n = pread(fd, h->buffer, h->bufsize, offset); + close(fd); + if(n < 0) + return -1; + h->buf_offset = offset; + h->bytes = n; + return n; +} + +off_t hreader_seek(struct hreader *h, off_t offset, int whence) { + switch(whence) { + case SEEK_SET: break; + case SEEK_CUR: offset += h->read_offset; break; + case SEEK_END: offset += h->size; break; + default: einval: errno = EINVAL; return -1; + } + if(offset < 0) goto einval; + h->read_offset = offset; + return offset; +} + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/ diff --git a/lib/hreader.h b/lib/hreader.h new file mode 100644 index 0000000..90431c1 --- /dev/null +++ b/lib/hreader.h @@ -0,0 +1,106 @@ +/* + * This file is part of DisOrder + * Copyright (C) 2010 Richard Kettlewell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/** @file lib/hreader.h + * @brief Hands-off reader - read files without keeping them open + */ +#ifndef HREADER_H +#define HREADER_H + +#include + +/** @brief A hands-off reader + * + * Allows files to be read without holding them open. + */ +struct hreader { + char *path; /* file to read */ + off_t size; /* file size */ + off_t read_offset; /* for next hreader_read() */ + off_t buf_offset; /* offset of start of buffer */ + char *buffer; /* input buffer */ + size_t bufsize; /* buffer size */ + size_t bytes; /* size of last read */ +}; + +/** @brief Initialize a hands-off reader + * @param path File to read + * @param h Reader to initialize + * @return 0 on success, -1 on error + */ +int hreader_init(const char *path, struct hreader *h); + +/** @brief Close a hands-off reader + * @param h Reader to close + */ +void hreader_close(struct hreader *h); + +/** @brief Read some bytes + * @param h Reader to read from + * @param buffer Where to store bytes + * @param n Maximum bytes to read + * @return Bytes read, or 0 at EOF, or -1 on error + */ +int hreader_read(struct hreader *h, void *buffer, size_t n); + +/** @brief Read some bytes at a given offset + * @param h Reader to read from + * @param offset Offset to read at + * @param buffer Where to store bytes + * @param n Maximum bytes to read + * @return Bytes read, or 0 at EOF, or -1 on error + */ +int hreader_pread(struct hreader *h, void *buffer, size_t n, off_t offset); + +/** @brief Seek within a file + * @param h Reader to seek + * @param offset Offset + * @param whence SEEK_* + * @return Result offset + */ +off_t hreader_seek(struct hreader *h, off_t offset, int whence); + +/** @brief Return file size + * @param h Reader to find size of + * @return Size in bytes + */ +static inline off_t hreader_size(const struct hreader *h) { + return h->size; +} + +/** @brief Test for end of file + * @param h Reader to test + * @return 1 at eof, 0 otherwise + * + * This tells you whether the next read will return 0 bytes, rather than + * whether the last read reached end of file. So it is slightly different to + * feof(). + */ +static inline int hreader_eof(const struct hreader *h) { + return h->read_offset == h->size; +} + +#endif /* HREADER_H */ + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/ diff --git a/lib/wav.c b/lib/wav.c index 455bb72..fe7ffbb 100644 --- a/lib/wav.c +++ b/lib/wav.c @@ -128,9 +128,8 @@ int wav_init(struct wavfile *f, const char *path) { off_t where; memset(f, 0, sizeof *f); - f->fd = -1; f->data = -1; - if((f->fd = open(path, O_RDONLY)) < 0) goto error_errno; + if(hreader_init(path, f->input)) goto error_errno; /* Read the file header * * offset size meaning @@ -138,7 +137,7 @@ int wav_init(struct wavfile *f, const char *path) { * 04 4 length of rest of file * 08 4 'WAVE' * */ - if((n = pread(f->fd, header, 12, 0)) < 0) goto error_errno; + if((n = hreader_pread(f->input, header, 12, 0)) < 0) goto error_errno; else if(n < 12) goto einval; if(strncmp(header, "RIFF", 4) || strncmp(header + 8, "WAVE", 4)) goto einval; @@ -151,7 +150,7 @@ int wav_init(struct wavfile *f, const char *path) { * 00 4 chunk ID * 04 4 length of rest of chunk */ - if((n = pread(f->fd, header, 8, where)) < 0) goto error_errno; + if((n = hreader_pread(f->input, header, 8, where)) < 0) goto error_errno; else if(n < 8) goto einval; if(!strncmp(header,"fmt ", 4)) { /* This is the format chunk @@ -168,7 +167,8 @@ int wav_init(struct wavfile *f, const char *path) { * 18 ? extra undocumented rubbish */ if(get32(header + 4) < 16) goto einval; - if((n = pread(f->fd, header + 8, 16, where + 8)) < 0) goto error_errno; + if((n = hreader_pread(f->input, header + 8, 16, where + 8)) < 0) + goto error_errno; else if(n < 16) goto einval; f->channels = get16(header + 0x0A); f->rate = get32(header + 0x0C); @@ -197,13 +197,7 @@ error: /** @brief Close a WAV file */ void wav_destroy(struct wavfile *f) { - if(f) { - const int save_errno = errno; - - if(f->fd >= 0) - close(f->fd); - errno = save_errno; - } + hreader_close(f->input); } /** @brief Visit all the data in a WAV file @@ -227,7 +221,7 @@ int wav_data(struct wavfile *f, size_t want = (off_t)sizeof buffer > left ? (size_t)left : sizeof buffer; want -= want % bytes_per_frame; - if((n = pread(f->fd, buffer, want, where)) < 0) return errno; + if((n = hreader_pread(f->input, buffer, want, where)) < 0) return errno; if((size_t)n < want) return EINVAL; if((err = callback(f, buffer, n, u))) return err; where += n; diff --git a/lib/wav.h b/lib/wav.h index d2de912..3c9c477 100644 --- a/lib/wav.h +++ b/lib/wav.h @@ -22,10 +22,12 @@ #ifndef WAV_H #define WAV_H +#include "hreader.h" + /** @brief WAV file access structure */ struct wavfile { - /** @brief File descriptor onto file */ - int fd; + /** @brief File read handle */ + struct hreader input[1]; /** @brief File length */ off_t length; diff --git a/plugins/Makefile.am b/plugins/Makefile.am index 3626991..76d7923 100644 --- a/plugins/Makefile.am +++ b/plugins/Makefile.am @@ -25,7 +25,8 @@ notify_la_LDFLAGS=-module disorder_tracklength_la_SOURCES=tracklength.c tracklength.h \ tracklength-mp3.c tracklength-ogg.c tracklength-wav.c \ -tracklength-flac.c mad.c madshim.h ../lib/wav.h ../lib/wav.c +tracklength-flac.c mad.c madshim.h ../lib/wav.h ../lib/wav.c \ +../lib/hreader.h ../lib/hreader.c disorder_tracklength_la_LDFLAGS=-module disorder_tracklength_la_LIBADD=$(LIBVORBISFILE) $(LIBMAD) $(LIBFLAC) -lm diff --git a/server/Makefile.am b/server/Makefile.am index fc7e1d9..7d59a76 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -25,7 +25,7 @@ AM_CPPFLAGS=-I${top_srcdir}/lib -I../lib disorderd_SOURCES=disorderd.c api.c api-server.c daemonize.c play.c \ server.c server-queue.c queue-ops.c state.c plugin.c \ - schedule.c dbparams.c background.c \ + schedule.c dbparams.c background.c mount.c \ exports.c ../lib/memgc.c disorder-server.h disorderd_LDADD=$(LIBOBJS) ../lib/libdisorder.a \ $(LIBPCRE) $(LIBDB) $(LIBAO) $(LIBGC) $(LIBGCRYPT) $(LIBICONV) \ diff --git a/server/decode.c b/server/decode.c index 11a0592..303fef0 100644 --- a/server/decode.c +++ b/server/decode.c @@ -20,18 +20,12 @@ */ #include "disorder-server.h" +#include "hreader.h" #include #include -/* libFLAC has had an API change and stupidly taken away the old API */ -#if HAVE_FLAC_FILE_DECODER_H -# include -#else -# include -#define FLAC__FileDecoder FLAC__StreamDecoder -#define FLAC__FileDecoderState FLAC__StreamDecoderState -#endif +#include #include "wav.h" #include "speaker-protocol.h" @@ -45,8 +39,7 @@ struct decoder { void (*decode)(void); }; -/** @brief Input file */ -static int inputfd; +static struct hreader input[1]; /** @brief Output file */ static FILE *outputfp; @@ -239,7 +232,9 @@ static enum mad_flow mp3_input(void attribute((unused)) *data, remain = 0; } /* Read new data */ - n = read(inputfd, input_buffer + remain, (sizeof input_buffer) - remain); + n = hreader_read(input, + input_buffer + remain, + (sizeof input_buffer) - remain); if(n < 0) disorder_fatal(errno, "reading from %s", path); /* Compute total number of bytes available */ @@ -267,7 +262,7 @@ static enum mad_flow mp3_error(void attribute((unused)) *data, static void decode_mp3(void) { struct mad_decoder mad[1]; - if((inputfd = open(path, O_RDONLY)) < 0) + if(hreader_init(path, input)) disorder_fatal(errno, "opening %s", path); mad_decoder_init(mad, 0/*data*/, mp3_input, 0/*header*/, 0/*filter*/, mp3_output, mp3_error, 0/*message*/); @@ -276,21 +271,52 @@ static void decode_mp3(void) { mad_decoder_finish(mad); } +static size_t ogg_read_func(void *ptr, size_t size, size_t nmemb, void *datasource) { + struct hreader *h = datasource; + + int n = hreader_read(h, ptr, size * nmemb); + if(n < 0) n = 0; + return n / size; +} + +static int ogg_seek_func(void *datasource, ogg_int64_t offset, int whence) { + struct hreader *h = datasource; + + return hreader_seek(h, offset, whence) < 0 ? -1 : 0; +} + +static int ogg_close_func(void attribute((unused)) *datasource) { + return 0; +} + +static long ogg_tell_func(void *datasource) { + struct hreader *h = datasource; + + return hreader_seek(h, 0, SEEK_CUR); +} + +static const ov_callbacks ogg_callbacks = { + ogg_read_func, + ogg_seek_func, + ogg_close_func, + ogg_tell_func, +}; + /** @brief OGG decoder */ static void decode_ogg(void) { - FILE *fp; + struct hreader ogginput[1]; OggVorbis_File vf[1]; int err; long n; int bitstream; vorbis_info *vi; - if(!(fp = fopen(path, "rb"))) - disorder_fatal(errno, "cannot open %s", path); + hreader_init(path, ogginput); /* There doesn't seem to be any standard function for mapping the error codes * to strings l-( */ - if((err = ov_open(fp, vf, 0/*initial*/, 0/*ibytes*/))) - disorder_fatal(0, "ov_fopen %s: %d", path, err); + if((err = ov_open_callbacks(ogginput, vf, 0/*initial*/, 0/*ibytes*/, + ogg_callbacks))) + disorder_fatal(0, "ov_open_callbacks %s: %d", path, err); if(!(vi = ov_info(vf, 0/*link*/))) disorder_fatal(0, "ov_info %s: failed", path); while((n = ov_read(vf, input_buffer, sizeof input_buffer, 1/*bigendianp*/, @@ -331,13 +357,13 @@ static void decode_wav(void) { * * This is a no-op here. */ -static void flac_metadata(const FLAC__FileDecoder attribute((unused)) *decoder, +static void flac_metadata(const FLAC__StreamDecoder attribute((unused)) *decoder, const FLAC__StreamMetadata attribute((unused)) *metadata, void attribute((unused)) *client_data) { } /** @brief Error callback for FLAC decoder */ -static void flac_error(const FLAC__FileDecoder attribute((unused)) *decoder, +static void flac_error(const FLAC__StreamDecoder attribute((unused)) *decoder, FLAC__StreamDecoderErrorStatus status, void attribute((unused)) *client_data) { disorder_fatal(0, "error decoding %s: %s", path, @@ -346,7 +372,7 @@ static void flac_error(const FLAC__FileDecoder attribute((unused)) *decoder, /** @brief Write callback for FLAC decoder */ static FLAC__StreamDecoderWriteStatus flac_write - (const FLAC__FileDecoder attribute((unused)) *decoder, + (const FLAC__StreamDecoder attribute((unused)) *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void attribute((unused)) *client_data) { @@ -371,39 +397,85 @@ static FLAC__StreamDecoderWriteStatus flac_write return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; } +static FLAC__StreamDecoderReadStatus flac_read(const FLAC__StreamDecoder attribute((unused)) *decoder, + FLAC__byte buffer[], + size_t *bytes, + void *client_data) { + struct hreader *flacinput = client_data; + int n = hreader_read(flacinput, buffer, *bytes); + if(n == 0) { + *bytes = 0; + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + } + if(n < 0) { + *bytes = 0; + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + } + *bytes = n; + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; +} + +static FLAC__StreamDecoderSeekStatus flac_seek(const FLAC__StreamDecoder attribute((unused)) *decoder, + FLAC__uint64 absolute_byte_offset, + void *client_data) { + struct hreader *flacinput = client_data; + if(hreader_seek(flacinput, absolute_byte_offset, SEEK_SET) < 0) + return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; + else + return FLAC__STREAM_DECODER_SEEK_STATUS_OK; +} + +static FLAC__StreamDecoderTellStatus flac_tell(const FLAC__StreamDecoder attribute((unused)) *decoder, + FLAC__uint64 *absolute_byte_offset, + void *client_data) { + struct hreader *flacinput = client_data; + off_t offset = hreader_seek(flacinput, 0, SEEK_CUR); + if(offset < 0) + return FLAC__STREAM_DECODER_TELL_STATUS_ERROR; + *absolute_byte_offset = offset; + return FLAC__STREAM_DECODER_TELL_STATUS_OK; +} + +static FLAC__StreamDecoderLengthStatus flac_length(const FLAC__StreamDecoder attribute((unused)) *decoder, + FLAC__uint64 *stream_length, + void *client_data) { + struct hreader *flacinput = client_data; + *stream_length = hreader_size(flacinput); + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; +} + +static FLAC__bool flac_eof(const FLAC__StreamDecoder attribute((unused)) *decoder, + void *client_data) { + struct hreader *flacinput = client_data; + return hreader_eof(flacinput); +} /** @brief FLAC file decoder */ static void decode_flac(void) { -#if HAVE_FLAC_FILE_DECODER_H - FLAC__FileDecoder *fd = 0; - FLAC__FileDecoderState fs; - - if(!(fd = FLAC__file_decoder_new())) - disorder_fatal(0, "FLAC__file_decoder_new failed"); - if(!(FLAC__file_decoder_set_filename(fd, path))) - disorder_fatal(0, "FLAC__file_set_filename failed"); - FLAC__file_decoder_set_metadata_callback(fd, flac_metadata); - FLAC__file_decoder_set_error_callback(fd, flac_error); - FLAC__file_decoder_set_write_callback(fd, flac_write); - if((fs = FLAC__file_decoder_init(fd))) - disorder_fatal(0, "FLAC__file_decoder_init: %s", FLAC__FileDecoderStateString[fs]); - FLAC__file_decoder_process_until_end_of_file(fd); -#else FLAC__StreamDecoder *sd = FLAC__stream_decoder_new(); FLAC__StreamDecoderInitStatus is; + struct hreader flacinput[1]; if (!sd) disorder_fatal(0, "FLAC__stream_decoder_new failed"); - - if((is = FLAC__stream_decoder_init_file(sd, path, flac_write, flac_metadata, - flac_error, 0))) - disorder_fatal(0, "FLAC__stream_decoder_init_file %s: %s", + if(hreader_init(path, flacinput)) + disorder_fatal(errno, "error opening %s", path); + + if((is = FLAC__stream_decoder_init_stream(sd, + flac_read, + flac_seek, + flac_tell, + flac_length, + flac_eof, + flac_write, flac_metadata, + flac_error, + flacinput))) + disorder_fatal(0, "FLAC__stream_decoder_init_stream %s: %s", path, FLAC__StreamDecoderInitStatusString[is]); FLAC__stream_decoder_process_until_end_of_stream(sd); FLAC__stream_decoder_finish(sd); FLAC__stream_decoder_delete(sd); -#endif } /** @brief Lookup table of decoders */ diff --git a/server/disorder-server.h b/server/disorder-server.h index e8f4dab..7cd1cf6 100644 --- a/server/disorder-server.h +++ b/server/disorder-server.h @@ -1,6 +1,6 @@ /* * This file is part of DisOrder - * Copyright (C) 2008, 2009 Richard Kettlewell + * Copyright (C) 2008-2010 Richard Kettlewell * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -372,6 +372,18 @@ int play_background(ev_source *ev, #define START_HARDFAIL 1 /**< @brief Track is broken. */ #define START_SOFTFAIL 2 /**< @brief Track OK, system (temporarily?) broken */ +void periodic_mount_check(ev_source *ev_); + +#ifndef MOUNT_CHECK_INTERVAL +# ifdef PATH_MTAB +// statting a file is really cheap so check once a second +# define MOUNT_CHECK_INTERVAL 1 +# else +// hashing getfsstat() output could be more expensive so be less aggressive +# define MOUNT_CHECK_INTERVAL 5 +# endif +#endif + #endif /* DISORDER_SERVER_H */ /* diff --git a/server/disorderd.c b/server/disorderd.c index 14b459f..2000a2f 100644 --- a/server/disorderd.c +++ b/server/disorderd.c @@ -302,6 +302,8 @@ int main(int argc, char **argv) { create_periodic(ev, periodic_play_check, 1, 0); /* Try adding a random track immediately and once every two seconds */ create_periodic(ev, periodic_add_random, 2, 1); + /* Issue a rescan when devices are mounted or unmouted */ + create_periodic(ev, periodic_mount_check, MOUNT_CHECK_INTERVAL, 1); /* enter the event loop */ n = ev_run(ev); /* if we exit the event loop, something must have gone wrong */ diff --git a/server/mount.c b/server/mount.c new file mode 100644 index 0000000..8b1c602 --- /dev/null +++ b/server/mount.c @@ -0,0 +1,99 @@ +/* + * This file is part of DisOrder. + * Copyright (C) 2010 Richard Kettlewell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/** @file server/mount.c + * @brief Periodically check for devices being mounted and unmounted + */ +#include "disorder-server.h" +#if HAVE_GETFSSTAT +# include +# include +# include +#endif + +#if HAVE_GETFSSTAT +static int compare_fsstat(const void *av, const void *bv) { + const struct statfs *a = av, *b = bv; + int c; + + c = memcmp(&a->f_fsid, &b->f_fsid, sizeof a->f_fsid); + if(c) + return c; + c = strcmp(a->f_mntonname, b->f_mntonname); + if(c) + return c; + return 0; +} +#endif + +void periodic_mount_check(ev_source *ev_) { + if(!config->mount_rescan) + return; +#if HAVE_GETFSSTAT + /* On OS X, we keep track of the hash of the kernel's mounted + * filesystem list */ + static int first = 1; + static unsigned char last[20]; + unsigned char *current; + int nfilesystems, space; + struct statfs *buf; + gcrypt_hash_handle h; + gcry_error_t e; + + space = getfsstat(NULL, 0, MNT_NOWAIT); + buf = xcalloc(space, sizeof *buf); + nfilesystems = getfsstat(buf, space * sizeof *buf, MNT_NOWAIT); + if(nfilesystems > space) + // The array grew between check and use! We just give up and try later. + return; + // Put into order so we get a bit of consistency + qsort(buf, nfilesystems, sizeof *buf, compare_fsstat); + if((e = gcry_md_open(&h, GCRY_MD_SHA1, 0))) { + disorder_error(0, "gcry_md_open: %s", gcry_strerror(e)); + return; + } + for(int n = 0; n < nfilesystems; ++n) { + gcry_md_write(h, &buf[n].f_fsid, sizeof buf[n].f_fsid); + gcry_md_write(h, buf[n].f_mntonname, 1 + strlen(buf[n].f_mntonname)); + } + current = gcry_md_read(h, GCRY_MD_SHA1); + if(!first && memcmp(current, last, sizeof last)) + trackdb_rescan(ev_, 1/*check*/, 0, 0); + memcpy(last, current, sizeof last); + first = 0; + gcry_md_close(h); +#elif defined PATH_MTAB + /* On Linux we keep track of the modification time of /etc/mtab */ + static time_t last_mount; + struct stat sb; + + if(stat(PATH_MTAB, &sb) >= 0) { + if(last_mount != 0 && last_mount != sb.st_mtime) + trackdb_rescan(ev_, 1/*check*/, 0, 0); + last_mount = sb.st_mtime; + } +#endif +} + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/