Made Ogg::Vorbis::Tagger#close return nil.
[ruby-vorbistagger.git] / ext / ext.c
1 /*
2  * Copyright (C) 2006 Tilman Sauerbeck (tilman at code-monkey de)
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation, version 2.
7  *
8  * This library is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public
14  * License along with this library; if not, write to the Free Software
15  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
16  * MA 02110-1301 USA
17  */
18
19 #include <ruby.h>
20 #include <stdio.h>
21 #include <stdbool.h>
22 #include <errno.h>
23 #include <assert.h>
24
25 #include "vcedit.h"
26 #include "comments.h"
27
28 #define CHECK_CLOSED(o) \
29         if (!o->state) \
30                 rb_raise (eClosed, "closed stream");
31
32 typedef struct {
33         vcedit_state *state;
34         VALUE comments;
35 } RbVorbisTagger;
36
37 static VALUE c_close (VALUE self);
38
39 static VALUE cComments, eVT, eClosed, eOpen, eInvalidData,
40              eInvalidComment, eTempFile, eReopen;
41 static ID id_length;
42
43 static void
44 c_free (RbVorbisTagger *o)
45 {
46         /* just in case the user forgot to call #close himself */
47         if (o->state)
48                 vcedit_state_unref (o->state);
49
50         ruby_xfree (o);
51 }
52
53 static void
54 c_mark (RbVorbisTagger *o)
55 {
56         rb_gc_mark (o->comments);
57 }
58
59 static VALUE
60 c_alloc (VALUE klass)
61 {
62         RbVorbisTagger *o;
63
64         return Data_Make_Struct (klass, RbVorbisTagger, c_mark, c_free, o);
65 }
66
67 /*
68  * call-seq:
69  *  Ogg::Vorbis::Tagger.open(arg)                    -> object
70  *  Ogg::Vorbis::Tagger.open(arg) { |object| block } -> object
71  *
72  * If a block isn't specified, Ogg::Vorbis::Tagger.open is a synonym
73  * for Ogg::Vorbis::Tagger.new.
74  * If a block is given, it will be invoked with the * Ogg::Vorbis::Tagger
75  * object as a parameter.
76  */
77 static VALUE
78 c_open (VALUE klass, VALUE arg)
79 {
80         VALUE obj = rb_class_new_instance (1, &arg, klass);
81
82         if (rb_block_given_p ())
83                 return rb_ensure (rb_yield, obj, c_close, obj);
84         else
85                 return obj;
86 }
87
88 /*
89  * call-seq:
90  *  Ogg::Vorbis::Tagger.new(filename) -> object
91  *
92  * Returns a new Ogg::Vorbis::Tagger object for the specified file.
93  *
94  * FIXME: add optional mode argument (read-only or read-write)
95  */
96 static VALUE
97 c_init (VALUE self, VALUE filename)
98 {
99         RbVorbisTagger *o;
100         vorbis_comment *vc;
101         int i;
102
103         Data_Get_Struct (self, RbVorbisTagger, o);
104
105         StringValue (filename);
106
107         o->state = vcedit_state_new (StringValuePtr (filename));
108         if (!o->state)
109                 rb_raise (rb_eNoMemError, "Out of Memory");
110
111         switch (vcedit_open (o->state)) {
112                 case VCEDIT_ERR_OPEN:
113                         rb_raise (eOpen, "Cannot open file");
114                 case VCEDIT_ERR_INVAL:
115                         rb_raise (eInvalidData, "Invalid data");
116                 default:
117                         break;
118         }
119
120         vc = vcedit_comments (o->state);
121
122         /* vcedit_open() succeeded, so vcedit_comments() cannot
123          * return NULL.
124          */
125         assert (vc);
126
127         /* check whether all comments are well-formed */
128         for (i = 0; i < vc->comments; i++) {
129                 char *ptr, *content = vc->user_comments[i];
130
131                 ptr = strchr (content, '=');
132                 if (!ptr || ptr == content)
133                         rb_raise (eInvalidComment, "invalid comment - %s", content);
134         }
135
136         o->comments = rb_class_new_instance (0, NULL, cComments);
137
138         comments_init (o->comments, o->state);
139
140         return self;
141 }
142
143 /*
144  * call-seq:
145  *  object.close -> nil
146  *
147  * Closes *object*. Further method calls on *object* will raise an
148  * Ogg::Vorbis::Tagger::ClosedStreamError exception.
149  * Returns +nil+.
150  */
151 static VALUE
152 c_close (VALUE self)
153 {
154         RbVorbisTagger *o;
155
156         Data_Get_Struct (self, RbVorbisTagger, o);
157
158         CHECK_CLOSED (o);
159
160         vcedit_state_unref (o->state);
161         o->state = NULL;
162
163         return Qnil;
164 }
165
166 /*
167  * call-seq:
168  *  object.write -> integer
169  *
170  * Writes the comments from *object* back to the IO object and
171  * returns the numbers of comments written.
172  */
173 static VALUE
174 c_write (VALUE self)
175 {
176         RbVorbisTagger *o;
177
178         Data_Get_Struct (self, RbVorbisTagger, o);
179
180         CHECK_CLOSED (o);
181
182         comments_sync (o->comments, o->state);
183
184         switch (vcedit_write (o->state)) {
185                 case VCEDIT_ERR_INVAL:
186                         rb_raise (eInvalidData, "Invalid data");
187                 case VCEDIT_ERR_TMPFILE:
188                         rb_raise (eTempFile, "Cannot create temporary file");
189                 case VCEDIT_ERR_REOPEN:
190                         rb_raise (eReopen, "Cannot reopen file");
191                 default:
192                         break;
193         }
194
195         return rb_funcall (o->comments, id_length, 0);
196 }
197
198 /*
199  * call-seq:
200  *  object.comments -> comments
201  *
202  * Returns the comments collection of *object*, which is an instance of
203  * Ogg::Vorbis::Comments.
204  */
205 static VALUE
206 c_comments (VALUE self)
207 {
208         RbVorbisTagger *o;
209
210         Data_Get_Struct (self, RbVorbisTagger, o);
211
212         CHECK_CLOSED (o);
213
214         return o->comments;
215 }
216
217 EXT_API
218 void
219 Init_vorbistagger_ext (void)
220 {
221         VALUE mOgg, mVorbis, eOgg, eVorbis, cVT;
222
223         mOgg = rb_define_module ("Ogg");
224         mVorbis = rb_define_module_under (mOgg, "Vorbis");
225
226         eOgg = rb_define_class_under (mOgg, "OggError", rb_eStandardError);
227         eVorbis = rb_define_class_under (mVorbis, "VorbisError", eOgg);
228
229         cVT = rb_define_class_under (mVorbis, "Tagger", rb_cObject);
230
231         rb_define_alloc_func (cVT, c_alloc);
232
233         rb_define_singleton_method (cVT, "open", c_open, 1);
234         rb_define_method (cVT, "initialize", c_init, 1);
235         rb_define_method (cVT, "close", c_close, 0);
236         rb_define_method (cVT, "write", c_write, 0);
237         rb_define_method (cVT, "comments", c_comments, 0);
238
239         eVT = rb_define_class_under (cVT, "TaggerError", eVorbis);
240         eClosed = rb_define_class_under (cVT, "ClosedStreamError", eVT);
241         eOpen = rb_define_class_under (cVT, "OpenError", eVT);
242         eInvalidData = rb_define_class_under (cVT, "InvalidDataError", eVT);
243         eInvalidComment = rb_define_class_under (cVT, "InvalidCommentError",
244                                                  eInvalidData);
245         eTempFile = rb_define_class_under (cVT, "TempFileError", eVT);
246         eReopen = rb_define_class_under (cVT, "ReopenError", eVT);
247
248         id_length = rb_intern ("length");
249
250         cComments = Init_Comments (mVorbis);
251 }