23967289b5e20ad56222171dafb9279147b84ee7
[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;
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                 rb_ary_store (o->items, i,
63                               rb_ary_new3 (2, k, v));
64         }
65
66         rb_iv_set (self, "@items", o->items);
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         RbVorbisComments *o;
337         struct RArray *items;
338         int i;
339
340         Data_Get_Struct (self, RbVorbisComments, o);
341
342         items = RARRAY (o->items);
343
344         for (i = 0; i < items->len; i++) {
345                 struct RArray *pair = RARRAY (items->ptr[i]);
346                 VALUE tmp;
347
348                 tmp = rb_funcall (pair->ptr[0], id_casecmp, 1, key);
349                 if (tmp == INT2FIX (0)) {
350                         rb_funcall (pair->ptr[1], id_replace, 1, value);
351                         return pair->ptr[1];
352                 }
353         }
354
355         rb_ary_push (o->items, rb_ary_new3 (2, key, value));
356
357         return value;
358 }
359
360 /*
361  * call-seq:
362  *  object.has_key?(key) -> true or false
363  *
364  * Returns true if a tag exists with the specified key or false
365  * otherwise.
366  */
367 static VALUE
368 c_has_key (VALUE self, VALUE key)
369 {
370         RbVorbisComments *o;
371         struct RArray *items;
372         int i;
373
374         Data_Get_Struct (self, RbVorbisComments, o);
375
376         items = RARRAY (o->items);
377
378         for (i = 0; i < items->len; i++) {
379                 struct RArray *pair = RARRAY (items->ptr[i]);
380                 VALUE tmp;
381
382                 tmp = rb_funcall (pair->ptr[0], id_casecmp, 1, key);
383                 if (tmp == INT2FIX (0))
384                         return Qtrue;
385         }
386
387         return Qfalse;
388 }
389
390 /*
391  * call-seq:
392  *  object <=> other -> -1, 0 or 1
393  *
394  * Compares *object* to *other* and returns -1, 0 or 1 if
395  * *object* is less than, equal or greater than *other*.
396  */
397 static VALUE
398 c_compare (VALUE self, VALUE other)
399 {
400         RbVorbisComments *o, *o2;
401         struct RArray *a, *b;
402         int i, j;
403
404         if (rb_obj_is_kind_of (other, CLASS_OF (self)) != Qtrue)
405                 rb_raise (rb_eArgError, "invalid argument");
406
407         Data_Get_Struct (self, RbVorbisComments, o);
408         Data_Get_Struct (other, RbVorbisComments, o2);
409
410         a = RARRAY (o->items);
411         b = RARRAY (o2->items);
412
413         if (a->len < b->len)
414                 return -1;
415
416         if (b->len < a->len)
417                 return 1;
418
419         for (i = 0; i < a->len; i++) {
420                 struct RArray *aa = RARRAY (a->ptr[i]);
421                 struct RArray *bb = RARRAY (b->ptr[i]);
422
423                 for (j = 0; j < 2; j++) {
424                         VALUE tmp;
425
426                         tmp = rb_funcall (aa->ptr[j], id_compare, 1, bb->ptr[j]);
427                         if (FIX2INT (tmp) != 0)
428                                 return tmp;
429                 }
430         }
431
432         return INT2FIX (0);
433 }
434
435 /*
436  * call-seq:
437  *  object.each { |key, value| block } -> object
438  *
439  * Calls _block_ once for each tag in *object*, passing the key and
440  * value of the tag.
441  * Returns *object*.
442  */
443 static VALUE
444 c_each (VALUE self)
445 {
446         RbVorbisComments *o;
447         struct RArray *items;
448         int i;
449
450         Data_Get_Struct (self, RbVorbisComments, o);
451
452         items = RARRAY (o->items);
453
454         for (i = 0; i < items->len; i++) {
455                 struct RArray *pair = RARRAY (items->ptr[i]);
456
457                 rb_yield_values (2, pair->ptr[0], pair->ptr[1]);
458         }
459
460         return self;
461 }
462
463 /*
464  * call-seq:
465  *  object.each_key { |key| block } -> object
466  *
467  * Calls _block_ once for each tag in *object*, passing the key
468  * of the tag.
469  * Returns *object*.
470  */
471 static VALUE
472 c_each_key (VALUE self)
473 {
474         RbVorbisComments *o;
475         struct RArray *items;
476         int i;
477
478         Data_Get_Struct (self, RbVorbisComments, o);
479
480         items = RARRAY (o->items);
481
482         for (i = 0; i < items->len; i++) {
483                 struct RArray *pair = RARRAY (items->ptr[i]);
484
485                 rb_yield (pair->ptr[0]);
486         }
487
488         return self;
489 }
490
491 /*
492  * call-seq:
493  *  object.each_value { |value| block } -> object
494  *
495  * Calls _block_ once for each tag in *object*, passing the value
496  * of the tag. Returns *object*.
497  */
498 static VALUE
499 c_each_value (VALUE self)
500 {
501         RbVorbisComments *o;
502         struct RArray *items;
503         int i;
504
505         Data_Get_Struct (self, RbVorbisComments, o);
506
507         items = RARRAY (o->items);
508
509         for (i = 0; i < items->len; i++) {
510                 struct RArray *pair = RARRAY (items->ptr[i]);
511
512                 rb_yield (pair->ptr[1]);
513         }
514
515         return self;
516 }
517
518 VALUE
519 Init_Comments (VALUE mVorbis)
520 {
521         VALUE c;
522
523         c = rb_define_class_under (mVorbis, "Comments", rb_cObject);
524
525         rb_define_alloc_func (c, c_alloc);
526
527         rb_define_method (c, "inspect", c_inspect, 0);
528         rb_define_method (c, "clear", c_clear, 0);
529         rb_define_method (c, "delete", c_delete, 1);
530         rb_define_method (c, "length", c_length, 0);
531         rb_define_method (c, "has_key?", c_has_key, 1);
532         rb_define_method (c, "[]", c_aref, 1);
533         rb_define_method (c, "[]=", c_aset, 2);
534         rb_define_method (c, "empty?", c_get_empty, 0);
535         rb_define_method (c, "keys", c_keys, 0);
536         rb_define_method (c, "values", c_values, 0);
537
538         rb_include_module (c, rb_mComparable);
539         rb_define_method (c, "<=>", c_compare, 1);
540
541         rb_include_module (c, rb_mEnumerable);
542         rb_define_method (c, "each", c_each, 0);
543         rb_define_method (c, "each_key", c_each_key, 0);
544         rb_define_method (c, "each_value", c_each_value, 0);
545
546         rb_define_alias (c, "size", "length");
547         rb_define_alias (c, "each_pair", "each");
548
549         id_casecmp = rb_intern ("casecmp");
550         id_replace = rb_intern ("replace");
551         id_compare = rb_intern ("<=>");
552
553         return c;
554 }