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