/* id3read.c
   interface for the id3tag library
   Copyright (C) 2001 Linus Walleij

This file is part of the GNOMAD package.

GNOMAD 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 2, or (at your option)
any later version.

You should have received a copy of the GNU General Public License
along with GNOMAD; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA. 

*/

#define ID3V2_MAX_STRING_LEN 4096
#include <id3tag.h>
#include <libmtp.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <glib.h>


/* Converts a figure representing a number of seconds to
 *  * a string in mm:ss notation */
gchar *seconds_to_mmss(guint seconds)
{
    gchar tmp2[10];
    gchar tmp[10];
    guint secfrac = seconds % 60;
    guint minfrac = seconds / 60;

    if (seconds == 0)
        return g_strdup("0:00");

    snprintf(tmp2, 10, "0%u", secfrac);
    while (strlen(tmp2)>2) {
        tmp2[0]=tmp2[1];
        tmp2[1]=tmp2[2];
        tmp2[2]='\0';
    }
    snprintf(tmp, 10, "%lu:%s", minfrac, tmp2);
    return g_strdup(tmp);
}

/* Converts a string in mm:ss notation to a figure
 *  * representing seconds */
guint mmss_to_seconds(gchar *mmss)
{
    gchar **tmp;
    guint seconds = 0;

    if (!mmss)
        return seconds;

    tmp = g_strsplit(mmss, ":", 0);
    if (tmp[1] != NULL) {
        seconds = 60 * strtoul(tmp[0],NULL,10);
        seconds += strtoul(tmp[1],NULL,10);
    }
    if (tmp != NULL)
        g_strfreev(tmp);
    return seconds;
}

/* Eventually make charset selectable */

static id3_utf8_t *
charset_to_utf8 (const id3_latin1_t * str)
{
    id3_utf8_t *tmp;

    tmp =
        (id3_utf8_t *) g_convert ((gchar *) str, -1, "UTF-8", "ISO-8859-1",
                                  NULL, NULL, NULL);
    return (id3_utf8_t *) tmp;
}

static id3_latin1_t *
charset_from_utf8 (const id3_utf8_t * str)
{
    id3_latin1_t *tmp;

    tmp =
        (id3_latin1_t *) g_convert ((gchar *) str, -1, "ISO-8859-1", "UTF-8",
                                    NULL, NULL, NULL);
    return (id3_latin1_t *) tmp;
}

/**
 * This function checks if a given file has an ID3 tag 
 * (either V1 or V2, either header and footer) or not.
 *
 * @param path a path to the file to check
 * @return true if the file has an ID3 tag, false otherwise
 */
static gboolean
hasid3tag (gchar * path)
{
    gint fd;
    guchar tag[3];
    gboolean result = FALSE;

#if !GLIB_CHECK_VERSION(2,6,0)
    fd = (gint) open (path, O_RDONLY, 0);
#else
    fd = (gint) g_open (path, O_RDONLY, 0);
#endif
    if (fd >= 0) {
        /* First check for a header */
        if (read (fd, tag, 3) == 3) {
            g_debug ("Checking for header ID3v2 tag...\n");
            if (tag[0] == 'I' && tag[1] == 'D' && tag[2] == '3') {
                result = TRUE;
            } else {
                /* Seek to end, check for footer */
                if (lseek (fd, -10, SEEK_END) > 0) {
                    if (read (fd, tag, 3) == 3) {
                        g_debug ("Checking for footer ID3v2 tag...\n");
                        if (tag[0] == '3' && tag[1] == 'D' && tag[2] == 'I') {
                            result = TRUE;
                        } else {
                            /* Seek to end, and check for ID3v1 tag */
                            if (lseek (fd, -128, SEEK_END) > 0) {
                                if (read (fd, tag, 3) == 3) {
                                    g_debug ("Checking for ID3v1 tag...\n");
                                    if (tag[0] == 'T' &&
                                        tag[1] == 'A' && tag[2] == 'G') {
                                        result = TRUE;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        close (fd);
    }
    g_debug (result ? "%s has ID3 tag.\n" : "%s has no ID3 tag.\n", path);
    return result;
}

/*
 * This function checks if a RIFF header exists at the beginning of 
 * the *data buffer. In that case its size is returned. The size
 * of the *data buffer must be supplied.
 */
static guint
riff_header_size (gchar * data, guint bufsize)
{
    guint retsize = 0;

    // Too small to be a RIFF header
    if (bufsize < 20) {
        return 0;
    }
    // Remove any RIFF header too (EVIL GARBAGE!)
    if (data[0] == 'R' &&
        data[1] == 'I' &&
        data[2] == 'F' &&
        data[3] == 'F' &&
        data[8] == 'W' &&
        data[9] == 'A' && data[10] == 'V' && data[11] == 'E') {
        gchar chunk_type[5];
        guint chunk_size;
        guint p = 12;           // Parse point

        chunk_type[4] = '\0';
        while (p + 8 < bufsize) {
            memcpy (chunk_type, &data[p], 4);
            chunk_size =
                (data[p + 7] << 24) + (data[p + 6] << 16) +
                (data[p + 5] << 8) + data[p + 4];
            // Even 16 bit boundary
            if (chunk_size % 2 != 0) {
                chunk_size++;
            }
            // Pointer behind chunk metadata
            p += 8;
            // g_debug("Chunk \'%s\' size %d bytes\n", chunk_type, chunk_size);
            if (!strcmp (chunk_type, "data")) {
                // The 'data' chunk is the vital one. We need not scan further.
                retsize = p;
                g_debug ("Found a RIFF tag of size %d bytes (%x)\n", retsize,
                         retsize);
                break;
            }
            p += chunk_size;
        }

    }
    return retsize;
}

/*****************************************************************************
 * ID3TAG interface
 * Many parts of this code copied in from gtkpod or plagiated, I confess.
 * Acknowledgements to Jorg Schuler for that...
 *****************************************************************************/

/*
 * Returns a text frame, or NULL
 */

static gchar *
getFrameText (struct id3_tag *tag, char *frame_name)
{
    const id3_ucs4_t *string;
    struct id3_frame *frame;
    union id3_field *field;
    gchar *utf8 = NULL;
    enum id3_field_textencoding encoding = ID3_FIELD_TEXTENCODING_ISO_8859_1;

    frame = id3_tag_findframe (tag, frame_name, 0);
    if (!frame)
        return NULL;

    /* Find the encoding used for the field */
    field = id3_frame_field (frame, 0);
    //printf ("field: %p\n", field);
    if (field && (id3_field_type (field) == ID3_FIELD_TYPE_TEXTENCODING)) {
        encoding = field->number.value;
        //printf ("encoding: %d\n", encoding);
    }

    if (frame_name == ID3_FRAME_COMMENT)
        field = id3_frame_field (frame, 3);
    else
        field = id3_frame_field (frame, 1);

    //printf ("field: %p\n", field);

    if (!field)
        return NULL;

    if (frame_name == ID3_FRAME_COMMENT)
        string = id3_field_getfullstring (field);
    else
        string = id3_field_getstrings (field, 0);

    // g_debug("string: %s\n", string);

    if (!string)
        return NULL;

    if (frame_name == ID3_FRAME_GENRE)
        string = id3_genre_name (string);

    if (encoding == ID3_FIELD_TEXTENCODING_ISO_8859_1) {
        /* ISO_8859_1 is just a "marker" -- most people just drop
           whatever coding system they are using into it, so we use
           charset_to_utf8() to convert to utf8 */
        id3_latin1_t *raw = id3_ucs4_latin1duplicate (string);
        utf8 = (gchar *) charset_to_utf8 (raw);
        g_free (raw);
    } else {
        /* Standard unicode is being used -- we won't have to worry
           about charsets then. */
        // g_debug("This frame is a Unicode frame!\n");
        utf8 = (gchar *) id3_ucs4_utf8duplicate (string);
    }
    // g_debug("Found tag: %s, value: %s\n", frame_name, utf8);
    return utf8;
}


/*
 * Set the specified frame.
 * Create it, if it doesn't exists, else change it.
 *
 * If value is NULL or the empty string "", the frame will be deleted.
 * Please note that a copy of value is made, so you can safely free()
 * id and value as soon as this function returns.
 */

static void
setFrameText (struct id3_tag *tag,
              const char *frame_name,
              const char *data,
              enum id3_field_textencoding encoding, gboolean override)
{
    int res;
    struct id3_frame *frame;
    union id3_field *field;
    id3_ucs4_t *ucs4;

    if (data == NULL)
        return;

    g_debug ("Updating id3 frame (enc: %d): %s: ", encoding, frame_name,
             data);
    g_debug (" %s\n", data);

    /*
     * An empty string removes the frame altogether.
     */
    if ((strlen (data) == 0) && (!override))
        return;
    if (strlen (data) == 0) {
        // g_debug("removing ID3 frame: %s\n", frame_name);
        while ((frame = id3_tag_findframe (tag, frame_name, 0)))
            id3_tag_detachframe (tag, frame);
        return;
    }

    frame = id3_tag_findframe (tag, frame_name, 0);
    if (!frame) {
        // g_debug("new frame: %s!\n", frame_name);
        frame = id3_frame_new (frame_name);
        id3_tag_attachframe (tag, frame);
    } else if (!override) {
        /* Do not overwrite old tags then */
        return;
    } else {
        // Needed for unicode?
        id3_tag_detachframe (tag, frame);
        frame = id3_frame_new (frame_name);
        id3_tag_attachframe (tag, frame);
    }

    /* Set the encoding - always field 0 */
    field = id3_frame_field (frame, 0);
    id3_field_settextencoding (field, encoding);

    /* Modify the default text field type */
    if (frame_name == ID3_FRAME_COMMENT) {
        /* Get the latin-1 string list, convert it to a Unicode list */
        field = id3_frame_field (frame, 3);
        field->type = ID3_FIELD_TYPE_STRINGFULL;
    } else {
        /* Get the latin-1 string, convert it to a Unicode string */
        field = id3_frame_field (frame, 1);
        field->type = ID3_FIELD_TYPE_STRINGLIST;
    }

    if (frame_name == ID3_FRAME_GENRE) {
        id3_ucs4_t *tmp_ucs4 = id3_utf8_ucs4duplicate ((id3_utf8_t *) data);
        int index = id3_genre_number (tmp_ucs4);

        if (index != -1) {
            /* valid genre -- simply store the genre number */
            gchar *tmp = g_strdup_printf ("%d", index);
            ucs4 = id3_latin1_ucs4duplicate ((id3_latin1_t *) tmp);
            g_free (tmp);
        } else {
            /* oups -- not a valid genre -- save the entire genre string */
            if (encoding == ID3_FIELD_TEXTENCODING_ISO_8859_1) {
                /* we read 'ISO_8859_1' to stand for 'any locale
                   charset' -- most programs seem to work that way */
                id3_latin1_t *raw = charset_from_utf8 ((id3_utf8_t *) data);
                ucs4 = id3_latin1_ucs4duplicate (raw);
                g_free (raw);
            } else {
                /* Yeah! We use unicode encoding and won't have to
                   worry about charsets */
                ucs4 = tmp_ucs4;
                tmp_ucs4 = NULL;
            }
        }
        // Perhaps not good that this will be NULL sometimes...
        g_free (tmp_ucs4);
    } else {
        if (encoding == ID3_FIELD_TEXTENCODING_ISO_8859_1) {
            /* we read 'ISO_8859_1' to stand for 'any locale charset'
               -- most programs seem to work that way */
            id3_latin1_t *raw = charset_from_utf8 ((id3_utf8_t *) data);
            ucs4 = id3_latin1_ucs4duplicate (raw);
            g_free (raw);
        } else {
            /* Yeah! We use unicode encoding and won't have to
               worry about charsets */
            ucs4 = id3_utf8_ucs4duplicate ((id3_utf8_t *) data);
        }
    }

    g_debug ("UCS 4 copy of tag %s:\n", frame_name);

    if (frame_name == ID3_FRAME_COMMENT) {
        res = id3_field_setfullstring (field, ucs4);
    } else {
        res = id3_field_setstrings (field, 1, &ucs4);
    }

    g_free (ucs4);

    if (res != 0)
        g_debug ("Error setting ID3 field: %s\n", frame_name);
}


static enum id3_field_textencoding
get_encoding_of_field (struct id3_tag *tag, const char *frame_name)
{
    struct id3_frame *frame;
    enum id3_field_textencoding encoding = -1;

    frame = id3_tag_findframe (tag, frame_name, 0);
    if (frame) {
        union id3_field *field = id3_frame_field (frame, 0);
        if (field && (id3_field_type (field) == ID3_FIELD_TYPE_TEXTENCODING))
            encoding = field->number.value;
    }
    return encoding;
}

/* Find out which encoding is being used. If in doubt, return
 * latin1. This code assumes that the same encoding is used in all
 * fields.  */
static enum id3_field_textencoding
get_encoding_of_tag (struct id3_tag *tag)
{
    enum id3_field_textencoding enc;

    enc = get_encoding_of_field (tag, ID3_FRAME_TITLE);
    if (enc != -1)
        return enc;
    enc = get_encoding_of_field (tag, ID3_FRAME_ARTIST);
    if (enc != -1)
        return enc;
    enc = get_encoding_of_field (tag, ID3_FRAME_ALBUM);
    if (enc != -1)
        return enc;
    enc = get_encoding_of_field (tag, "TCOM");
    if (enc != -1)
        return enc;
    enc = get_encoding_of_field (tag, ID3_FRAME_COMMENT);
    if (enc != -1)
        return enc;
    enc = get_encoding_of_field (tag, ID3_FRAME_YEAR);
    if (enc != -1)
        return enc;
    return ID3_FIELD_TEXTENCODING_ISO_8859_1;
}

/*****************************************************************************
 * FUNCTIONS FOR SETTING ID3v2 FIELDS
 *****************************************************************************/

static void
setArtist (struct id3_tag *tag, gchar * artist,
           enum id3_field_textencoding encoding, gboolean override)
{
    setFrameText (tag, "TPE1", artist, encoding, override);
}


static void
setTitle (struct id3_tag *tag, gchar * title,
          enum id3_field_textencoding encoding, gboolean override)
{
    setFrameText (tag, "TIT2", title, encoding, override);
}

static void
setAlbum (struct id3_tag *tag, gchar * album,
          enum id3_field_textencoding encoding, gboolean override)
{
    setFrameText (tag, "TALB", album, encoding, override);
}

static void
setYear (struct id3_tag *tag, gchar * year,
         enum id3_field_textencoding encoding, gboolean override)
{
    /* Some confusion here... */
    setFrameText (tag, "TYER", year, encoding, override);
    setFrameText (tag, "TDRC", year, encoding, override);
}

static void
setGenre (struct id3_tag *tag, gchar * genre,
          enum id3_field_textencoding encoding, gboolean override)
{
    setFrameText (tag, "TCON", genre, encoding, override);
}

static void
setSonglen (struct id3_tag *tag, gchar * length,
            enum id3_field_textencoding encoding, gboolean override)
{
    guint seconds;
    gchar *tmp;

    seconds = mmss_to_seconds (length);
    if (seconds > 0) {
        // Convert seconds to milliseconds
        seconds *= 1000;
        // Print the length to a string
        tmp = g_strdup_printf ("%lu", seconds);
        setFrameText (tag, "TLEN", tmp, encoding, override);
        g_free (tmp);
    }
    return;
}


static void
setTracknum (struct id3_tag *tag, guint tracknumin,
             enum id3_field_textencoding encoding, gboolean override)
{
    gchar *tracknum;

    tracknum = g_strdup_printf ("%.2d", tracknumin);
    setFrameText (tag, "TRCK", tracknum, encoding, override);
    g_free (tracknum);
    return;
}

static void
setOrigFilename (struct id3_tag *tag, gchar * filename,
                 enum id3_field_textencoding encoding, gboolean override)
{
    setFrameText (tag, "TOFN", filename, encoding, override);
}

/*****************************************************************************
 * FUNCTIONS FOR GETTING ID3v2 FIELDS
 *****************************************************************************/

gchar *
getArtist (struct id3_tag *tag)
{
    gchar *artname = NULL;

    artname = getFrameText (tag, "TPE1");
    if (artname == NULL)
        artname = getFrameText (tag, "TPE2");
    if (artname == NULL)
        artname = getFrameText (tag, "TPE3");
    if (artname == NULL)
        artname = getFrameText (tag, "TPE4");
    if (artname == NULL)
        artname = getFrameText (tag, "TCOM");
    return artname;
}

gchar *
getTitle (struct id3_tag *tag)
{
    return getFrameText (tag, "TIT2");
}

gchar *
getAlbum (struct id3_tag *tag)
{
    return getFrameText (tag, "TALB");
}

gchar *
getYear (struct id3_tag *tag)
{
    gchar *year = NULL;

    year = getFrameText (tag, "TYER");
    if (year == NULL) {
        year = getFrameText (tag, "TDRC");
    }
    if (year == NULL) {
        year = g_strdup("None");
    }
    return year;
}

gchar *
getGenre (struct id3_tag *tag)
{
    return getFrameText (tag, "TCON");
}

int
getSonglen (struct id3_tag *tag)
{
    gchar *timetext;
    long milliseconds;
    int seconds;

    timetext = getFrameText (tag, "TLEN");
    if (timetext == NULL)
        return -1;

    // g_debug("Found time tag TLEN: %s ms ... ", timetext);
    milliseconds = atol (timetext);
    g_free (timetext);

    if (milliseconds > 0) {
        seconds = (int) milliseconds / 1000;
        // g_debug("%d milliseconds is %d seconds.\n", milliseconds, seconds);
        return seconds;
    }

    g_debug ("ID3v2 TLEN tag time was 0\n");

    return -1;
}

gchar *
getTracknum (struct id3_tag *tag)
{
    gchar trackno[40];
    gchar *trackstr = getFrameText (tag, "TRCK");
    gchar *posstr = getFrameText (tag, "TPOS");
    gint i;

    if (trackstr == NULL) {
        return NULL;
    }
    trackno[0] = '\0';
    // Take care of any a/b formats
    for (i = 0; i < strlen (trackstr); i++) {
        if (trackstr[i] == '/') {
            // Terminate it at switch character
            trackstr[i] = '\0';
            break;
        }
    }
    // "Part of set" variable
    if (posstr != NULL) {
        // Same a/b format problem again
        for (i = 0; i < strlen (posstr); i++) {
            if (posstr[i] == '/') {
                // Terminate it at switch character
                posstr[i] = '\0';
                break;
            }
        }
        strncpy (trackno, posstr, sizeof (trackno));
        if (strlen (trackstr) == 1) {
            strcat (trackno, "0");
        }
        strncat (trackno, trackstr, sizeof (trackno));
        g_free (trackstr);
        g_free (posstr);
    } else {
        strncpy (trackno, trackstr, sizeof (trackno));
        g_free (trackstr);
    }
    // Terminate and return
    trackno[sizeof (trackno) - 1] = '\0';
    return g_strdup (trackno);
}

gchar *
getOrigFilename (struct id3_tag *tag)
{
    return getFrameText (tag, "TOFN");
}

/**
 * Removes a tag from an MP3 file or adds a tag (and removes any old at the same time)
 * @param add whether to add a tag (TRUE) or remove a tag (FALSE)
 * @param path the UTF-8 path to the file, always UTF-8
 * @param v2tag the ID3v2 tag
 * @param v2taglen the length (in bytes) of the ID3v2 tag
 * @param v1tag the ID3v1 tag
 * @param v1taglen the ID3v1 tag length in bytes (128 bytes)
 */
static void
remove_or_add_tag (gboolean add, gchar * path, guchar * v2tag, guint v2taglen,
                   guchar * v1tag, guint v1taglen)
{
    // File descriptors...
    gint f1, f2, f3;
    gchar template[128];
    guint header_taglength = 0;
    guint footer_taglength = 0;
    guint filelength = 0;
    register gchar *buffer;
    guint bufsiz;
    gchar *tmppath;

    strcpy (template, g_get_tmp_dir ());
    template[strlen (template) + 1] = '\0';
    template[strlen (template)] = G_DIR_SEPARATOR;
    strcat (template, "gnomadXXXXXX");

    g_debug ("Removing ID3 tags from/to %s\n", path);

#if GLIB_CHECK_VERSION(2,6,0)
    f1 = (gint) g_open (tmppath, O_RDONLY, 0);
#else
    f1 = (gint) open (tmppath, O_RDONLY, 0);
#endif

    if (f1 < 0) {
        g_debug ("Could not open file f1 in remove_or_add_tag()\n");
        g_free (tmppath);
        return;
    }
    // g_debug("Opened file f1...\n");


    // Allocate a copying buffer
    for (bufsiz = 0x8000; bufsiz >= 128; bufsiz >>= 1) {
        buffer = (gchar *) g_malloc (bufsiz);
        if (buffer)
            break;
    }

    // Temporary file
    f2 = g_mkstemp (template);
    // g_debug("Opened temporary file f2...\n");

    if (f2 >= 0) {
        guchar tag[10];
        gint n;

        // g_debug("Allocated a buffer of %d bytes...\n", bufsiz);

        g_debug ("Looking for ID3v2 header tag\n");

        n = read (f1, tag, 10);
        if (n == 10 && tag[0] == 'I' && tag[1] == 'D' && tag[2] == '3') {
            g_debug ("Found ID3v2 tag header...");
            // Get tag length from the tag - notice that this is an
            // UNSYNCED integer, so the highest bit of each 8-bit group 
            // is unused (always 0).
            header_taglength =
                ((tag[6] & 0x7F) << 21) | ((tag[7] & 0x7F) << 14) |
                ((tag[8] & 0x7F) << 7) | (tag[9] & 0x7F);
            if ((tag[5] & 0x10) == 0) {
                header_taglength += 10;
            } else {
                header_taglength += 20;
            }
            g_debug (" %d (0x%x) bytes\n", header_taglength,
                     header_taglength);

            // Wind past any RIFF header too
            n = read (f1, buffer, bufsiz);
            header_taglength +=
                riff_header_size (&buffer[header_taglength], n);
            g_debug ("ID3v2 header (and any RIFF header) %d (0x%x) bytes\n",
                     header_taglength, header_taglength);
        } else {
            // Any plain RIFF header is removed too.
            gint n;

            n = read (f1, buffer, bufsiz);
            header_taglength = riff_header_size (&buffer[0], n);
        }

        // As we move to the end of the file, detect the filelength
        filelength = lseek (f1, 0, SEEK_END);

        if (filelength > 0) {
            // Then detect the length of any ID3v1 tag
            g_debug ("Detecting ID3v1 tag\n");
            if (lseek (f1, -128, SEEK_END) > 0) {
                guchar tag[3];
                gint n;

                n = read (f1, tag, 3);
                if (n == 3 && tag[0] == 'T' && tag[1] == 'A' && tag[2] == 'G') {
                    g_debug ("Found ID3v1 tag footer, 128 (0x80) bytes...\n");
                    footer_taglength = 128;
                }
            }
            // Then detect the length of any ID3v2 footer tag
            g_debug ("Detecting ID3v2 footer tag\n");
            if (lseek (f1, -10 - footer_taglength, SEEK_END) > 0) {
                guchar tag[10];
                guint footlen;
                gint n;

                n = read (f1, tag, 10);
                if (n == 10 &&
                    tag[0] == '3' && tag[1] == 'D' && tag[2] == 'I') {
                    g_debug ("Found ID3v2 footer tag...");
                    footlen =
                        ((tag[6] & 0x7F) << 21) | ((tag[7] & 0x7F) << 14) |
                        ((tag[8] & 0x7F) << 7) | (tag[9] & 0x7F);
                    // First remove the tag headers
                    footer_taglength += 20;
                    // Then remove the indicated length (no looking for bad tag here)
                    footer_taglength += footlen;
                    g_debug (" %d (0x%x) bytes.\n", footlen, footlen);
                }
            }

            g_debug
                ("Header %d (0x%x) bytes, footer %d (0x%x) bytes to be removed.\n",
                 header_taglength, header_taglength, footer_taglength,
                 footer_taglength);

            if (add) {
                // Write ID3v2 tag at the beginning of the file
                g_debug ("Adding ID3v2 tag to file\n");
                if (v2taglen != write (f2, v2tag, v2taglen)) {
                    g_debug
                        ("Error writing ID3v2 header tag in remove_or_add_tag()\n");
                }
            }
            // Next skip past the header, and copy until we reach the footer
            g_debug ("Copying original file, ");
            if (lseek (f1, header_taglength, SEEK_SET) >= 0) {
                guint remain =
                    filelength - header_taglength - footer_taglength;

                g_debug ("%d bytes.\n", remain);
                while (remain > 0) {
                    register gint n;

                    if (remain > bufsiz) {
                        n = read (f1, buffer, bufsiz);
                    } else {
                        n = read (f1, buffer, remain);
                    }
                    if (n == -1) {
                        g_debug
                            ("Error reading source file during file copying in remove_or_add_tag()\n");
                        break;
                    }
                    if (n == 0) {
                        break;
                    }
                    if (n != write (f2, buffer, n)) {
                        g_debug
                            ("Error writing target file during file copying in remove_or_add_tag()\n");
                        break;
                    }
                    remain -= n;
                }
            }
        }
        close (f1);

        if (add) {
            // Write ID3v1 tag at the end of the file
            g_debug ("Adding ID3v1 tag to file\n");
            if (v1taglen != write (f2, v1tag, v1taglen)) {
                g_debug
                    ("Error writing ID3v1 footer tag in remove_or_add_tag()\n");
            }
        }
        // Then copy back the stripped file
        // Rewind the temporary file
        lseek (f2, 0, SEEK_SET);
        g_debug ("Copying the file back...\n");
#if GLIB_CHECK_VERSION(2,8,0)
        f3 = (gint) g_creat (tmppath,
                             (mode_t) (S_IRUSR | S_IWUSR | S_IRGRP |
                                       S_IROTH));
#else
        f3 = (gint) creat (tmppath,
                           (mode_t) (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
#endif
        if (f3) {
            // g_debug("Creat() on original file succeeded...\n");
            while (1) {
                register gint n;

                n = read (f2, buffer, bufsiz);
                if (n == -1) {
                    g_debug
                        ("Error reading source file during final copying in remove_or_add_tag()\n");
                    break;
                }
                if (n == 0) {
                    break;
                }
                if (n != write (f3, buffer, n)) {
                    g_debug
                        ("Error writing target file during final copying in remove_or_add_tag()\n");
                    break;
                }
            }
            close (f3);
        } else {
            g_debug
                ("Erroneous file descriptor when copying file back in remove_or_add_tag()\n");
        }
        close (f2);
        // g_debug("Deleting %s\n", template);
#if GLIB_CHECK_VERSION(2,6,0)
        g_unlink (template);
#else
        unlink (template);
#endif
    } else {
        // In case we couldn't open f2
        close (f1);
    }
    g_free (tmppath);
    g_free (buffer);
}



/*****************************************************************************
 * EXPORTED FUNCTIONS
 *****************************************************************************/


void
remove_tag_from_mp3file (gchar * path)
{
    remove_or_add_tag (FALSE, path, NULL, 0, NULL, 0);
}


void
set_tag_for_mp3file (int fd, const LIBMTP_track_t *trackdata, int override)
{
    struct id3_file *fh;
    struct id3_tag *tag;
    gchar *tmppath;
    enum id3_field_textencoding encoding;
    id3_length_t tagv2len;
    id3_length_t tagv1len;
    id3_byte_t *tagv2;
    id3_byte_t *tagv1;

    g_debug ("Setting tag info for %s...\n", tmppath);

    /* Get the tag for the old file */
    fh = id3_file_fdopen (fd, ID3_FILE_MODE_READONLY);

    if (!fh) {
        g_debug ("Could not open file %s!\n", trackdata->filename);
        return;
    }
    tag = id3_file_tag (fh);
    if (!tag) {
        g_debug ("Could not get tag for file %s!\n", trackdata->filename);
        return;
    }

    /* use the same encoding as before... */
    encoding = get_encoding_of_tag (tag);

    /* Close old file. */
    // id3_file_close(fh);

    if (trackdata->artist != NULL && strcmp (trackdata->artist, "<Unknown>")) {
        setArtist (tag, trackdata->artist, encoding, override);
    }
    if (trackdata->title != NULL && strcmp (trackdata->title, "<Unknown>")) {
        setTitle (tag, trackdata->title, encoding, override);
    }
    if (trackdata->album != NULL && strcmp (trackdata->album, "<Unknown>")) {
        setAlbum (tag, trackdata->album, encoding, override);
    }
    if (trackdata->date != NULL && strcmp(trackdata->date, "<Unknown>")) {
        setYear (tag, trackdata->date, encoding, override);
    }
    if (trackdata->genre != NULL && strcmp (trackdata->genre, "<Unknown>")) {
        setGenre (tag, trackdata->genre, encoding, override);
    }
    if (trackdata->duration != 0) {
        gchar *tmp;
        tmp = seconds_to_mmss(trackdata->duration/1000);
        setSonglen (tag, tmp, encoding, override);
        g_free(tmp);
    }
    setTracknum (tag, trackdata->tracknumber, encoding, override);
    if (trackdata->filename != NULL &&
        strlen (trackdata->filename) && strcmp (trackdata->filename, "0")) {
        setOrigFilename (tag, trackdata->filename, encoding, override);
    }

    /* Render tag so we can look at it */
    tagv2 = g_malloc (64738);
    tagv1 = g_malloc (128);

    /* Render ID3v2 tag */
    id3_tag_options (tag, ID3_TAG_OPTION_ID3V1, 0);
    tagv2len = id3_tag_render (tag, tagv2);
    g_debug ("Rendered ID3v2 tag, length %d (0x%x) bytes:\n", tagv2len,
             tagv2len);

    /* Render ID3v1 tag */
    id3_tag_options (tag, ID3_TAG_OPTION_ID3V1, ~0);
    tagv1len = id3_tag_render (tag, tagv1);
    g_debug ("Rendered ID3v1 tag 128 (0x80) bytes:\n");

    /* Close old file */
    id3_file_close (fh);

    /* Totally revamp file */
    remove_or_add_tag (TRUE, trackdata->filename, tagv2, tagv2len, tagv1, tagv1len);

    g_free (tagv2);
    g_free (tagv1);
    g_free (tmppath);
}


syntax highlighted by Code2HTML, v. 0.9.1