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