chiark / gitweb /
Don't keep audio files open except when they are actually being read
authorRichard Kettlewell <rjk@greenend.org.uk>
Wed, 9 Jun 2010 19:23:09 +0000 (20:23 +0100)
committerRichard Kettlewell <rjk@greenend.org.uk>
Wed, 9 Jun 2010 19:23:09 +0000 (20:23 +0100)
right that moment ("hands-off reading").

Automatically rescan when anything is mounted or unmounted.

Both are intended to improve compatibility with removable storage.

16 files changed:
CHANGES.html
configure.ac
doc/disorder_config.5.in
lib/Makefile.am
lib/configuration.c
lib/configuration.h
lib/hreader.c [new file with mode: 0644]
lib/hreader.h [new file with mode: 0644]
lib/wav.c
lib/wav.h
plugins/Makefile.am
server/Makefile.am
server/decode.c
server/disorder-server.h
server/disorderd.c
server/mount.c [new file with mode: 0644]

index ed1e9b4..53c9fa0 100644 (file)
@@ -68,6 +68,26 @@ span.command {
 <p>This file documents recent user-visible changes to <a
  href="http://www.greenend.org.uk/rjk/disorder/">DisOrder</a>.</p>
 
+<h2>Changes up to version 5.1</h2>
+
+<div class=section>
+
+  <h3>Removable Device Support</h3>
+
+  <div class=section>
+
+    <p>The server will now automatically initiate a rescan when a filesystem is
+    mounted or unmounted.  (Use the <tt>mount_rescan</tt> option if you want to
+    suppress this behavior.)</p>
+
+    <p>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.</p>
+
+  </div>
+
+</div>
+
 <h2>Changes up to version 5.0</h2>
 
   <div class=section>
index b0290c9..69cdea3 100644 (file)
@@ -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 <netinet/in.h>])
 
+# 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
   # <db.h> had better be version 3 or later
index a5b55ba..0c8f7c8 100644 (file)
@@ -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.
index be12f1c..ff0eb8b 100644 (file)
@@ -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                                     \
index 45f2329..0b173d4 100644 (file)
@@ -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);
index 617d929..22e107d 100644 (file)
@@ -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 (file)
index 0000000..bdf3c07
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+/** @file lib/hreader.c
+ * @brief Hands-off reader - read files without keeping them open
+ */
+#include <config.h>
+#include "hreader.h"
+#include "mem.h"
+#include <string.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+
+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 (file)
index 0000000..90431c1
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+/** @file lib/hreader.h
+ * @brief Hands-off reader - read files without keeping them open
+ */
+#ifndef HREADER_H
+#define HREADER_H
+
+#include <unistd.h>
+
+/** @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:
+*/
index 455bb72..fe7ffbb 100644 (file)
--- 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;
index d2de912..3c9c477 100644 (file)
--- a/lib/wav.h
+++ b/lib/wav.h
 #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;
index 3626991..76d7923 100644 (file)
@@ -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
 
index fc7e1d9..7d59a76 100644 (file)
@@ -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) \
index 11a0592..303fef0 100644 (file)
  */
 
 #include "disorder-server.h"
+#include "hreader.h"
 
 #include <mad.h>
 #include <vorbis/vorbisfile.h>
 
-/* libFLAC has had an API change and stupidly taken away the old API */
-#if HAVE_FLAC_FILE_DECODER_H
-# include <FLAC/file_decoder.h>
-#else
-# include <FLAC/stream_decoder.h>
-#define FLAC__FileDecoder FLAC__StreamDecoder
-#define FLAC__FileDecoderState FLAC__StreamDecoderState
-#endif
+#include <FLAC/stream_decoder.h>
 
 #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 */
index e8f4dab..7cd1cf6 100644 (file)
@@ -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 */
 
 /*
index 14b459f..2000a2f 100644 (file)
@@ -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 (file)
index 0000000..8b1c602
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+/** @file server/mount.c
+ * @brief Periodically check for devices being mounted and unmounted
+ */
+#include "disorder-server.h"
+#if HAVE_GETFSSTAT
+# include <sys/param.h>
+# include <sys/ucred.h>
+# include <sys/mount.h>
+#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:
+*/