Freeze the key-value pair arrays in Ogg::Vorbis::Comments.
[ruby-vorbistagger.git] / ext / comments.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 #include <ctype.h>
22 #include <assert.h>
23
24 #include "vcedit.h"
25
26 typedef struct {
27         vcedit_state *state;
28
29         VALUE items;
30 } RbVorbisComments;
31
32 static ID id_casecmp, id_replace, id_compare;
33
34 void
35 comments_init (VALUE self, vcedit_state *state)
36 {
37         RbVorbisComments *o;
38         vorbis_comment *vc;
39         int i;
40
41         Data_Get_Struct (self, RbVorbisComments, o);
42
43         o->state = state;
44         vcedit_state_ref (state);
45
46         vc = vcedit_comments (o->state);
47
48         o->items = rb_ary_new2 (vc->comments);
49
50         for (i = 0; i < vc->comments; i++) {
51                 VALUE k, v, pair;
52                 char *ptr, *content = vc->user_comments[i];
53
54                 ptr = strchr (content, '=');
55                 assert (ptr);
56
57                 k = rb_str_new (content, ptr - content);
58                 OBJ_FREEZE (k);
59
60                 v = rb_str_new2 (ptr + 1);
61
62                 pair = rb_ary_new3 (2, k, v);
63                 OBJ_FREEZE (pair);
64
65                 rb_ary_store (o->items, i, pair);
66         }
67
68         rb_iv_set (self, "@items", o->items);
69 }
70
71 void
72 comments_sync (VALUE self)
73 {
74         RbVorbisComments *o;
75         vorbis_comment *vc;
76         struct RArray *items;
77         int i;
78
79         Data_Get_Struct (self, RbVorbisComments, o);
80
81         vc = vcedit_comments (o->state);
82
83         vorbis_comment_clear (vc);
84         vorbis_comment_init (vc);
85
86         items = RARRAY (o->items);
87
88         for (i = 0; i < items->len; i++) {
89                 struct RArray *pair = RARRAY (items->ptr[i]);
90
91                 vorbis_comment_add_tag (vc,
92                                         StringValuePtr (pair->ptr[0]),
93                                         StringValuePtr (pair->ptr[1]));
94         }
95 }
96
97 static void
98 c_mark (RbVorbisComments *o)
99 {
100         rb_gc_mark (o->items);
101 }
102
103 static void
104 c_free (RbVorbisComments *o)
105 {
106         vcedit_state_unref (o->state);
107
108         ruby_xfree (o);
109 }
110
111 static VALUE
112 c_alloc (VALUE klass)
113 {
114         RbVorbisComments *o;
115
116         return Data_Make_Struct (klass, RbVorbisComments, c_mark, c_free, o);
117 }
118
119 /*
120  * call-seq:
121  *  object.inspect -> string
122  *
123  * Returns the contents of *object* as a string.
124  */
125 static VALUE
126 c_inspect (VALUE self)
127 {
128         VALUE ret;
129         RbVorbisComments *o;
130         struct RArray *items;
131         int i;
132
133         Data_Get_Struct (self, RbVorbisComments, o);
134
135         items = RARRAY (o->items);
136
137         ret = rb_str_buf_new (128);
138         rb_str_buf_cat (ret, "{", 1);
139
140         for (i = 0; i < items->len; i++) {
141                 struct RArray *pair = RARRAY (items->ptr[i]);
142
143                 if (i)
144                         rb_str_buf_cat (ret, ", ", 2);
145
146                 rb_str_buf_append (ret, rb_inspect (pair->ptr[0]));
147                 rb_str_buf_cat (ret, "=>", 2);
148                 rb_str_buf_append (ret, rb_inspect (pair->ptr[1]));
149         }
150
151         rb_str_buf_cat (ret, "}", 1);
152
153         return ret;
154 }
155
156 /*
157  * call-seq:
158  *  object.clear -> object
159  *
160  * Removes all elements from *object* and returns it.
161  */
162 static VALUE
163 c_clear (VALUE self)
164 {
165         RbVorbisComments *o;
166
167         Data_Get_Struct (self, RbVorbisComments, o);
168
169         rb_ary_clear (o->items);
170
171         return self;
172 }
173
174 /*
175  * call-seq:
176  *  object.delete(key) -> string or nil
177  *
178  * If a tag with the specified key exists, that tag is deleted and
179  * the tag's value is returned. Otherwise, +nil+ is returned.
180  */
181 static VALUE
182 c_delete (VALUE self, VALUE key)
183 {
184         VALUE ret = Qnil;
185         RbVorbisComments *o;
186         struct RArray *items;
187         int i, pos = -1;
188
189         Data_Get_Struct (self, RbVorbisComments, o);
190
191         items = RARRAY (o->items);
192
193         for (i = 0; i < items->len; i++) {
194                 struct RArray *pair = RARRAY (items->ptr[i]);
195                 VALUE tmp;
196
197                 tmp = rb_funcall (pair->ptr[0], id_casecmp, 1, key);
198                 if (tmp == INT2FIX (0)) {
199                         ret = pair->ptr[1];
200                         pos = i;
201                         break;
202                 }
203         }
204
205         if (pos != -1)
206                 rb_ary_delete_at (o->items, pos);
207
208         return ret;
209 }
210
211 /*
212  * call-seq:
213  *  object.keys -> array
214  *
215  * Returns an array that contains the keys of the tags in *object*.
216  */
217 static VALUE
218 c_keys (VALUE self)
219 {
220         VALUE ret;
221         RbVorbisComments *o;
222         struct RArray *items;
223         int i;
224
225         Data_Get_Struct (self, RbVorbisComments, o);
226
227         items = RARRAY (o->items);
228         ret = rb_ary_new2 (items->len);
229
230         for (i = 0; i < items->len; i++) {
231                 struct RArray *pair = RARRAY (items->ptr[i]);
232
233                 rb_ary_store (ret, i, pair->ptr[0]);
234         }
235
236         return ret;
237 }
238
239 /*
240  * call-seq:
241  *  object.values -> array
242  *
243  * Returns an array that contains the values of the tags in *object*.
244  */
245 static VALUE
246 c_values (VALUE self)
247 {
248         VALUE ret;
249         RbVorbisComments *o;
250         struct RArray *items;
251         int i;
252
253         Data_Get_Struct (self, RbVorbisComments, o);
254
255         items = RARRAY (o->items);
256         ret = rb_ary_new2 (items->len);
257
258         for (i = 0; i < items->len; i++) {
259                 struct RArray *pair = RARRAY (items->ptr[i]);
260
261                 rb_ary_store (ret, i, pair->ptr[1]);
262         }
263
264         return ret;
265 }
266
267 /*
268  * call-seq:
269  *  object.length -> integer
270  *
271  * Returns the number of tags in *object*.
272  */
273 static VALUE
274 c_length (VALUE self)
275 {
276         RbVorbisComments *o;
277
278         Data_Get_Struct (self, RbVorbisComments, o);
279
280         return LONG2NUM (RARRAY (o->items)->len);
281 }
282
283 /*
284  * call-seq:
285  *  object.empty? -> true or false
286  *
287  * Returns true if *object* is empty or false otherwise.
288  */
289 static VALUE
290 c_get_empty (VALUE self)
291 {
292         RbVorbisComments *o;
293
294         Data_Get_Struct (self, RbVorbisComments, o);
295
296         return !RARRAY(o->items)->len;
297 }
298
299 /*
300  * call-seq:
301  *  object[key] -> string
302  *
303  * Returns the value of the tag with the key *key* or +nil+ if the
304  * tag cannot be found.
305  */
306 static VALUE
307 c_aref (VALUE self, VALUE key)
308 {
309         RbVorbisComments *o;
310         struct RArray *items;
311         int i;
312
313         Data_Get_Struct (self, RbVorbisComments, o);
314
315         items = RARRAY (o->items);
316
317         for (i = 0; i < items->len; i++) {
318                 struct RArray *pair = RARRAY (items->ptr[i]);
319                 VALUE tmp;
320
321                 tmp = rb_funcall (pair->ptr[0], id_casecmp, 1, key);
322                 if (tmp == INT2FIX (0))
323                         return pair->ptr[1];
324         }
325
326         return Qnil;
327 }
328
329 /*
330  * call-seq:
331  *  object[key] = string
332  *
333  * Sets the value of the tag with the key *key* to *string*.
334  */
335 static VALUE
336 c_aset (VALUE self, VALUE key, VALUE value)
337 {
338         VALUE tmp;
339         RbVorbisComments *o;
340         struct RArray *items;
341         int i;
342
343         Data_Get_Struct (self, RbVorbisComments, o);
344
345         items = RARRAY (o->items);
346
347         for (i = 0; i < items->len; i++) {
348                 struct RArray *pair = RARRAY (items->ptr[i]);
349                 VALUE tmp;
350
351                 tmp = rb_funcall (pair->ptr[0], id_casecmp, 1, key);
352                 if (tmp == INT2FIX (0)) {
353                         rb_funcall (pair->ptr[1], id_replace, 1, value);
354                         return pair->ptr[1];
355                 }
356         }
357
358         tmp = rb_ary_new3 (2, key, value);
359         OBJ_FREEZE (tmp);
360
361         rb_ary_push (o->items, tmp);
362
363         return value;
364 }
365
366 /*
367  * call-seq:
368  *  object.has_key?(key) -> true or false
369  *
370  * Returns true if a tag exists with the specified key or false
371  * otherwise.
372  */
373 static VALUE
374 c_has_key (VALUE self, VALUE key)
375 {
376         RbVorbisComments *o;
377         struct RArray *items;
378         int i;
379
380         Data_Get_Struct (self, RbVorbisComments, o);
381
382         items = RARRAY (o->items);
383
384         for (i = 0; i < items->len; i++) {
385                 struct RArray *pair = RARRAY (items->ptr[i]);
386                 VALUE tmp;
387
388                 tmp = rb_funcall (pair->ptr[0], id_casecmp, 1, key);
389                 if (tmp == INT2FIX (0))
390                         return Qtrue;
391         }
392
393         return Qfalse;
394 }
395
396 /*
397  * call-seq:
398  *  object <=> other -> -1, 0 or 1
399  *
400  * Compares *object* to *other* and returns -1, 0 or 1 if
401  * *object* is less than, equal or greater than *other*.
402  */
403 static VALUE
404 c_compare (VALUE self, VALUE other)
405 {
406         RbVorbisComments *o, *o2;
407         struct RArray *a, *b;
408         int i, j;
409
410         if (rb_obj_is_kind_of (other, CLASS_OF (self)) != Qtrue)
411                 rb_raise (rb_eArgError, "invalid argument");
412
413         Data_Get_Struct (self, RbVorbisComments, o);
414         Data_Get_Struct (other, RbVorbisComments, o2);
415
416         a = RARRAY (o->items);
417         b = RARRAY (o2->items);
418
419         if (a->len < b->len)
420                 return -1;
421
422         if (b->len < a->len)
423                 return 1;
424
425         for (i = 0; i < a->len; i++) {
426                 struct RArray *aa = RARRAY (a->ptr[i]);
427                 struct RArray *bb = RARRAY (b->ptr[i]);
428
429                 for (j = 0; j < 2; j++) {
430                         VALUE tmp;
431
432                         tmp = rb_funcall (aa->ptr[j], id_compare, 1, bb->ptr[j]);
433                         if (FIX2INT (tmp) != 0)
434                                 return tmp;
435                 }
436         }
437
438         return INT2FIX (0);
439 }
440
441 /*
442  * call-seq:
443  *  object.each { |key, value| block } -> object
444  *
445  * Calls _block_ once for each tag in *object*, passing the key and
446  * value of the tag.
447  * Returns *object*.
448  */
449 static VALUE
450 c_each (VALUE self)
451 {
452         RbVorbisComments *o;
453         struct RArray *items;
454         int i;
455
456         Data_Get_Struct (self, RbVorbisComments, o);
457
458         items = RARRAY (o->items);
459
460         for (i = 0; i < items->len; i++) {
461                 struct RArray *pair = RARRAY (items->ptr[i]);
462
463                 rb_yield_values (2, pair->ptr[0], pair->ptr[1]);
464         }
465
466         return self;
467 }
468
469 /*
470  * call-seq:
471  *  object.each_key { |key| block } -> object
472  *
473  * Calls _block_ once for each tag in *object*, passing the key
474  * of the tag.
475  * Returns *object*.
476  */
477 static VALUE
478 c_each_key (VALUE self)
479 {
480         RbVorbisComments *o;
481         struct RArray *items;
482         int i;
483
484         Data_Get_Struct (self, RbVorbisComments, o);
485
486         items = RARRAY (o->items);
487
488         for (i = 0; i < items->len; i++) {
489                 struct RArray *pair = RARRAY (items->ptr[i]);
490
491                 rb_yield (pair->ptr[0]);
492         }
493
494         return self;
495 }
496
497 /*
498  * call-seq:
499  *  object.each_value { |value| block } -> object
500  *
501  * Calls _block_ once for each tag in *object*, passing the value
502  * of the tag. Returns *object*.
503  */
504 static VALUE
505 c_each_value (VALUE self)
506 {
507         RbVorbisComments *o;
508         struct RArray *items;
509         int i;
510
511         Data_Get_Struct (self, RbVorbisComments, o);
512
513         items = RARRAY (o->items);
514
515         for (i = 0; i < items->len; i++) {
516                 struct RArray *pair = RARRAY (items->ptr[i]);
517
518                 rb_yield (pair->ptr[1]);
519         }
520
521         return self;
522 }
523
524 VALUE
525 Init_Comments (VALUE mVorbis)
526 {
527         VALUE c;
528
529         c = rb_define_class_under (mVorbis, "Comments", rb_cObject);
530
531         rb_define_alloc_func (c, c_alloc);
532
533         rb_define_method (c, "inspect", c_inspect, 0);
534         rb_define_method (c, "clear", c_clear, 0);
535         rb_define_method (c, "delete", c_delete, 1);
536         rb_define_method (c, "length", c_length, 0);
537         rb_define_method (c, "has_key?", c_has_key, 1);
538         rb_define_method (c, "[]", c_aref, 1);
539         rb_define_method (c, "[]=", c_aset, 2);
540         rb_define_method (c, "empty?", c_get_empty, 0);
541         rb_define_method (c, "keys", c_keys, 0);
542         rb_define_method (c, "values", c_values, 0);
543
544         rb_include_module (c, rb_mComparable);
545         rb_define_method (c, "<=>", c_compare, 1);
546
547         rb_include_module (c, rb_mEnumerable);
548         rb_define_method (c, "each", c_each, 0);
549         rb_define_method (c, "each_key", c_each_key, 0);
550         rb_define_method (c, "each_value", c_each_value, 0);
551
552         rb_define_alias (c, "size", "length");
553         rb_define_alias (c, "each_pair", "each");
554
555         id_casecmp = rb_intern ("casecmp");
556         id_replace = rb_intern ("replace");
557         id_compare = rb_intern ("<=>");
558
559         return c;
560 }