Re-implemented Integer#to_eet_chunks in C.
[ruby-eet.git] / ext / ext.c
1 /*
2  * $Id: ext.c 60 2005-06-08 16:18:32Z 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 #ifdef WORDS_BIGENDIAN
40 # define BSWAP32(x) \
41         ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
42         (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
43 #else /* !WORDS_BIGENDIAN */
44 # define BSWAP32(x) (x)
45 #endif /* WORDS_BIGENDIAN */
46
47 static VALUE c_close (VALUE self);
48
49 static VALUE cStream, cChunk,
50              eEetError, eNameError, ePropError,
51              sym_lossy, sym_level, sym_quality, sym_char, sym_short,
52              sym_long_long, sym_double;
53 static ID id_include, id_serialize, id_push, id_keys,
54           id_to_eet_chunks, id_to_eet_name, id_to_eet_properties,
55           id_tag, id_data;
56
57 static void
58 c_free (Eet_File **ef)
59 {
60         if (*ef) {
61                 eet_close (*ef);
62                 *ef = NULL;
63
64                 eet_shutdown ();
65         }
66
67         free (ef);
68 }
69
70 static VALUE
71 c_alloc (VALUE klass)
72 {
73         Eet_File **ef = NULL;
74
75         return Data_Make_Struct (klass, Eet_File *, NULL, c_free, ef);
76 }
77
78 /*
79  * call-seq:
80  *  Eet::File.open(file [, mode] )                -> ef or nil
81  *  Eet::File.open(file [, mode] ) { |ef| block } -> nil
82  *
83  * If a block isn't specified, Eet::File.open is a synonym for
84  * Eet::File.new.
85  * If a block is given, it will be invoked with the
86  * Eet::File object as a parameter, and the file will be
87  * automatically closed when the block terminates. The call always
88  * returns +nil+ in this case.
89  */
90 static VALUE
91 c_open (int argc, VALUE *argv, VALUE klass)
92 {
93         VALUE obj = rb_class_new_instance (argc, argv, klass);
94
95         if (rb_block_given_p ())
96                 return rb_ensure (rb_yield, obj, c_close, obj);
97         else
98                 return obj;
99 }
100
101 /*
102  * call-seq:
103  *  Eet::File.new(file [, mode] ) -> ef or nil
104  *
105  * Creates an Eet::File object for _file_.
106  *
107  * _file_ is opened with the specified mode (defaulting to "r").
108  * Possible values for _mode_ are "r" for read-only access,
109  * "w" for write-only access and "r+" for read/write access.
110  */
111 static VALUE
112 c_init (int argc, VALUE *argv, VALUE self)
113 {
114         VALUE file = Qnil, mode = Qnil;
115         Eet_File **ef = NULL;
116         Eet_File_Mode m = EET_FILE_MODE_READ;
117         const char *tmp, *cfile;
118
119         Data_Get_Struct (self, Eet_File *, ef);
120
121         rb_scan_args (argc, argv, "11", &file, &mode);
122
123         cfile = StringValuePtr (file);
124
125         if (!NIL_P (mode)) {
126                 tmp = StringValuePtr (mode);
127                 if (!strcmp (tmp, "r+"))
128                         m = EET_FILE_MODE_READ_WRITE;
129                 else if (!strcmp (tmp, "w"))
130                         m = EET_FILE_MODE_WRITE;
131                 else if (strcmp (tmp, "r"))
132                         rb_raise (rb_eArgError, "illegal access mode %s", tmp);
133         }
134
135         eet_init ();
136
137         *ef = eet_open (cfile, m);
138         if (!*ef) {
139                 switch (m) {
140                         case EET_FILE_MODE_READ_WRITE:
141                         case EET_FILE_MODE_WRITE:
142                                 tmp = "Permission denied - %s";
143                                 break;
144                         default:
145                                 tmp = "File not found - %s";
146                                 break;
147                 }
148
149                 rb_raise (rb_eRuntimeError, tmp, cfile);
150         }
151
152         return self;
153 }
154
155 /*
156  * call-seq:
157  *  ef.close -> ef
158  *
159  * Closes _ef_ and flushes any pending writes.
160  * _ef_ is unavailable for any further data operations;
161  * an +IOError+ is raised if such an attempt is made.
162  *
163  * Eet::File objects are automatically closed when they
164  * are claimed by the garbage collector.
165  */
166 static VALUE
167 c_close (VALUE self)
168 {
169         Eet_File **ef = NULL;
170
171         Data_Get_Struct (self, Eet_File *, ef);
172         CHECK_CLOSED (ef);
173
174         eet_close (*ef);
175         *ef = NULL;
176
177         eet_shutdown ();
178
179         return self;
180 }
181
182 /*
183  * call-seq:
184  *  ef.list([glob]) -> array
185  *
186  * Returns an Array of entries in _ef_ that match the shell glob
187  * _glob_ (defaulting to "*").
188  */
189 static VALUE
190 c_list (int argc, VALUE *argv, VALUE self)
191 {
192         VALUE glob = Qnil, ret;
193         Eet_File **ef = NULL;
194         char **entries, *tmp = "*";
195         int i, count = 0;
196
197         Data_Get_Struct (self, Eet_File *, ef);
198         CHECK_CLOSED (ef);
199
200         switch (eet_mode_get (*ef)) {
201                 case EET_FILE_MODE_READ:
202                 case EET_FILE_MODE_READ_WRITE:
203                         break;
204                 default:
205                         rb_raise (rb_eIOError, "cannot list entries");
206         }
207
208         rb_scan_args (argc, argv, "01", &glob);
209
210         if (!NIL_P (glob))
211                 tmp = StringValuePtr (glob);
212
213         ret = rb_ary_new ();
214
215         entries = eet_list (*ef, tmp, &count);
216
217         for (i = 0; i < count; i++)
218                 rb_ary_push (ret, rb_str_new2 (entries[i]));
219
220         free (entries);
221
222         return ret;
223 }
224
225 /*
226  * call-seq:
227  *  ef.delete(key) -> ef
228  *
229  * Deletes the entry from _ef_ that is stored as _key_.
230  * If the entry cannot be deleted, an +IOError+ is raised,
231  * otherwise _ef_ is returned.
232  */
233 static VALUE
234 c_delete (VALUE self, VALUE key)
235 {
236         Eet_File **ef = NULL;
237         char *ckey;
238
239         Data_Get_Struct (self, Eet_File *, ef);
240         CHECK_CLOSED (ef);
241
242         ckey = StringValuePtr (key);
243         CHECK_KEY (key);
244
245         if (!eet_delete (*ef, ckey))
246                 rb_raise (rb_eIOError, "cannot delete entry - %s", ckey);
247
248         return self;
249 }
250
251 /*
252  * call-seq:
253  *  ef.read(key) -> string
254  *
255  * Reads an entry from _ef_ that is stored as _key_.
256  * If the data cannot be read, an +IOError+ is raised,
257  * otherwise a String is returned that contains the data.
258  */
259 static VALUE
260 c_read (VALUE self, VALUE key)
261 {
262         VALUE ret;
263         Eet_File **ef = NULL;
264         void *data;
265         char *ckey;
266         int size = 0;
267
268         Data_Get_Struct (self, Eet_File *, ef);
269         CHECK_CLOSED (ef);
270
271         ckey = StringValuePtr (key);
272         CHECK_KEY (key);
273
274         data = eet_read (*ef, ckey, &size);
275         if (!data)
276                 rb_raise (rb_eIOError, "cannot read entry - %s", ckey);
277
278         ret = rb_str_new (data, size);
279
280         free (data);
281
282         return ret;
283 }
284
285 /*
286  * call-seq:
287  *  ef.write(key, data [, compress] ) -> integer
288  *
289  * Stores _data_ in _ef_ as _key_.
290  * If _compress_ is true (which is the default), the data will be
291  * compressed.
292  * If the data cannot be written, an +IOError+ is raised,
293  * otherwise a the number of bytes written is returned.
294  */
295 static VALUE
296 c_write (int argc, VALUE *argv, VALUE self)
297 {
298         VALUE key = Qnil, buf = Qnil, comp = Qnil;
299         Eet_File **ef = NULL;
300         char *ckey, *cbuf;
301         int n;
302
303         Data_Get_Struct (self, Eet_File *, ef);
304         CHECK_CLOSED (ef);
305
306         rb_scan_args (argc, argv, "21", &key, &buf, &comp);
307
308         if (NIL_P (comp))
309                 comp = Qtrue;
310
311         ckey = StringValuePtr (key);
312         CHECK_KEY (key);
313         cbuf = StringValuePtr (buf);
314
315         n = eet_write (*ef, ckey,
316                        cbuf, RSTRING (buf)->len,
317                        comp == Qtrue);
318         if (!n)
319                 rb_raise (rb_eIOError, "couldn't write to file");
320         else
321                 return INT2FIX (n);
322 }
323
324 /*
325  * call-seq:
326  *  ef.read_image(key) -> array
327  *
328  * Reads an image entry from _ef_ that is stored as _key_.
329  * If the data cannot be read, an +IOError+ is raised,
330  * otherwise an Array is returned that contains the image data,
331  * the image width, the image height, a boolean that indicates
332  * whether the image has an alpha channel or not and a hash
333  * that contains the compression options that were used to store
334  * the image (see Eet::File#write_image).
335  */
336 static VALUE
337 c_read_image (VALUE self, VALUE key)
338 {
339         VALUE ret, comp;
340         Eet_File **ef = NULL;
341         void *data;
342         char *ckey;
343         int w = 0, h = 0, has_alpha = 0, level = 0, quality = 0, lossy = 0;
344
345         Data_Get_Struct (self, Eet_File *, ef);
346         CHECK_CLOSED (ef);
347
348         ckey = StringValuePtr (key);
349         CHECK_KEY (key);
350
351         data = eet_data_image_read (*ef, ckey, &w, &h,
352                                     &has_alpha, &level, &quality,
353                                     &lossy);
354         if (!data)
355                 rb_raise (rb_eIOError, "cannot read entry - %s", ckey);
356
357         comp = rb_hash_new ();
358         rb_hash_aset (comp, sym_lossy, INT2FIX (lossy));
359         rb_hash_aset (comp, sym_level, INT2FIX (level));
360         rb_hash_aset (comp, sym_quality, INT2FIX (quality));
361
362         ret = rb_ary_new3 (5, rb_str_new (data, w * h * 4),
363                                INT2FIX (w), INT2FIX (h),
364                                has_alpha ? Qtrue : Qfalse, comp);
365         free (data);
366
367         return ret;
368 }
369
370 /*
371  * call-seq:
372  *  ef.write_image(key, image_data, w, h [, alpha] [, comp] ) -> integer
373  *
374  * Stores _image_data_ with width _w_ and height _h_ in _ef_ as _key_.
375  * Pass true for _alpha_ if the image contains an alpha channel.
376  * You can control how the image is compressed by passing _comp_, which
377  * is a hash whose :lossy entry is true if the image should be
378  * compressed lossily. If :lossy is true, the :quality entry
379  * (0-100, default 100) sets the compression quality.
380  * If :lossy is false, the :level entry (0-9, default 9) sets the
381  * compression level. If _comp_ isn't passed, then the
382  * image is stored losslessly.
383  * If the data cannot be written, an +IOError+ is raised,
384  * otherwise a the number of bytes written is returned.
385  */
386 static VALUE
387 c_write_image (int argc, VALUE *argv, VALUE self)
388 {
389         VALUE key = Qnil, buf = Qnil, w = Qnil, h = Qnil, has_alpha = Qnil;
390         VALUE comp = Qnil, tmp;
391         Eet_File **ef = NULL;
392         char *ckey, *cbuf;
393         int n, lossy = 0, level = 9, quality = 100;
394
395         Data_Get_Struct (self, Eet_File *, ef);
396         CHECK_CLOSED (ef);
397
398         rb_scan_args (argc, argv, "42", &key, &buf, &w, &h, &has_alpha,
399                       &comp);
400
401         if (NIL_P (has_alpha))
402                 has_alpha = Qfalse;
403
404         ckey = StringValuePtr (key);
405         CHECK_KEY (key);
406         cbuf = StringValuePtr (buf);
407         Check_Type (w, T_FIXNUM);
408         Check_Type (h, T_FIXNUM);
409
410         if (!NIL_P (comp)) {
411                 Check_Type (comp, T_HASH);
412
413                 tmp = rb_hash_aref (comp, sym_lossy);
414                 if (!NIL_P (tmp))
415                         lossy = FIX2INT (tmp);
416
417                 tmp = rb_hash_aref (comp, sym_level);
418                 if (!NIL_P (tmp))
419                         level = FIX2INT (tmp);
420
421                 tmp = rb_hash_aref (comp, sym_quality);
422                 if (!NIL_P (tmp))
423                         quality = FIX2INT (tmp);
424         }
425
426         if (!RSTRING (buf)->len)
427                 return INT2FIX (0);
428
429         n = eet_data_image_write (*ef, ckey, cbuf,
430                                   FIX2INT (w), FIX2INT (h),
431                                   has_alpha == Qtrue,
432                                   level, quality, lossy);
433         if (!n)
434                 rb_raise (rb_eIOError, "couldn't write to file");
435         else
436                 return INT2FIX (n);
437 }
438
439 static VALUE
440 stream_serialize (VALUE self)
441 {
442         VALUE ret;
443         struct RArray *stream;
444         long i;
445
446         ret = rb_str_new2 ("");
447
448         stream = RARRAY (self);
449         if (!stream->len)
450                 return ret;
451
452         for (i = 0; i < stream->len; i++) {
453                 VALUE str = rb_funcall (stream->ptr[i], id_serialize, 0, NULL);
454
455                 rb_str_append (ret, str);
456         }
457
458         return ret;
459 }
460
461 static VALUE
462 chunk_init (VALUE self, VALUE tag, VALUE data)
463 {
464         unsigned long len;
465
466         StringValue (tag);
467         StringValue (data);
468
469         if (rb_funcall (tag, id_include, 1, INT2FIX (0)) == Qtrue) \
470                 rb_raise (rb_eArgError, "tag must not contain binary zeroes");
471
472         /* libeet uses a signed 32bit integer to store the
473          * chunk size, so make sure we don't overflow it
474          */
475         len = RSTRING (tag)->len + 1 + RSTRING (data)->len;
476         if (len < 0 || len >= 2147483647L)
477                 rb_raise (rb_eArgError, "tag or data too long");
478
479         rb_ivar_set (self, id_tag, rb_str_dup_frozen (tag));
480         rb_ivar_set (self, id_data, rb_str_dup_frozen (data));
481
482         return self;
483 }
484
485 static VALUE
486 chunk_serialize (VALUE self)
487 {
488         VALUE tmp, ret;
489         unsigned int size, buf_len;
490         unsigned char *buf;
491         struct RString *tag, *data;
492
493         tmp = rb_ivar_get (self, id_tag);
494         StringValue (tmp);
495         tag = RSTRING (tmp);
496
497         tmp = rb_ivar_get (self, id_data);
498         StringValue (tmp);
499         data = RSTRING (tmp);
500
501         buf_len = 9 + tag->len + data->len;
502         ret = rb_str_buf_new (buf_len);
503
504         buf = 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, chunks;
529         long len, i;
530
531         if (rb_obj_is_kind_of (arg, rb_cArray) == Qfalse)
532                 rb_raise (ePropError, "hash value is not an array");
533
534         value = rb_ary_entry (arg, 0);
535         if (NIL_P (value))
536                 return ST_CONTINUE;
537
538         type = rb_ary_entry (arg, 1);
539         chunks = rb_funcall (value, id_to_eet_chunks, 2, tag, type);
540
541         len = RARRAY (chunks)->len;
542
543         for (i = 0; i < len; i++)
544                 rb_funcall (stream, id_push, 1, rb_ary_entry (chunks, i));
545
546         return ST_CONTINUE;
547 }
548
549 /*
550  * call-seq:
551  *  object.to_eet -> string
552  *
553  * Serializes the receiver to EET format.
554  */
555 static VALUE
556 c_to_eet (VALUE self)
557 {
558         VALUE props, name, stream, chunk, args[2];
559 #ifndef HAVE_RB_HASH_FOREACH
560         struct RArray *keys;
561         long i;
562 #endif
563
564         props = rb_funcall (self, id_to_eet_properties, 0);
565
566         if (rb_obj_is_kind_of (props, rb_cHash) == Qfalse ||
567             !RHASH (props)->tbl->num_entries)
568                 rb_raise (ePropError, "invalid EET properties");
569
570         name = rb_funcall (self, id_to_eet_name, 0);
571         StringValue (name);
572
573         if (!RSTRING (name)->len ||
574             rb_funcall (name, id_include, 1, INT2FIX (0)))
575                 rb_raise (eNameError, "invalid EET name");
576
577         stream = rb_class_new_instance (0, NULL, cStream);
578
579 #ifdef HAVE_RB_HASH_FOREACH
580         rb_hash_foreach (props, for_each_prop, stream);
581 #else
582         keys = RARRAY (rb_funcall (props, id_keys, 0));
583
584         for (i = 0; i < keys->len; i++)
585                 for_each_prop (keys->ptr[i],
586                                rb_hash_aref (props, keys->ptr[i]),
587                                stream);
588 #endif
589
590         args[0] = name;
591         args[1] = rb_funcall (stream, id_serialize, 0);
592         chunk = rb_class_new_instance (2, args, cChunk);
593
594         stream = rb_class_new_instance (1, &chunk, cStream);
595
596         return rb_funcall (stream, id_serialize, 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, rb_intern ("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, "list", c_list, -1);
666         rb_define_method (c, "delete", c_delete, 1);
667         rb_define_method (c, "read", c_read, 1);
668         rb_define_method (c, "write", c_write, -1);
669         rb_define_method (c, "read_image", c_read_image, 1);
670         rb_define_method (c, "write_image", c_write_image, -1);
671
672         cStream = rb_define_class_under (m, "Stream", rb_cArray);
673         rb_define_method (cStream, "serialize", stream_serialize, 0);
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, "serialize", chunk_serialize, 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_serialize = rb_intern ("serialize");
693         id_push = rb_intern ("push");
694         id_keys = rb_intern ("keys");
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 }