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