Reworked output file writing.
[ruby-vorbistagger.git] / ext / vcedit.c
index c7bd8e75becb618d5ace62fd212f33db2001ca0b..b6c7dfd8d0d0db18e361b0fd79b37fa74a1664cb 100644 (file)
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
+#include <limits.h>
+#include <unistd.h>
 #include <ogg/ogg.h>
 #include <vorbis/codec.h>
+#include <assert.h>
 
 #include "vcedit.h"
 
 #define CHUNKSIZE 4096
 
+static int vcedit_open (vcedit_state *state);
+
 struct vcedit_state_St {
        int refcount;
 
@@ -36,10 +41,9 @@ struct vcedit_state_St {
        vorbis_comment *vc;
        vorbis_info *vi;
 
-       vcedit_read_func read;
-       vcedit_write_func write;
+       char filename[PATH_MAX];
 
-       void *in;
+       FILE *in;
        long serial;
        unsigned char *mainbuf;
        unsigned char *bookbuf;
@@ -53,7 +57,7 @@ struct vcedit_state_St {
 };
 
 vcedit_state *
-vcedit_state_new (void)
+vcedit_state_new (const char *filename)
 {
        vcedit_state *state;
 
@@ -65,6 +69,16 @@ vcedit_state_new (void)
 
        state->refcount = 1;
 
+       snprintf (state->filename, sizeof (state->filename),
+                 "%s", filename);
+
+       state->in = fopen (state->filename, "rb");
+
+       if (vcedit_open (state) < 0) {
+               free (state);
+               return NULL;
+       }
+
        return state;
 }
 
@@ -83,35 +97,43 @@ vcedit_comments (vcedit_state *state)
 static void
 vcedit_clear_internals (vcedit_state *state)
 {
-       const char *tmp;
-
        if (state->vc) {
                vorbis_comment_clear (state->vc);
                free (state->vc);
+               state->vc = NULL;
        }
 
        if (state->os) {
                ogg_stream_clear (state->os);
                free (state->os);
+               state->os = NULL;
        }
 
        if (state->oy) {
                ogg_sync_clear (state->oy);
                free (state->oy);
+               state->oy = NULL;
        }
 
        free (state->vendor);
+       state->vendor = NULL;
+
        free (state->mainbuf);
+       state->mainbuf = NULL;
+       state->mainlen = 0;
+
        free (state->bookbuf);
+       state->bookbuf = NULL;
+       state->booklen = 0;
 
-    if (state->vi) {
+       if (state->vi) {
                vorbis_info_clear (state->vi);
-        free (state->vi);
-    }
+               free (state->vi);
+               state->vi = NULL;
+       }
 
-    tmp = state->lasterror;
-    memset (state, 0, sizeof (vcedit_state));
-    state->lasterror = tmp;
+       state->serial = 0;
+       state->prevW = state->extrapage = state->eosin = 0;
 }
 
 void
@@ -126,6 +148,7 @@ vcedit_state_unref (vcedit_state *state)
        state->refcount--;
 
        if (!state->refcount) {
+               fclose (state->in);
                vcedit_clear_internals (state);
                free (state);
        }
@@ -220,7 +243,7 @@ _fetch_next_packet (vcedit_state *s, ogg_packet *p, ogg_page *page)
 
        while (ogg_sync_pageout (s->oy, page) <= 0) {
                buffer = ogg_sync_buffer (s->oy, CHUNKSIZE);
-               bytes = s->read (buffer, 1, CHUNKSIZE, s->in);
+               bytes = fread (buffer, 1, CHUNKSIZE, s->in);
                ogg_sync_wrote (s->oy, bytes);
 
                if (!bytes)
@@ -240,10 +263,8 @@ _fetch_next_packet (vcedit_state *s, ogg_packet *p, ogg_page *page)
        return _fetch_next_packet (s, p, page);
 }
 
-int
-vcedit_open_callbacks (vcedit_state *state, void *in,
-                       vcedit_read_func read_func,
-                       vcedit_write_func write_func)
+static int
+vcedit_open (vcedit_state *state)
 {
        char *buffer;
        int bytes, i;
@@ -252,16 +273,12 @@ vcedit_open_callbacks (vcedit_state *state, void *in,
        ogg_packet header_main, header_comments, header_codebooks;
        ogg_page og;
 
-       state->in = in;
-       state->read = read_func;
-       state->write = write_func;
-
        state->oy = malloc (sizeof (ogg_sync_state));
        ogg_sync_init (state->oy);
 
        while (1) {
                buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
-               bytes = state->read (buffer, 1, CHUNKSIZE, state->in);
+               bytes = fread (buffer, 1, CHUNKSIZE, state->in);
 
                ogg_sync_wrote (state->oy, bytes);
 
@@ -348,7 +365,7 @@ vcedit_open_callbacks (vcedit_state *state, void *in,
                }
 
                buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
-               bytes = state->read (buffer, 1, CHUNKSIZE, state->in);
+               bytes = fread (buffer, 1, CHUNKSIZE, state->in);
 
                if (bytes == 0 && i < 2) {
                        state->lasterror = "EOF before end of vorbis headers.";
@@ -371,16 +388,36 @@ err:
 }
 
 int
-vcedit_write (vcedit_state *state, void *out)
+vcedit_write (vcedit_state *state)
 {
        ogg_stream_state streamout;
        ogg_packet header_main, header_comments, header_codebooks, op;
        ogg_page ogout, ogin;
        ogg_int64_t granpos = 0;
-       int result, bytes, needflush = 0, needout = 0;
-       char *buffer;
+       FILE *out;
+       char *buffer, tmpfile[PATH_MAX];
+       int s, result, bytes, needflush = 0, needout = 0;
        size_t tmp;
 
+       strcpy (tmpfile, state->filename);
+       strcat (tmpfile, ".XXXXXX");
+
+       s = mkstemp (tmpfile);
+       if (s == -1) {
+               state->lasterror = "Error writing stream to output. "
+                                  "Cannot open temporary file.";
+               return -1;
+       }
+
+       out = fdopen (s, "wb");
+       if (!out) {
+               unlink (tmpfile);
+               close (s);
+               state->lasterror = "Error writing stream to output. "
+                                  "Cannot open temporary file.";
+               return -1;
+       }
+
        state->eosin = 0;
        state->extrapage = 0;
 
@@ -405,11 +442,11 @@ vcedit_write (vcedit_state *state, void *out)
        ogg_stream_packetin (&streamout, &header_codebooks);
 
        while ((result = ogg_stream_flush (&streamout, &ogout))) {
-               tmp = state->write (ogout.header, 1, ogout.header_len, out);
+               tmp = fwrite (ogout.header, 1, ogout.header_len, out);
                if (tmp != (size_t) ogout.header_len)
                        goto cleanup;
 
-               tmp = state->write (ogout.body, 1, ogout.body_len, out);
+               tmp = fwrite (ogout.body, 1, ogout.body_len, out);
                if (tmp != (size_t) ogout.body_len)
                        goto cleanup;
        }
@@ -422,21 +459,21 @@ vcedit_write (vcedit_state *state, void *out)
 
                if (needflush) {
                        if (ogg_stream_flush (&streamout, &ogout)) {
-                               tmp = state->write (ogout.header, 1, ogout.header_len, out);
+                               tmp = fwrite (ogout.header, 1, ogout.header_len, out);
                                if (tmp != (size_t) ogout.header_len)
                                        goto cleanup;
 
-                               tmp = state->write (ogout.body, 1, ogout.body_len, out);
+                               tmp = fwrite (ogout.body, 1, ogout.body_len, out);
                                if (tmp != (size_t) ogout.body_len)
                                        goto cleanup;
                        }
                } else if (needout) {
                        if (ogg_stream_pageout (&streamout, &ogout)) {
-                               tmp = state->write (ogout.header, 1, ogout.header_len, out);
+                               tmp = fwrite (ogout.header, 1, ogout.header_len, out);
                                if (tmp != (size_t) ogout.header_len)
                                        goto cleanup;
 
-                               tmp = state->write (ogout.body, 1, ogout.body_len, out);
+                               tmp = fwrite (ogout.body, 1, ogout.body_len, out);
                                if (tmp != (size_t) ogout.body_len)
                                        goto cleanup;
                        }
@@ -465,21 +502,21 @@ vcedit_write (vcedit_state *state, void *out)
        streamout.e_o_s = 1;
 
        while (ogg_stream_flush (&streamout, &ogout)) {
-               tmp = state->write (ogout.header, 1, ogout.header_len, out);
+               tmp = fwrite (ogout.header, 1, ogout.header_len, out);
                if (tmp != (size_t) ogout.header_len)
                        goto cleanup;
 
-               tmp = state->write (ogout.body, 1, ogout.body_len, out);
+               tmp = fwrite (ogout.body, 1, ogout.body_len, out);
                if (tmp != (size_t) ogout.body_len)
                        goto cleanup;
        }
 
        if (state->extrapage) {
-               tmp = state->write (ogin.header, 1, ogin.header_len, out);
+               tmp = fwrite (ogin.header, 1, ogin.header_len, out);
                if (tmp != (size_t) ogin.header_len)
                        goto cleanup;
 
-               tmp = state->write (ogin.body, 1, ogin.body_len, out);
+               tmp = fwrite (ogin.body, 1, ogin.body_len, out);
                if (tmp != (size_t) ogin.body_len)
                        goto cleanup;
        }
@@ -503,18 +540,18 @@ vcedit_write (vcedit_state *state, void *out)
                                /* Don't bother going through the rest, we can just
                                 * write the page out now
                                 */
-                               tmp = state->write (ogout.header,1,ogout.header_len, out);
+                               tmp = fwrite (ogout.header, 1, ogout.header_len, out);
                                if (tmp != (size_t) ogout.header_len)
                                        goto cleanup;
 
-                               tmp = state->write (ogout.body,1,ogout.body_len, out);
+                               tmp = fwrite (ogout.body, 1, ogout.body_len, out);
                                if (tmp != (size_t) ogout.body_len)
                                        goto cleanup;
                        }
                }
 
                buffer = ogg_sync_buffer (state->oy, CHUNKSIZE);
-               bytes = state->read (buffer, 1, CHUNKSIZE, state->in);
+               bytes = fread (buffer, 1, CHUNKSIZE, state->in);
                ogg_sync_wrote (state->oy, bytes);
 
                if (!bytes) {
@@ -523,6 +560,12 @@ vcedit_write (vcedit_state *state, void *out)
                }
        }
 
+       fclose (out);
+       fclose (state->in);
+
+       unlink (state->filename);
+       rename (tmpfile, state->filename);
+
 cleanup:
        ogg_stream_clear (&streamout);
 
@@ -542,5 +585,10 @@ cleanup:
                return -1;
        }
 
+       vcedit_clear_internals (state);
+
+       state->in = fopen (state->filename, "rb");
+       vcedit_open (state);
+
        return 0;
 }