Made Ogg::Vorbis::Tagger#close return nil.
[ruby-vorbistagger.git] / ext / ext.c
index 4794d282dca70b4a66d20321a1eb24e9a7be2c45..46ce0d178d1090296f6a74b16e29b205e692c706 100644 (file)
--- a/ext/ext.c
+++ b/ext/ext.c
  */
 
 #include <ruby.h>
+#include <stdio.h>
 #include <stdbool.h>
+#include <errno.h>
+#include <assert.h>
 
 #include "vcedit.h"
 #include "comments.h"
 
-typedef struct {
-       VALUE io;
-       bool need_close;
+#define CHECK_CLOSED(o) \
+       if (!o->state) \
+               rb_raise (eClosed, "closed stream");
 
+typedef struct {
        vcedit_state *state;
-
        VALUE comments;
-       VALUE io_buf;
 } RbVorbisTagger;
 
 static VALUE c_close (VALUE self);
 
-static VALUE cComments, eVTError;
-static ID id_read, id_write, id_seek, id_length;
-
-static size_t
-on_read (void *ptr, size_t size, size_t nmemb, RbVorbisTagger *o)
-{
-       struct RString *buf;
-       size_t total = size * nmemb;
-       VALUE tmp;
-
-       rb_str_resize (o->io_buf, size * nmemb);
-
-       tmp = rb_funcall (o->io, id_read, 2, LONG2NUM (total), o->io_buf);
-       if (NIL_P (tmp))
-               return 0;
-
-       buf = RSTRING (tmp);
-       memcpy (ptr, buf->ptr, buf->len);
-
-       return buf->len;
-}
-
-static size_t
-on_write (const void *ptr, size_t size, size_t nmemb, RbVorbisTagger *o)
-{
-       size_t total = size * nmemb;
-
-       rb_str_resize (o->io_buf, total);
-       memcpy (RSTRING (o->io_buf)->ptr, ptr, total);
-
-       return NUM2LONG (rb_io_write (o->io, o->io_buf));
-}
-
-static void
-c_mark (RbVorbisTagger *o)
-{
-       rb_gc_mark (o->io);
-       rb_gc_mark (o->comments);
-       rb_gc_mark (o->io_buf);
-}
+static VALUE cComments, eVT, eClosed, eOpen, eInvalidData,
+             eInvalidComment, eTempFile, eReopen;
+static ID id_length;
 
 static void
 c_free (RbVorbisTagger *o)
@@ -85,6 +50,12 @@ c_free (RbVorbisTagger *o)
        ruby_xfree (o);
 }
 
+static void
+c_mark (RbVorbisTagger *o)
+{
+       rb_gc_mark (o->comments);
+}
+
 static VALUE
 c_alloc (VALUE klass)
 {
@@ -96,14 +67,12 @@ c_alloc (VALUE klass)
 /*
  * call-seq:
  *  Ogg::Vorbis::Tagger.open(arg)                    -> object
- *  Ogg::Vorbis::Tagger.open(arg) { |object| block } -> nil
+ *  Ogg::Vorbis::Tagger.open(arg) { |object| block } -> object
  *
  * If a block isn't specified, Ogg::Vorbis::Tagger.open is a synonym
  * for Ogg::Vorbis::Tagger.new.
- * If a block is given, it will be invoked with the
- * Ogg::Vorbis::Tagger object as a parameter, and the file or IO object
- * will be automatically closed when the block terminates.
- * The method always returns +nil+ in this case.
+ * If a block is given, it will be invoked with the * Ogg::Vorbis::Tagger
+ * object as a parameter.
  */
 static VALUE
 c_open (VALUE klass, VALUE arg)
@@ -118,52 +87,42 @@ c_open (VALUE klass, VALUE arg)
 
 /*
  * call-seq:
- *  Ogg::Vorbis::Tagger.new(arg) -> object
+ *  Ogg::Vorbis::Tagger.new(filename) -> object
  *
- * Returns a new Ogg::Vorbis::Tagger object for the specified argument.
- * *arg* can either be an IO object or a filename.
+ * Returns a new Ogg::Vorbis::Tagger object for the specified file.
  *
  * FIXME: add optional mode argument (read-only or read-write)
  */
 static VALUE
-c_init (VALUE self, VALUE io)
+c_init (VALUE self, VALUE filename)
 {
        RbVorbisTagger *o;
        vorbis_comment *vc;
-       int s, i;
+       int i;
 
        Data_Get_Struct (self, RbVorbisTagger, o);
 
-       /* is this actually an IO object or a filename? */
-       if (rb_respond_to (io, id_read) &&
-           rb_respond_to (io, id_write) &&
-           rb_respond_to (io, id_seek))
-               o->need_close = false;
-       else if (!NIL_P (rb_check_string_type (io))) {
-               io = rb_file_open (StringValuePtr (io), "rb+");
-               o->need_close = true;
-       } else
-               rb_raise (rb_eArgError, "invalid argument");
-
-       o->io = io;
-       o->io_buf = rb_str_buf_new (BUFSIZ);
-
-       o->state = vcedit_state_new ();
-       if (!o->state)
-               rb_raise (eVTError, "vcedit_new_state() failed - %s",
-                         vcedit_error (o->state));
+       StringValue (filename);
 
-       s = vcedit_open_callbacks (o->state, o,
-                                  (vcedit_read_func) on_read,
-                                  (vcedit_write_func) on_write);
-       if (s < 0)
-               rb_raise (eVTError, "vcedit_open_callbacks() failed - %s",
-                         vcedit_error (o->state));
+       o->state = vcedit_state_new (StringValuePtr (filename));
+       if (!o->state)
+               rb_raise (rb_eNoMemError, "Out of Memory");
+
+       switch (vcedit_open (o->state)) {
+               case VCEDIT_ERR_OPEN:
+                       rb_raise (eOpen, "Cannot open file");
+               case VCEDIT_ERR_INVAL:
+                       rb_raise (eInvalidData, "Invalid data");
+               default:
+                       break;
+       }
 
        vc = vcedit_comments (o->state);
-       if (!vc)
-               rb_raise (eVTError, "vcedit_comments() failed - %s",
-                         vcedit_error (o->state));
+
+       /* vcedit_open() succeeded, so vcedit_comments() cannot
+        * return NULL.
+        */
+       assert (vc);
 
        /* check whether all comments are well-formed */
        for (i = 0; i < vc->comments; i++) {
@@ -171,7 +130,7 @@ c_init (VALUE self, VALUE io)
 
                ptr = strchr (content, '=');
                if (!ptr || ptr == content)
-                       rb_raise (eVTError, "malformed comment - %s", content);
+                       rb_raise (eInvalidComment, "invalid comment - %s", content);
        }
 
        o->comments = rb_class_new_instance (0, NULL, cComments);
@@ -183,9 +142,11 @@ c_init (VALUE self, VALUE io)
 
 /*
  * call-seq:
- *  object.close -> object
+ *  object.close -> nil
  *
- * Closes *object* and returns it.
+ * Closes *object*. Further method calls on *object* will raise an
+ * Ogg::Vorbis::Tagger::ClosedStreamError exception.
+ * Returns +nil+.
  */
 static VALUE
 c_close (VALUE self)
@@ -194,13 +155,12 @@ c_close (VALUE self)
 
        Data_Get_Struct (self, RbVorbisTagger, o);
 
+       CHECK_CLOSED (o);
+
        vcedit_state_unref (o->state);
        o->state = NULL;
 
-       if (o->need_close)
-               rb_io_close (o->io);
-
-       return self;
+       return Qnil;
 }
 
 /*
@@ -214,19 +174,23 @@ static VALUE
 c_write (VALUE self)
 {
        RbVorbisTagger *o;
-       int s;
 
        Data_Get_Struct (self, RbVorbisTagger, o);
 
-       comments_sync (o->comments);
+       CHECK_CLOSED (o);
 
-       /* seek back to BOF */
-       rb_funcall (o->io, id_seek, 1, INT2FIX (0));
+       comments_sync (o->comments, o->state);
 
-       s = vcedit_write (o->state, o);
-       if (s < 0)
-               rb_raise (rb_eIOError, "write failed - %s",
-                         vcedit_error (o->state));
+       switch (vcedit_write (o->state)) {
+               case VCEDIT_ERR_INVAL:
+                       rb_raise (eInvalidData, "Invalid data");
+               case VCEDIT_ERR_TMPFILE:
+                       rb_raise (eTempFile, "Cannot create temporary file");
+               case VCEDIT_ERR_REOPEN:
+                       rb_raise (eReopen, "Cannot reopen file");
+               default:
+                       break;
+       }
 
        return rb_funcall (o->comments, id_length, 0);
 }
@@ -245,6 +209,8 @@ c_comments (VALUE self)
 
        Data_Get_Struct (self, RbVorbisTagger, o);
 
+       CHECK_CLOSED (o);
+
        return o->comments;
 }
 
@@ -270,11 +236,15 @@ Init_vorbistagger_ext (void)
        rb_define_method (cVT, "write", c_write, 0);
        rb_define_method (cVT, "comments", c_comments, 0);
 
-       eVTError = rb_define_class_under (cVT, "TaggerError", eVorbis);
+       eVT = rb_define_class_under (cVT, "TaggerError", eVorbis);
+       eClosed = rb_define_class_under (cVT, "ClosedStreamError", eVT);
+       eOpen = rb_define_class_under (cVT, "OpenError", eVT);
+       eInvalidData = rb_define_class_under (cVT, "InvalidDataError", eVT);
+       eInvalidComment = rb_define_class_under (cVT, "InvalidCommentError",
+                                                eInvalidData);
+       eTempFile = rb_define_class_under (cVT, "TempFileError", eVT);
+       eReopen = rb_define_class_under (cVT, "ReopenError", eVT);
 
-       id_read = rb_intern ("read");
-       id_write = rb_intern ("write");
-       id_seek = rb_intern ("seek");
        id_length = rb_intern ("length");
 
        cComments = Init_Comments (mVorbis);