Reworked error handling.
[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 typedef struct {
29         vcedit_state *state;
30         VALUE comments;
31 } RbVorbisTagger;
32
33 static VALUE c_close (VALUE self);
34
35 static VALUE cComments, eVT, eOpen, eInvalidData, eInvalidComment,
36              eTempFile, eReopen;
37 static ID id_length;
38
39 static void
40 c_free (RbVorbisTagger *o)
41 {
42         /* just in case the user forgot to call #close himself */
43         if (o->state)
44                 vcedit_state_unref (o->state);
45
46         ruby_xfree (o);
47 }
48
49 static void
50 c_mark (RbVorbisTagger *o)
51 {
52         rb_gc_mark (o->comments);
53 }
54
55 static VALUE
56 c_alloc (VALUE klass)
57 {
58         RbVorbisTagger *o;
59
60         return Data_Make_Struct (klass, RbVorbisTagger, c_mark, c_free, o);
61 }
62
63 /*
64  * call-seq:
65  *  Ogg::Vorbis::Tagger.open(arg)                    -> object
66  *  Ogg::Vorbis::Tagger.open(arg) { |object| block } -> object
67  *
68  * If a block isn't specified, Ogg::Vorbis::Tagger.open is a synonym
69  * for Ogg::Vorbis::Tagger.new.
70  * If a block is given, it will be invoked with the * Ogg::Vorbis::Tagger
71  * object as a parameter.
72  */
73 static VALUE
74 c_open (VALUE klass, VALUE arg)
75 {
76         VALUE obj = rb_class_new_instance (1, &arg, klass);
77
78         if (rb_block_given_p ())
79                 return rb_ensure (rb_yield, obj, c_close, obj);
80         else
81                 return obj;
82 }
83
84 /*
85  * call-seq:
86  *  Ogg::Vorbis::Tagger.new(filename) -> object
87  *
88  * Returns a new Ogg::Vorbis::Tagger object for the specified file.
89  *
90  * FIXME: add optional mode argument (read-only or read-write)
91  */
92 static VALUE
93 c_init (VALUE self, VALUE filename)
94 {
95         RbVorbisTagger *o;
96         vorbis_comment *vc;
97         int i;
98
99         Data_Get_Struct (self, RbVorbisTagger, o);
100
101         StringValue (filename);
102
103         o->state = vcedit_state_new (StringValuePtr (filename));
104         if (!o->state)
105                 rb_raise (rb_eNoMemError, "Out of Memory");
106
107         switch (vcedit_open (o->state)) {
108                 case VCEDIT_ERR_OPEN:
109                         rb_raise (eOpen, "Cannot open file");
110                 case VCEDIT_ERR_INVAL:
111                         rb_raise (eInvalidData, "Invalid data");
112                 default:
113                         break;
114         }
115
116         vc = vcedit_comments (o->state);
117
118         /* vcedit_open() succeeded, so vcedit_comments() cannot
119          * return NULL.
120          */
121         assert (vc);
122
123         /* check whether all comments are well-formed */
124         for (i = 0; i < vc->comments; i++) {
125                 char *ptr, *content = vc->user_comments[i];
126
127                 ptr = strchr (content, '=');
128                 if (!ptr || ptr == content)
129                         rb_raise (eInvalidComment, "invalid comment - %s", content);
130         }
131
132         o->comments = rb_class_new_instance (0, NULL, cComments);
133
134         comments_init (o->comments, o->state);
135
136         return self;
137 }
138
139 /*
140  * call-seq:
141  *  object.close -> object
142  *
143  * Closes *object* and returns it.
144  */
145 static VALUE
146 c_close (VALUE self)
147 {
148         RbVorbisTagger *o;
149
150         Data_Get_Struct (self, RbVorbisTagger, o);
151
152         vcedit_state_unref (o->state);
153         o->state = NULL;
154
155         return self;
156 }
157
158 /*
159  * call-seq:
160  *  object.write -> integer
161  *
162  * Writes the comments from *object* back to the IO object and
163  * returns the numbers of comments written.
164  */
165 static VALUE
166 c_write (VALUE self)
167 {
168         RbVorbisTagger *o;
169
170         Data_Get_Struct (self, RbVorbisTagger, o);
171
172         comments_sync (o->comments, o->state);
173
174         switch (vcedit_write (o->state)) {
175                 case VCEDIT_ERR_INVAL:
176                         rb_raise (eInvalidData, "Invalid data");
177                 case VCEDIT_ERR_TMPFILE:
178                         rb_raise (eTempFile, "Cannot create temporary file");
179                 case VCEDIT_ERR_REOPEN:
180                         rb_raise (eReopen, "Cannot reopen file");
181                 default:
182                         break;
183         }
184
185         return rb_funcall (o->comments, id_length, 0);
186 }
187
188 /*
189  * call-seq:
190  *  object.comments -> comments
191  *
192  * Returns the comments collection of *object*, which is an instance of
193  * Ogg::Vorbis::Comments.
194  */
195 static VALUE
196 c_comments (VALUE self)
197 {
198         RbVorbisTagger *o;
199
200         Data_Get_Struct (self, RbVorbisTagger, o);
201
202         return o->comments;
203 }
204
205 EXT_API
206 void
207 Init_vorbistagger_ext (void)
208 {
209         VALUE mOgg, mVorbis, eOgg, eVorbis, cVT;
210
211         mOgg = rb_define_module ("Ogg");
212         mVorbis = rb_define_module_under (mOgg, "Vorbis");
213
214         eOgg = rb_define_class_under (mOgg, "OggError", rb_eStandardError);
215         eVorbis = rb_define_class_under (mVorbis, "VorbisError", eOgg);
216
217         cVT = rb_define_class_under (mVorbis, "Tagger", rb_cObject);
218
219         rb_define_alloc_func (cVT, c_alloc);
220
221         rb_define_singleton_method (cVT, "open", c_open, 1);
222         rb_define_method (cVT, "initialize", c_init, 1);
223         rb_define_method (cVT, "close", c_close, 0);
224         rb_define_method (cVT, "write", c_write, 0);
225         rb_define_method (cVT, "comments", c_comments, 0);
226
227         eVT = rb_define_class_under (cVT, "TaggerError", eVorbis);
228         eOpen = rb_define_class_under (cVT, "OpenError", eVT);
229         eInvalidData = rb_define_class_under (cVT, "InvalidDataError", eVT);
230         eInvalidComment = rb_define_class_under (cVT, "InvalidCommentError",
231                                                  eInvalidData);
232         eTempFile = rb_define_class_under (cVT, "TempFileError", eVT);
233         eReopen = rb_define_class_under (cVT, "ReopenError", eVT);
234
235         id_length = rb_intern ("length");
236
237         cComments = Init_Comments (mVorbis);
238 }