Changed the initialize size of the IO buffer to 0.
[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 <stdbool.h>
21
22 #include "vcedit.h"
23 #include "comments.h"
24
25 typedef struct {
26         VALUE io;
27         bool need_close;
28
29         vcedit_state *state;
30
31         VALUE comments;
32 } RbVorbisTagger;
33
34 static VALUE c_close (VALUE self);
35
36 static VALUE cComments, eVTError, io_buf;
37 static ID id_read, id_write, id_seek, id_length;
38
39 static size_t
40 on_read (void *ptr, size_t size, size_t nmemb, RbVorbisTagger *o)
41 {
42         struct RString *buf;
43         size_t total = size * nmemb;
44         VALUE tmp;
45
46         rb_str_resize (io_buf, size * nmemb);
47
48         tmp = rb_funcall (o->io, id_read, 2, LONG2NUM (total), io_buf);
49         if (NIL_P (tmp))
50                 return 0;
51
52         buf = RSTRING (tmp);
53         memcpy (ptr, buf->ptr, buf->len);
54
55         return buf->len;
56 }
57
58 static size_t
59 on_write (const void *ptr, size_t size, size_t nmemb, RbVorbisTagger *o)
60 {
61         size_t total = size * nmemb;
62
63         rb_str_resize (io_buf, total);
64         memcpy (RSTRING (io_buf)->ptr, ptr, total);
65
66         return NUM2LONG (rb_io_write (o->io, io_buf));
67 }
68
69 static void
70 c_mark (RbVorbisTagger *o)
71 {
72         rb_gc_mark (o->io);
73         rb_gc_mark (o->comments);
74         rb_gc_mark (io_buf);
75 }
76
77 static void
78 c_free (RbVorbisTagger *o)
79 {
80         /* just in case the user forgot to call #close himself */
81         if (o->state)
82                 vcedit_state_unref (o->state);
83
84         ruby_xfree (o);
85 }
86
87 static VALUE
88 c_alloc (VALUE klass)
89 {
90         RbVorbisTagger *o;
91
92         return Data_Make_Struct (klass, RbVorbisTagger, c_mark, c_free, o);
93 }
94
95 /*
96  * call-seq:
97  *  Ogg::Vorbis::Tagger.open(arg)                    -> object
98  *  Ogg::Vorbis::Tagger.open(arg) { |object| block } -> nil
99  *
100  * If a block isn't specified, Ogg::Vorbis::Tagger.open is a synonym
101  * for Ogg::Vorbis::Tagger.new.
102  * If a block is given, it will be invoked with the
103  * Ogg::Vorbis::Tagger object as a parameter, and the file or IO object
104  * will be automatically closed when the block terminates.
105  * The method always returns +nil+ in this case.
106  */
107 static VALUE
108 c_open (VALUE klass, VALUE arg)
109 {
110         VALUE obj = rb_class_new_instance (1, &arg, klass);
111
112         if (rb_block_given_p ())
113                 return rb_ensure (rb_yield, obj, c_close, obj);
114         else
115                 return obj;
116 }
117
118 /*
119  * call-seq:
120  *  Ogg::Vorbis::Tagger.new(arg) -> object
121  *
122  * Returns a new Ogg::Vorbis::Tagger object for the specified argument.
123  * *arg* can either be an IO object or a filename.
124  *
125  * FIXME: add optional mode argument (read-only or read-write)
126  */
127 static VALUE
128 c_init (VALUE self, VALUE io)
129 {
130         RbVorbisTagger *o;
131         vorbis_comment *vc;
132         int s, i;
133
134         Data_Get_Struct (self, RbVorbisTagger, o);
135
136         /* is this actually an IO object or a filename? */
137         if (rb_respond_to (io, id_read) &&
138             rb_respond_to (io, id_write) &&
139             rb_respond_to (io, id_seek))
140                 o->need_close = false;
141         else if (!NIL_P (rb_check_string_type (io))) {
142                 io = rb_file_open (StringValuePtr (io), "rb+");
143                 o->need_close = true;
144         } else
145                 rb_raise (rb_eArgError, "invalid argument");
146
147         o->io = io;
148
149         o->state = vcedit_state_new ();
150         if (!o->state)
151                 rb_raise (eVTError, "vcedit_new_state() failed - %s",
152                           vcedit_error (o->state));
153
154         s = vcedit_open_callbacks (o->state, o,
155                                    (vcedit_read_func) on_read,
156                                    (vcedit_write_func) on_write);
157         if (s < 0)
158                 rb_raise (eVTError, "vcedit_open_callbacks() failed - %s",
159                           vcedit_error (o->state));
160
161         vc = vcedit_comments (o->state);
162         if (!vc)
163                 rb_raise (eVTError, "vcedit_comments() failed - %s",
164                           vcedit_error (o->state));
165
166         /* check whether all comments are well-formed */
167         for (i = 0; i < vc->comments; i++) {
168                 char *ptr, *content = vc->user_comments[i];
169
170                 ptr = strchr (content, '=');
171                 if (!ptr || ptr == content)
172                         rb_raise (eVTError, "malformed comment - %s", content);
173         }
174
175         o->comments = rb_class_new_instance (0, NULL, cComments);
176
177         comments_init (o->comments, o->state);
178
179         return self;
180 }
181
182 /*
183  * call-seq:
184  *  object.close -> object
185  *
186  * Closes *object* and returns it.
187  */
188 static VALUE
189 c_close (VALUE self)
190 {
191         RbVorbisTagger *o;
192
193         Data_Get_Struct (self, RbVorbisTagger, o);
194
195         vcedit_state_unref (o->state);
196         o->state = NULL;
197
198         if (o->need_close)
199                 rb_io_close (o->io);
200
201         return self;
202 }
203
204 /*
205  * call-seq:
206  *  object.write -> integer
207  *
208  * Writes the comments from *object* back to the IO object and
209  * returns the numbers of comments written.
210  */
211 static VALUE
212 c_write (VALUE self)
213 {
214         RbVorbisTagger *o;
215         int s;
216
217         Data_Get_Struct (self, RbVorbisTagger, o);
218
219         comments_sync (o->comments);
220
221         /* seek back to BOF */
222         rb_funcall (o->io, id_seek, 1, INT2FIX (0));
223
224         s = vcedit_write (o->state, o);
225         if (s < 0)
226                 rb_raise (rb_eIOError, "write failed - %s",
227                           vcedit_error (o->state));
228
229         return rb_funcall (o->comments, id_length, 0);
230 }
231
232 /*
233  * call-seq:
234  *  object.comments -> comments
235  *
236  * Returns the comments collection of *object*, which is an instance of
237  * Ogg::Vorbis::Comments.
238  */
239 static VALUE
240 c_comments (VALUE self)
241 {
242         RbVorbisTagger *o;
243
244         Data_Get_Struct (self, RbVorbisTagger, o);
245
246         return o->comments;
247 }
248
249 EXT_API
250 void
251 Init_vorbistagger_ext (void)
252 {
253         VALUE mOgg, mVorbis, eOgg, eVorbis, cVT;
254
255         mOgg = rb_define_module ("Ogg");
256         mVorbis = rb_define_module_under (mOgg, "Vorbis");
257
258         eOgg = rb_define_class_under (mOgg, "OggError", rb_eStandardError);
259         eVorbis = rb_define_class_under (mVorbis, "VorbisError", eOgg);
260
261         cVT = rb_define_class_under (mVorbis, "Tagger", rb_cObject);
262
263         rb_define_alloc_func (cVT, c_alloc);
264
265         rb_define_singleton_method (cVT, "open", c_open, 1);
266         rb_define_method (cVT, "initialize", c_init, 1);
267         rb_define_method (cVT, "close", c_close, 0);
268         rb_define_method (cVT, "write", c_write, 0);
269         rb_define_method (cVT, "comments", c_comments, 0);
270
271         eVTError = rb_define_class_under (cVT, "TaggerError", eVorbis);
272
273         id_read = rb_intern ("read");
274         id_write = rb_intern ("write");
275         id_seek = rb_intern ("seek");
276         id_length = rb_intern ("length");
277
278         cComments = Init_Comments (mVorbis);
279
280         io_buf = rb_str_buf_new (0);
281         rb_global_variable (&io_buf);
282 }