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