Fixed some warnings.
[ruby-eet.git] / ext / ext.c
1 /*
2  * $Id: ext.c 76 2006-05-14 13:51:45Z tilman $
3  *
4  * Copyright (c) 2005 Tilman Sauerbeck (tilman at code-monkey de)
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining
7  * a copy of this software and associated documentation files (the
8  * "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sublicense, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be
15  * included in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24  */
25
26 #include <Eet.h>
27 #include <ruby.h>
28 #include <st.h>
29 #include <locale.h>
30
31 #define CHECK_KEY(key) \
32         if (rb_funcall (key, id_include, 1, INT2FIX (0)) == Qtrue) \
33                 rb_raise (rb_eArgError, "key must not contain binary zeroes");
34
35 #define CHECK_CLOSED(ef) \
36         if (!*(ef)) \
37                 rb_raise (rb_eIOError, "closed stream");
38
39 #define CHECK_READABLE(ef) \
40         switch (eet_mode_get (*ef)) { \
41                 case EET_FILE_MODE_READ: \
42                 case EET_FILE_MODE_READ_WRITE: \
43                         break; \
44                 default: \
45                         rb_raise (rb_eIOError, "permission denied"); \
46         }
47
48 #ifdef WORDS_BIGENDIAN
49 # define BSWAP32(x) \
50         ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
51         (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
52 #else /* !WORDS_BIGENDIAN */
53 # define BSWAP32(x) (x)
54 #endif /* WORDS_BIGENDIAN */
55
56 static VALUE c_close (VALUE self);
57
58 static VALUE cStream, cChunk,
59              eEetError, eNameError, ePropError,
60              sym_lossy, sym_level, sym_quality, sym_char, sym_short,
61              sym_long_long, sym_double;
62 static ID id_include, id_to_s, id_keys, id_pack,
63           id_to_eet_chunks, id_to_eet_name, id_to_eet_properties,
64           id_tag, id_data;
65
66 static void
67 c_free (Eet_File **ef)
68 {
69         if (*ef) {
70                 eet_close (*ef);
71                 *ef = NULL;
72
73                 eet_shutdown ();
74         }
75
76         free (ef);
77 }
78
79 static VALUE
80 c_alloc (VALUE klass)
81 {
82         Eet_File **ef = NULL;
83
84         return Data_Make_Struct (klass, Eet_File *, NULL, c_free, ef);
85 }
86
87 /*
88  * call-seq:
89  *  Eet::File.open(file [, mode] )                -> ef or nil
90  *  Eet::File.open(file [, mode] ) { |ef| block } -> nil
91  *
92  * If a block isn't specified, Eet::File.open is a synonym for
93  * Eet::File.new.
94  * If a block is given, it will be invoked with the
95  * Eet::File object as a parameter, and the file will be
96  * automatically closed when the block terminates. The call always
97  * returns +nil+ in this case.
98  */
99 static VALUE
100 c_open (int argc, VALUE *argv, VALUE klass)
101 {
102         VALUE obj = rb_class_new_instance (argc, argv, klass);
103
104         if (rb_block_given_p ())
105                 return rb_ensure (rb_yield, obj, c_close, obj);
106         else
107                 return obj;
108 }
109
110 /*
111  * call-seq:
112  *  Eet::File.new(file [, mode] ) -> ef or nil
113  *
114  * Creates an Eet::File object for _file_.
115  *
116  * _file_ is opened with the specified mode (defaulting to "r").
117  * Possible values for _mode_ are "r" for read-only access,
118  * "w" for write-only access and "r+" for read/write access.
119  */
120 static VALUE
121 c_init (int argc, VALUE *argv, VALUE self)
122 {
123         VALUE file = Qnil, mode = Qnil;
124         Eet_File **ef = NULL;
125         Eet_File_Mode m = EET_FILE_MODE_READ;
126         const char *tmp, *cfile;
127
128         Data_Get_Struct (self, Eet_File *, ef);
129
130         rb_scan_args (argc, argv, "11", &file, &mode);
131
132         cfile = StringValuePtr (file);
133
134         if (!NIL_P (mode)) {
135                 tmp = StringValuePtr (mode);
136                 if (!strcmp (tmp, "r+"))
137                         m = EET_FILE_MODE_READ_WRITE;
138                 else if (!strcmp (tmp, "w"))
139                         m = EET_FILE_MODE_WRITE;
140                 else if (strcmp (tmp, "r"))
141                         rb_raise (rb_eArgError, "illegal access mode %s", tmp);
142         }
143
144         eet_init ();
145
146         *ef = eet_open (cfile, m);
147         if (!*ef) {
148                 switch (m) {
149                         case EET_FILE_MODE_READ_WRITE:
150                         case EET_FILE_MODE_WRITE:
151                                 tmp = "Permission denied - %s";
152                                 break;
153                         default:
154                                 tmp = "File not found - %s";
155                                 break;
156                 }
157
158                 rb_raise (rb_eRuntimeError, tmp, cfile);
159         }
160
161         return self;
162 }
163
164 /*
165  * call-seq:
166  *  ef.close -> ef
167  *
168  * Closes _ef_ and flushes any pending writes.
169  * _ef_ is unavailable for any further data operations;
170  * an +IOError+ is raised if such an attempt is made.
171  *
172  * Eet::File objects are automatically closed when they
173  * are claimed by the garbage collector.
174  */
175 static VALUE
176 c_close (VALUE self)
177 {
178         Eet_File **ef = NULL;
179
180         Data_Get_Struct (self, Eet_File *, ef);
181         CHECK_CLOSED (ef);
182
183         eet_close (*ef);
184         *ef = NULL;
185
186         eet_shutdown ();
187
188         return self;
189 }
190
191 static VALUE
192 get_keys (Eet_File *ef, char *glob)
193 {
194         VALUE ret;
195         char **keys;
196         int i, count = 0;
197
198         keys = eet_list (ef, glob, &count);
199         ret = rb_ary_new2 (count);
200
201         for (i = 0; i < count; i++)
202                 rb_ary_store (ret, i, rb_str_new2 (keys[i]));
203
204         free (keys);
205
206         return ret;
207 }
208
209 /*
210  * call-seq:
211  *  ef.entries -> array
212  *
213  * Returns an Array with the keys of the entries in _ef_.
214  * If the keys cannot be retrieved, an +IOError+ is raised.
215  */
216 static VALUE
217 c_entries (VALUE self)
218 {
219         Eet_File **ef = NULL;
220
221         Data_Get_Struct (self, Eet_File *, ef);
222         CHECK_CLOSED (ef);
223         CHECK_READABLE (ef);
224
225         return get_keys (*ef, "*");
226 }
227
228 /*
229  * call-seq:
230  *  ef[glob] -> array
231  *
232  * Returns an Array with the keys of entries in _ef_ that match the
233  * shell glob _glob_.
234  * If the keys cannot be retrieved, an +IOError+ is raised.
235  */
236 static VALUE
237 c_glob (VALUE self, VALUE glob)
238 {
239         Eet_File **ef = NULL;
240
241         Data_Get_Struct (self, Eet_File *, ef);
242         CHECK_CLOSED (ef);
243         CHECK_READABLE (ef);
244
245         return get_keys (*ef, StringValuePtr (glob));
246 }
247
248 /*
249  * call-seq:
250  *  ef.delete(key) -> ef
251  *
252  * Deletes the entry from _ef_ that is stored as _key_.
253  * If the entry cannot be deleted, an +IOError+ is raised,
254  * otherwise _ef_ is returned.
255  */
256 static VALUE
257 c_delete (VALUE self, VALUE key)
258 {
259         Eet_File **ef = NULL;
260         char *ckey;
261
262         Data_Get_Struct (self, Eet_File *, ef);
263         CHECK_CLOSED (ef);
264
265         ckey = StringValuePtr (key);
266         CHECK_KEY (key);
267
268         if (!eet_delete (*ef, ckey))
269                 rb_raise (rb_eIOError, "cannot delete entry - %s", ckey);
270
271         return self;
272 }
273
274 /*
275  * call-seq:
276  *  ef.read(key) -> string
277  *
278  * Reads an entry from _ef_ that is stored as _key_.
279  * If the data cannot be read, an +IOError+ is raised,
280  * otherwise a String is returned that contains the data.
281  */
282 static VALUE
283 c_read (VALUE self, VALUE key)
284 {
285         VALUE ret;
286         Eet_File **ef = NULL;
287         void *data;
288         char *ckey;
289         int size = 0;
290
291         Data_Get_Struct (self, Eet_File *, ef);
292         CHECK_CLOSED (ef);
293
294         ckey = StringValuePtr (key);
295         CHECK_KEY (key);
296
297         data = eet_read (*ef, ckey, &size);
298         if (!data)
299                 rb_raise (rb_eIOError, "cannot read entry - %s", ckey);
300
301         ret = rb_str_new (data, size);
302
303         free (data);
304
305         return ret;
306 }
307
308 /*
309  * call-seq:
310  *  ef.write(key, data [, compress] ) -> integer
311  *
312  * Stores _data_ in _ef_ as _key_.
313  * If _compress_ is true (which is the default), the data will be
314  * compressed.
315  * If the data cannot be written, an +IOError+ is raised,
316  * otherwise a the number of bytes written is returned.
317  */
318 static VALUE
319 c_write (int argc, VALUE *argv, VALUE self)
320 {
321         VALUE key = Qnil, buf = Qnil, comp = Qnil;
322         Eet_File **ef = NULL;
323         char *ckey, *cbuf;
324         int n;
325
326         Data_Get_Struct (self, Eet_File *, ef);
327         CHECK_CLOSED (ef);
328
329         rb_scan_args (argc, argv, "21", &key, &buf, &comp);
330
331         if (NIL_P (comp))
332                 comp = Qtrue;
333
334         ckey = StringValuePtr (key);
335         CHECK_KEY (key);
336         cbuf = StringValuePtr (buf);
337
338         n = eet_write (*ef, ckey,
339                        cbuf, RSTRING (buf)->len,
340                        comp == Qtrue);
341         if (!n)
342                 rb_raise (rb_eIOError, "couldn't write to file");
343         else
344                 return INT2FIX (n);
345 }
346
347 /*
348  * call-seq:
349  *  ef.read_image(key) -> array
350  *
351  * Reads an image entry from _ef_ that is stored as _key_.
352  * If the data cannot be read, an +IOError+ is raised,
353  * otherwise an Array is returned that contains the image data,
354  * the image width, the image height, a boolean that indicates
355  * whether the image has an alpha channel or not and a hash
356  * that contains the compression options that were used to store
357  * the image (see Eet::File#write_image).
358  */
359 static VALUE
360 c_read_image (VALUE self, VALUE key)
361 {
362         VALUE ret, comp;
363         Eet_File **ef = NULL;
364         void *data;
365         char *ckey;
366         unsigned int w = 0, h = 0;
367         int has_alpha = 0, level = 0, quality = 0, lossy = 0;
368
369         Data_Get_Struct (self, Eet_File *, ef);
370         CHECK_CLOSED (ef);
371
372         ckey = StringValuePtr (key);
373         CHECK_KEY (key);
374
375         data = eet_data_image_read (*ef, ckey, &w, &h,
376                                     &has_alpha, &level, &quality,
377                                     &lossy);
378         if (!data)
379                 rb_raise (rb_eIOError, "cannot read entry - %s", ckey);
380
381         comp = rb_hash_new ();
382         rb_hash_aset (comp, sym_lossy, INT2FIX (lossy));
383         rb_hash_aset (comp, sym_level, INT2FIX (level));
384         rb_hash_aset (comp, sym_quality, INT2FIX (quality));
385
386         ret = rb_ary_new3 (5, rb_str_new (data, w * h * 4),
387                                INT2FIX (w), INT2FIX (h),
388                                has_alpha ? Qtrue : Qfalse, comp);
389         free (data);
390
391         return ret;
392 }
393
394 /*
395  * call-seq:
396  *  ef.write_image(key, image_data, w, h [, alpha] [, comp] ) -> integer
397  *
398  * Stores _image_data_ with width _w_ and height _h_ in _ef_ as _key_.
399  * Pass true for _alpha_ if the image contains an alpha channel.
400  * You can control how the image is compressed by passing _comp_, which
401  * is a hash whose :lossy entry is true if the image should be
402  * compressed lossily. If :lossy is true, the :quality entry
403  * (0-100, default 100) sets the compression quality.
404  * If :lossy is false, the :level entry (0-9, default 9) sets the
405  * compression level. If _comp_ isn't passed, then the
406  * image is stored losslessly.
407  * If the data cannot be written, an +IOError+ is raised,
408  * otherwise a the number of bytes written is returned.
409  */
410 static VALUE
411 c_write_image (int argc, VALUE *argv, VALUE self)
412 {
413         VALUE key = Qnil, buf = Qnil, w = Qnil, h = Qnil, has_alpha = Qnil;
414         VALUE comp = Qnil, tmp;
415         Eet_File **ef = NULL;
416         char *ckey, *cbuf;
417         int n, lossy = 0, level = 9, quality = 100;
418
419         Data_Get_Struct (self, Eet_File *, ef);
420         CHECK_CLOSED (ef);
421
422         rb_scan_args (argc, argv, "42", &key, &buf, &w, &h, &has_alpha,
423                       &comp);
424
425         if (NIL_P (has_alpha))
426                 has_alpha = Qfalse;
427
428         ckey = StringValuePtr (key);
429         CHECK_KEY (key);
430         cbuf = StringValuePtr (buf);
431         Check_Type (w, T_FIXNUM);
432         Check_Type (h, T_FIXNUM);
433
434         if (!NIL_P (comp)) {
435                 Check_Type (comp, T_HASH);
436
437                 tmp = rb_hash_aref (comp, sym_lossy);
438                 if (!NIL_P (tmp))
439                         lossy = FIX2INT (tmp);
440
441                 tmp = rb_hash_aref (comp, sym_level);
442                 if (!NIL_P (tmp))
443                         level = FIX2INT (tmp);
444
445                 tmp = rb_hash_aref (comp, sym_quality);
446                 if (!NIL_P (tmp))
447                         quality = FIX2INT (tmp);
448         }
449
450         if (!RSTRING (buf)->len)
451                 return INT2FIX (0);
452
453         n = eet_data_image_write (*ef, ckey, cbuf,
454                                   FIX2INT (w), FIX2INT (h),
455                                   has_alpha == Qtrue,
456                                   level, quality, lossy);
457         if (!n)
458                 rb_raise (rb_eIOError, "couldn't write to file");
459         else
460                 return INT2FIX (n);
461 }
462
463 static VALUE
464 chunk_init (VALUE self, VALUE tag, VALUE data)
465 {
466         long tag_len, data_len, tmp;
467
468         StringValue (tag);
469         StringValue (data);
470
471         if (rb_funcall (tag, id_include, 1, INT2FIX (0)) == Qtrue)
472                 rb_raise (rb_eArgError, "tag must not contain binary zeroes");
473
474         /* libeet uses a signed 32bit integer to store the
475          * chunk size, so make sure we don't overflow it
476          */
477         tag_len = RSTRING (tag)->len;
478         data_len = RSTRING (data)->len;
479         tmp = tag_len + 1 + data_len;
480
481         if (tmp < tag_len || tmp < data_len || tmp < 1 || tmp >= 2147483647L)
482                 rb_raise (rb_eArgError, "tag or data too long");
483
484         rb_ivar_set (self, id_tag, rb_str_dup_frozen (tag));
485         rb_ivar_set (self, id_data, rb_str_dup_frozen (data));
486
487         return self;
488 }
489
490 static VALUE
491 chunk_to_s (VALUE self)
492 {
493         VALUE tmp, ret;
494         unsigned int size, buf_len;
495         unsigned char *buf;
496         struct RString *tag, *data;
497
498         tmp = rb_ivar_get (self, id_tag);
499         tag = RSTRING (tmp);
500
501         tmp = rb_ivar_get (self, id_data);
502         data = RSTRING (tmp);
503
504         buf_len = 9 + tag->len + data->len;
505         ret = rb_str_buf_new (buf_len);
506
507         buf = (unsigned char *) RSTRING (ret)->ptr;
508         RSTRING (ret)->len = buf_len;
509
510         memcpy (buf, "CHnK", 4);
511         buf += 4;
512
513         size = tag->len + data->len + 1;
514         size = BSWAP32 (size);
515         memcpy (buf, &size, 4);
516         buf += 4;
517
518         memcpy (buf, tag->ptr, tag->len);
519         buf += tag->len;
520
521         *buf++ = 0;
522
523         memcpy (buf, data->ptr, data->len);
524
525         return ret;
526 }
527
528 static int
529 for_each_prop (VALUE tag, VALUE arg, VALUE stream)
530 {
531         VALUE value, type, tmp;
532
533         if (rb_obj_is_kind_of (arg, rb_cArray) == Qfalse)
534                 rb_raise (ePropError, "hash value is not an array");
535
536         value = rb_ary_entry (arg, 0);
537         if (NIL_P (value))
538                 return ST_CONTINUE;
539
540         type = rb_ary_entry (arg, 1);
541         tmp = rb_funcall (value, id_to_eet_chunks, 2, tag, type);
542
543         rb_ary_concat (stream, tmp);
544
545         return ST_CONTINUE;
546 }
547
548 /*
549  * call-seq:
550  *  object.to_eet -> string
551  *
552  * Serializes the receiver to EET format.
553  */
554 static VALUE
555 c_to_eet (VALUE self)
556 {
557         VALUE props, name, stream, chunk, args[2];
558 #ifndef HAVE_RB_HASH_FOREACH
559         struct RArray *keys;
560         long i;
561 #endif
562
563         props = rb_funcall (self, id_to_eet_properties, 0);
564
565         if (rb_obj_is_kind_of (props, rb_cHash) == Qfalse ||
566             !RHASH (props)->tbl->num_entries)
567                 rb_raise (ePropError, "invalid EET properties");
568
569         name = rb_funcall (self, id_to_eet_name, 0);
570         StringValue (name);
571
572         if (!RSTRING (name)->len ||
573             rb_funcall (name, id_include, 1, INT2FIX (0)))
574                 rb_raise (eNameError, "invalid EET name");
575
576         stream = rb_class_new_instance (0, NULL, cStream);
577
578 #ifdef HAVE_RB_HASH_FOREACH
579         rb_hash_foreach (props, for_each_prop, stream);
580 #else
581         keys = RARRAY (rb_funcall (props, id_keys, 0));
582
583         for (i = 0; i < keys->len; i++)
584                 for_each_prop (keys->ptr[i],
585                                rb_hash_aref (props, keys->ptr[i]),
586                                stream);
587 #endif
588
589         args[0] = name;
590         args[1] = rb_ary_to_s (stream);
591
592         rb_ary_clear (stream); /* give the GC a hand... */
593
594         chunk = rb_class_new_instance (2, args, cChunk);
595
596         return rb_funcall (chunk, id_to_s, 0);
597 }
598
599 static VALUE
600 int_to_eet_chunks (int argc, VALUE *argv, VALUE self)
601 {
602         VALUE tag, type = Qnil, ary, args[2], chunk;
603         char *cfmt = "V";
604
605         rb_scan_args (argc, argv, "11", &tag, &type);
606
607         ary = rb_ary_new3 (1, self);
608
609         if (type == sym_char)
610                 cfmt = "c";
611         else if (type == sym_short)
612                 cfmt = "v";
613         else if (type == sym_long_long)
614                 cfmt = "q";
615
616         args[0] = tag;
617         args[1] = rb_funcall (ary, id_pack, 1, rb_str_new2 (cfmt));
618         chunk = rb_class_new_instance (2, args, cChunk);
619
620         return rb_ary_new3 (1, chunk);
621 }
622
623 static VALUE
624 float_to_eet_chunks (int argc, VALUE *argv, VALUE self)
625 {
626         VALUE tag, type = Qnil, args[2], chunk;
627         char buf[65], *loc;
628         double d;
629         int len;
630
631         rb_scan_args (argc, argv, "11", &tag, &type);
632
633         d = NUM2DBL (self);
634
635         /* switch locale to make sure we get proper snprintf output */
636         loc = setlocale (LC_NUMERIC, "C");
637
638         len = snprintf (buf, sizeof (buf) - 1, "%a",
639                         type == sym_double ? d : (float) d);
640
641         if (loc)
642                 setlocale (LC_NUMERIC, loc);
643
644         buf[++len] = '\0';
645
646         args[0] = tag;
647         args[1] = rb_str_new (buf, len);
648         chunk = rb_class_new_instance (2, args, cChunk);
649
650         return rb_ary_new3 (1, chunk);
651 }
652
653 void
654 Init_eet_ext ()
655 {
656         VALUE m, c;
657
658         m = rb_define_module ("Eet");
659
660         c = rb_define_class_under (m, "File", rb_cObject);
661         rb_define_alloc_func (c, c_alloc);
662         rb_define_singleton_method (c, "open", c_open, -1);
663         rb_define_method (c, "initialize", c_init, -1);
664         rb_define_method (c, "close", c_close, 0);
665         rb_define_method (c, "entries", c_entries, 0);
666         rb_define_method (c, "[]", c_glob, 1);
667         rb_define_method (c, "delete", c_delete, 1);
668         rb_define_method (c, "read", c_read, 1);
669         rb_define_method (c, "write", c_write, -1);
670         rb_define_method (c, "read_image", c_read_image, 1);
671         rb_define_method (c, "write_image", c_write_image, -1);
672
673         cStream = rb_define_class_under (m, "Stream", rb_cArray);
674
675         cChunk = rb_define_class_under (m, "Chunk", rb_cObject);
676         rb_define_method (cChunk, "initialize", chunk_init, 2);
677         rb_define_method (cChunk, "to_s", chunk_to_s, 0);
678
679         rb_define_attr (cChunk, "tag", 1, 0);
680         rb_define_attr (cChunk, "data", 1, 0);
681
682         rb_define_method (rb_cObject, "to_eet", c_to_eet, 0);
683
684         rb_define_method (rb_cInteger, "to_eet_chunks", int_to_eet_chunks, -1);
685         rb_define_method (rb_cFloat, "to_eet_chunks", float_to_eet_chunks, -1);
686
687         eEetError = rb_define_class_under (m, "EetError", rb_eStandardError);
688         eNameError = rb_define_class_under (m, "NameError", eEetError);
689         ePropError = rb_define_class_under (m, "PropertyError", eEetError);
690
691         id_include = rb_intern ("include?");
692         id_to_s = rb_intern ("to_s");
693         id_keys = rb_intern ("keys");
694         id_pack = rb_intern ("pack");
695         id_to_eet_chunks = rb_intern ("to_eet_chunks");
696         id_to_eet_name = rb_intern ("to_eet_name");
697         id_to_eet_properties = rb_intern ("to_eet_properties");
698         id_tag = rb_intern ("@tag");
699         id_data = rb_intern ("@data");
700         sym_lossy = ID2SYM (rb_intern ("lossy"));
701         sym_level = ID2SYM (rb_intern ("level"));
702         sym_quality =  ID2SYM (rb_intern ("quality"));
703         sym_char = ID2SYM (rb_intern ("char"));
704         sym_short = ID2SYM (rb_intern ("short"));
705         sym_long_long = ID2SYM (rb_intern ("long_long"));
706         sym_double = ID2SYM (rb_intern ("double"));
707 }