*/
#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)
ruby_xfree (o);
}
+static void
+c_mark (RbVorbisTagger *o)
+{
+ rb_gc_mark (o->comments);
+}
+
static VALUE
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)
/*
* 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;
+ 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 ();
+ StringValue (filename);
+
+ o->state = vcedit_state_new (StringValuePtr (filename));
if (!o->state)
- rb_raise (eVTError, "vcedit_new_state() failed - %s",
- vcedit_error (o->state));
+ rb_raise (rb_eNoMemError, "Out of Memory");
- 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));
+ 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++) {
+ char *ptr, *content = vc->user_comments[i];
+
+ ptr = strchr (content, '=');
+ if (!ptr || ptr == content)
+ rb_raise (eInvalidComment, "invalid comment - %s", content);
+ }
o->comments = rb_class_new_instance (0, NULL, cComments);
/*
* 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)
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;
}
/*
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);
+ return rb_funcall2 (o->comments, id_length, 0, NULL);
}
/*
Data_Get_Struct (self, RbVorbisTagger, o);
+ CHECK_CLOSED (o);
+
return o->comments;
}
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);