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