server: Convert non-afs commands to lopsub.
[paraslash.git] / flac_afh.c
index 9efc4e0..385d4f0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2011 Andre Noll <maan@tuebingen.mpg.de>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
 #include "error.h"
 #include "afh.h"
 #include "string.h"
+#include "fd.h"
 
 struct private_flac_afh_data {
-       char *map;
+       const char *map;
        size_t map_bytes;
        size_t fpos;
        struct afh_info *afhi;
@@ -80,7 +81,7 @@ static FLAC__int64 meta_tell_cb(FLAC__IOHandle handle)
 static int meta_eof_cb(FLAC__IOHandle handle)
 {
        struct private_flac_afh_data *pfad = handle;
-       return pfad->fpos == pfad->map_bytes - 1;
+       return pfad->fpos == pfad->map_bytes;
 }
 
 static int meta_close_cb(FLAC__IOHandle __a_unused handle)
@@ -88,6 +89,15 @@ static int meta_close_cb(FLAC__IOHandle __a_unused handle)
        return 0;
 }
 
+static const FLAC__IOCallbacks meta_callbacks = {
+       .read = meta_read_cb,
+       .write = NULL,
+       .seek = meta_seek_cb,
+       .tell = meta_tell_cb,
+       .eof = meta_eof_cb,
+       .close = meta_close_cb
+};
+
 static void free_tags(struct taginfo *tags)
 {
        freep(&tags->artist);
@@ -131,22 +141,95 @@ static void flac_read_vorbis_comments(FLAC__StreamMetadata_VorbisComment *vc,
        }
 }
 
-static int flac_read_meta(struct private_flac_afh_data *pfad)
+/*
+ * FLAC__metadata_object_vorbiscomment_replace_comment() is buggy in some
+ * libFLAC versions (see commit e95399c1 in the flac git repository). Hence we
+ * use delete and add as a workaround.
+ */
+static int flac_replace_vorbis_comment(FLAC__StreamMetadata *b,
+               const char *tag, const char* val)
 {
+       FLAC__bool ok;
+       FLAC__StreamMetadata_VorbisComment_Entry entry;
        int ret;
-       FLAC__IOCallbacks meta_callbacks = {
-               .read = meta_read_cb,
-               .write = NULL,
-               .seek = meta_seek_cb,
-               .tell = meta_tell_cb,
-               .eof = meta_eof_cb,
-               .close = meta_close_cb
-       };
+
+       PARA_INFO_LOG("replacing %s\n", tag);
+       ret = FLAC__metadata_object_vorbiscomment_remove_entries_matching(
+               b, tag);
+       if (ret < 0)
+               return -E_FLAC_REPLACE_COMMENT;
+       ok = FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(
+               &entry, tag, val? val : "");
+       if (!ok)
+               return -E_FLAC_REPLACE_COMMENT;
+       ok = FLAC__metadata_object_vorbiscomment_append_comment(b, entry,
+               false /* no copy */);
+       if (!ok) {
+               free(entry.entry);
+               return -E_FLAC_REPLACE_COMMENT;
+       }
+       return 1;
+}
+
+static int flac_replace_vorbis_comments(FLAC__Metadata_Chain *chain,
+               FLAC__StreamMetadata *b, struct taginfo *tags)
+{
+       FLAC__bool ok;
+       int ret;
+
+       ret = flac_replace_vorbis_comment(b, "artist", tags->artist);
+       if (ret < 0)
+               return ret;
+       ret = flac_replace_vorbis_comment(b, "title", tags->title);
+       if (ret < 0)
+               return ret;
+       ret = flac_replace_vorbis_comment(b, "album", tags->album);
+       if (ret < 0)
+               return ret;
+       ret = flac_replace_vorbis_comment(b, "year", tags->year);
+       if (ret < 0)
+               return ret;
+       ret = flac_replace_vorbis_comment(b, "comment", tags->comment);
+       if (ret < 0)
+               return ret;
+       /*
+        * Even if padding is disabled, libflac will try to modify the original
+        * file inplace if the metadata size has not changed. This won't work
+        * here though, because the original file is mapped read-only. Since
+        * there is no option to force the use of a temp file we work around
+        * this shortcoming by adding a dummy entry which increases the size of
+        * the meta data. If the entry already exists, we simply remove it.
+        */
+       ok = FLAC__metadata_chain_check_if_tempfile_needed(chain,
+               false /* no padding */);
+       if (!ok) {
+               PARA_INFO_LOG("adding/removing dummy comment\n");
+               ret = FLAC__metadata_object_vorbiscomment_remove_entries_matching(
+                       b, "comment2");
+               if (ret < 0)
+                       return -E_FLAC_REPLACE_COMMENT;
+               if (ret == 0) { /* nothing was removed */
+                       ret = flac_replace_vorbis_comment(b, "comment2",
+                               "avoid inplace write");
+                       if (ret < 0)
+                               return ret;
+               }
+               assert(FLAC__metadata_chain_check_if_tempfile_needed(chain,
+                       false /* no padding */));
+       }
+       return 1;
+}
+
+static int flac_init_meta(struct private_flac_afh_data *pfad,
+               FLAC__Metadata_Chain **chainp, FLAC__Metadata_Iterator **iterp)
+{
+       int ret;
+       FLAC__bool ok;
        FLAC__Metadata_Chain *chain;
        FLAC__Metadata_Iterator *iter;
-       FLAC__StreamMetadata_StreamInfo *info = NULL;
-       FLAC__bool ok;
 
+       *chainp = NULL;
+       *iterp = NULL;
        chain = FLAC__metadata_chain_new();
        if (!chain)
                return -E_FLAC_CHAIN_ALLOC;
@@ -160,6 +243,25 @@ static int flac_read_meta(struct private_flac_afh_data *pfad)
        if (!iter)
                goto free_chain;
        FLAC__metadata_iterator_init(iter, chain);
+       *iterp = iter;
+       *chainp = chain;
+       return 1;
+free_chain:
+       FLAC__metadata_chain_delete(chain);
+       return ret;
+}
+
+static int flac_read_meta(struct private_flac_afh_data *pfad)
+{
+       int ret;
+       FLAC__Metadata_Chain *chain;
+       FLAC__Metadata_Iterator *iter;
+       FLAC__StreamMetadata_StreamInfo *info = NULL;
+       FLAC__bool ok;
+
+       ret = flac_init_meta(pfad, &chain, &iter);
+       if (ret < 0)
+               return ret;
        for (;;) {
                FLAC__StreamMetadata *b;
                b = FLAC__metadata_iterator_get_block(iter);
@@ -184,7 +286,6 @@ static int flac_read_meta(struct private_flac_afh_data *pfad)
        ret = info? 0: -E_FLAC_STREAMINFO;
 free_iter:
        FLAC__metadata_iterator_delete(iter);
-free_chain:
        FLAC__metadata_chain_delete(chain);
        if (ret < 0)
                free_tags(&pfad->afhi->tags);
@@ -215,7 +316,7 @@ static FLAC__StreamDecoderTellStatus tell_cb(
        return FLAC__STREAM_DECODER_TELL_STATUS_OK;
 }
 
-/* libflac insits on this callback being present. */
+/* libflac insists on this callback being present. */
 static FLAC__StreamDecoderWriteStatus write_cb(
                __a_unused const FLAC__StreamDecoder *decoder,
                __a_unused const FLAC__Frame *frame,
@@ -332,7 +433,89 @@ static int flac_get_file_info(char *map, size_t map_bytes, __a_unused int fd,
        return 1;
 }
 
-static const char* flac_suffixes[] = {"flac", NULL};
+static size_t temp_write_cb(const void *ptr, size_t size, size_t nmemb,
+       FLAC__IOHandle handle)
+{
+       int ret, fd = *(int *)handle;
+       size_t n = size * nmemb; /* FIXME: possible overflow */
+
+       ret = write_all(fd, ptr, n);
+
+       /*
+        * libflac expects POSIX semantics: If an error occurs, or the end of
+        * the file is reached, the return value is a short item count or zero.
+        */
+       if (ret < 0) {
+               PARA_ERROR_LOG("%s\n", para_strerror(-ret));
+               return 0;
+       }
+       return nmemb;
+}
+
+/* only the write callback needs to be supplied for writing the temp file. */
+static const FLAC__IOCallbacks temp_callbacks = {
+       .write = temp_write_cb,
+};
+
+static int flac_write_chain(FLAC__Metadata_Chain *chain,
+               struct private_flac_afh_data *pfad, int fd)
+{
+       FLAC__bool ok;
+
+       ok = FLAC__metadata_chain_write_with_callbacks_and_tempfile(chain,
+               false /* no padding*/, pfad,
+               meta_callbacks, &fd, temp_callbacks);
+       if (!ok) {
+               FLAC__Metadata_ChainStatus st;
+               st = FLAC__metadata_chain_status(chain);
+               PARA_ERROR_LOG("chain status: %u\n", st);
+               if (st == FLAC__METADATA_CHAIN_STATUS_READ_ERROR)
+                       PARA_ERROR_LOG("read error\n");
+               return -E_FLAC_WRITE_CHAIN;
+       }
+       return 1;
+}
+
+static int flac_rewrite_tags(const char *map, size_t map_bytes,
+               struct taginfo *tags, int fd, __a_unused const char *filename)
+{
+       int ret;
+       FLAC__Metadata_Chain *chain;
+       FLAC__Metadata_Iterator *iter;
+       FLAC__StreamMetadata *b = NULL;
+       FLAC__bool ok;
+       struct private_flac_afh_data *pfad = para_calloc(sizeof(*pfad));
+
+       pfad->map = map;
+       pfad->map_bytes = map_bytes;
+       pfad->fpos = 0;
+
+       ret = flac_init_meta(pfad, &chain, &iter);
+       if (ret < 0)
+               goto free_pfad;
+       for (ok = true; ok; ok = FLAC__metadata_iterator_next(iter)) {
+               b = FLAC__metadata_iterator_get_block(iter);
+               assert(b);
+               if (b->type == FLAC__METADATA_TYPE_VORBIS_COMMENT)
+                       break;
+               b = NULL;
+       }
+       ret = -E_FLAC_REPLACE_COMMENT;
+       if (!b)
+               goto out;
+       ret = flac_replace_vorbis_comments(chain, b, tags);
+       if (ret < 0)
+               goto out;
+       ret = flac_write_chain(chain, pfad, fd);
+out:
+       FLAC__metadata_iterator_delete(iter);
+       FLAC__metadata_chain_delete(chain);
+free_pfad:
+       free(pfad);
+       return ret;
+}
+
+static const char * const flac_suffixes[] = {"flac", NULL};
 
 /**
  * The init function of the flac audio format handler.
@@ -343,4 +526,5 @@ void flac_afh_init(struct audio_format_handler *afh)
 {
        afh->get_file_info = flac_get_file_info,
        afh->suffixes = flac_suffixes;
+       afh->rewrite_tags = flac_rewrite_tags;
 }