Initial commit.
[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;
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         o->comments = rb_class_new_instance (0, NULL, cComments);
169
170         comments_init (o->comments, o->state);
171
172         return self;
173 }
174
175 /*
176  * call-seq:
177  *  object.close -> object
178  *
179  * Closes *object* and returns it.
180  */
181 static VALUE
182 c_close (VALUE self)
183 {
184         RbVorbisTagger *o;
185
186         Data_Get_Struct (self, RbVorbisTagger, o);
187
188         vcedit_state_unref (o->state);
189         o->state = NULL;
190
191         if (o->need_close)
192                 rb_io_close (o->io);
193
194         return self;
195 }
196
197 /*
198  * call-seq:
199  *  object.write -> integer
200  *
201  * Writes the comments from *object* back to the IO object and
202  * returns the numbers of comments written.
203  */
204 static VALUE
205 c_write (VALUE self)
206 {
207         RbVorbisTagger *o;
208         int s;
209
210         Data_Get_Struct (self, RbVorbisTagger, o);
211
212         comments_sync (o->comments);
213
214         /* seek back to BOF */
215         rb_funcall (o->io, id_seek, 1, INT2FIX (0));
216
217         s = vcedit_write (o->state, o);
218         if (s < 0)
219                 rb_raise (rb_eIOError, "write failed - %s",
220                           vcedit_error (o->state));
221
222         return rb_funcall (o->comments, id_length, 0);
223 }
224
225 /*
226  * call-seq:
227  *  object.comments -> comments
228  *
229  * Returns the comments collection of *object*, which is an instance of
230  * Ogg::Vorbis::Comments.
231  */
232 static VALUE
233 c_comments (VALUE self)
234 {
235         RbVorbisTagger *o;
236
237         Data_Get_Struct (self, RbVorbisTagger, o);
238
239         return o->comments;
240 }
241
242 EXT_API
243 void
244 Init_vorbistagger_ext (void)
245 {
246         VALUE mOgg, mVorbis, eOgg, eVorbis, cVT;
247
248         mOgg = rb_define_module ("Ogg");
249         mVorbis = rb_define_module_under (mOgg, "Vorbis");
250
251         eOgg = rb_define_class_under (mOgg, "OggError", rb_eStandardError);
252         eVorbis = rb_define_class_under (mVorbis, "VorbisError", eOgg);
253
254         cVT = rb_define_class_under (mVorbis, "Tagger", rb_cObject);
255
256         rb_define_alloc_func (cVT, c_alloc);
257
258         rb_define_singleton_method (cVT, "open", c_open, 1);
259         rb_define_method (cVT, "initialize", c_init, 1);
260         rb_define_method (cVT, "close", c_close, 0);
261         rb_define_method (cVT, "write", c_write, 0);
262         rb_define_method (cVT, "comments", c_comments, 0);
263
264         eVTError = rb_define_class_under (cVT, "TaggerError", eVorbis);
265
266         id_read = rb_intern ("read");
267         id_write = rb_intern ("write");
268         id_seek = rb_intern ("seek");
269         id_length = rb_intern ("length");
270
271         cComments = Init_Comments (mVorbis);
272 }