465099f801f49d8efe211365e0bf6eb744faf7af
[ruby-eet.git] / ext / ext.c
1 /*
2  * $Id: ext.c 59 2005-06-08 16:13:15Z 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_double;
52 static ID id_include, id_serialize, id_push, id_keys,
53           id_to_eet_chunks, id_to_eet_name, id_to_eet_properties,
54           id_tag, id_data;
55
56 static void
57 c_free (Eet_File **ef)
58 {
59         if (*ef) {
60                 eet_close (*ef);
61                 *ef = NULL;
62
63                 eet_shutdown ();
64         }
65
66         free (ef);
67 }
68
69 static VALUE
70 c_alloc (VALUE klass)
71 {
72         Eet_File **ef = NULL;
73
74         return Data_Make_Struct (klass, Eet_File *, NULL, c_free, ef);
75 }
76
77 /*
78  * call-seq:
79  *  Eet::File.open(file [, mode] )                -> ef or nil
80  *  Eet::File.open(file [, mode] ) { |ef| block } -> nil
81  *
82  * If a block isn't specified, Eet::File.open is a synonym for
83  * Eet::File.new.
84  * If a block is given, it will be invoked with the
85  * Eet::File object as a parameter, and the file will be
86  * automatically closed when the block terminates. The call always
87  * returns +nil+ in this case.
88  */
89 static VALUE
90 c_open (int argc, VALUE *argv, VALUE klass)
91 {
92         VALUE obj = rb_class_new_instance (argc, argv, klass);
93
94         if (rb_block_given_p ())
95                 return rb_ensure (rb_yield, obj, c_close, obj);
96         else
97                 return obj;
98 }
99
100 /*
101  * call-seq:
102  *  Eet::File.new(file [, mode] ) -> ef or nil
103  *
104  * Creates an Eet::File object for _file_.
105  *
106  * _file_ is opened with the specified mode (defaulting to "r").
107  * Possible values for _mode_ are "r" for read-only access,
108  * "w" for write-only access and "r+" for read/write access.
109  */
110 static VALUE
111 c_init (int argc, VALUE *argv, VALUE self)
112 {
113         VALUE file = Qnil, mode = Qnil;
114         Eet_File **ef = NULL;
115         Eet_File_Mode m = EET_FILE_MODE_READ;
116         const char *tmp, *cfile;
117
118         Data_Get_Struct (self, Eet_File *, ef);
119
120         rb_scan_args (argc, argv, "11", &file, &mode);
121
122         cfile = StringValuePtr (file);
123
124         if (!NIL_P (mode)) {
125                 tmp = StringValuePtr (mode);
126                 if (!strcmp (tmp, "r+"))
127                         m = EET_FILE_MODE_READ_WRITE;
128                 else if (!strcmp (tmp, "w"))
129                         m = EET_FILE_MODE_WRITE;
130                 else if (strcmp (tmp, "r"))
131                         rb_raise (rb_eArgError, "illegal access mode %s", tmp);
132         }
133
134         eet_init ();
135
136         *ef = eet_open (cfile, m);
137         if (!*ef) {
138                 switch (m) {
139                         case EET_FILE_MODE_READ_WRITE:
140                         case EET_FILE_MODE_WRITE:
141                                 tmp = "Permission denied - %s";
142                                 break;
143                         default:
144                                 tmp = "File not found - %s";
145                                 break;
146                 }
147
148                 rb_raise (rb_eRuntimeError, tmp, cfile);
149         }
150
151         return self;
152 }
153
154 /*
155  * call-seq:
156  *  ef.close -> ef
157  *
158  * Closes _ef_ and flushes any pending writes.
159  * _ef_ is unavailable for any further data operations;
160  * an +IOError+ is raised if such an attempt is made.
161  *
162  * Eet::File objects are automatically closed when they
163  * are claimed by the garbage collector.
164  */
165 static VALUE
166 c_close (VALUE self)
167 {
168         Eet_File **ef = NULL;
169
170         Data_Get_Struct (self, Eet_File *, ef);
171         CHECK_CLOSED (ef);
172
173         eet_close (*ef);
174         *ef = NULL;
175
176         eet_shutdown ();
177
178         return self;
179 }
180
181 /*
182  * call-seq:
183  *  ef.list([glob]) -> array
184  *
185  * Returns an Array of entries in _ef_ that match the shell glob
186  * _glob_ (defaulting to "*").
187  */
188 static VALUE
189 c_list (int argc, VALUE *argv, VALUE self)
190 {
191         VALUE glob = Qnil, ret;
192         Eet_File **ef = NULL;
193         char **entries, *tmp = "*";
194         int i, count = 0;
195
196         Data_Get_Struct (self, Eet_File *, ef);
197         CHECK_CLOSED (ef);
198
199         switch (eet_mode_get (*ef)) {
200                 case EET_FILE_MODE_READ:
201                 case EET_FILE_MODE_READ_WRITE:
202                         break;
203                 default:
204                         rb_raise (rb_eIOError, "cannot list entries");
205         }
206
207         rb_scan_args (argc, argv, "01", &glob);
208
209         if (!NIL_P (glob))
210                 tmp = StringValuePtr (glob);
211
212         ret = rb_ary_new ();
213
214         entries = eet_list (*ef, tmp, &count);
215
216         for (i = 0; i < count; i++)
217                 rb_ary_push (ret, rb_str_new2 (entries[i]));
218
219         free (entries);
220
221         return ret;
222 }
223
224 /*
225  * call-seq:
226  *  ef.delete(key) -> ef
227  *
228  * Deletes the entry from _ef_ that is stored as _key_.
229  * If the entry cannot be deleted, an +IOError+ is raised,
230  * otherwise _ef_ is returned.
231  */
232 static VALUE
233 c_delete (VALUE self, VALUE key)
234 {
235         Eet_File **ef = NULL;
236         char *ckey;
237
238         Data_Get_Struct (self, Eet_File *, ef);
239         CHECK_CLOSED (ef);
240
241         ckey = StringValuePtr (key);
242         CHECK_KEY (key);
243
244         if (!eet_delete (*ef, ckey))
245                 rb_raise (rb_eIOError, "cannot delete entry - %s", ckey);
246
247         return self;
248 }
249
250 /*
251  * call-seq:
252  *  ef.read(key) -> string
253  *
254  * Reads an entry from _ef_ that is stored as _key_.
255  * If the data cannot be read, an +IOError+ is raised,
256  * otherwise a String is returned that contains the data.
257  */
258 static VALUE
259 c_read (VALUE self, VALUE key)
260 {
261         VALUE ret;
262         Eet_File **ef = NULL;
263         void *data;
264         char *ckey;
265         int size = 0;
266
267         Data_Get_Struct (self, Eet_File *, ef);
268         CHECK_CLOSED (ef);
269
270         ckey = StringValuePtr (key);
271         CHECK_KEY (key);
272
273         data = eet_read (*ef, ckey, &size);
274         if (!data)
275                 rb_raise (rb_eIOError, "cannot read entry - %s", ckey);
276
277         ret = rb_str_new (data, size);
278
279         free (data);
280
281         return ret;
282 }
283
284 /*
285  * call-seq:
286  *  ef.write(key, data [, compress] ) -> integer
287  *
288  * Stores _data_ in _ef_ as _key_.
289  * If _compress_ is true (which is the default), the data will be
290  * compressed.
291  * If the data cannot be written, an +IOError+ is raised,
292  * otherwise a the number of bytes written is returned.
293  */
294 static VALUE
295 c_write (int argc, VALUE *argv, VALUE self)
296 {
297         VALUE key = Qnil, buf = Qnil, comp = Qnil;
298         Eet_File **ef = NULL;
299         char *ckey, *cbuf;
300         int n;
301
302         Data_Get_Struct (self, Eet_File *, ef);
303         CHECK_CLOSED (ef);
304
305         rb_scan_args (argc, argv, "21", &key, &buf, &comp);
306
307         if (NIL_P (comp))
308                 comp = Qtrue;
309
310         ckey = StringValuePtr (key);
311         CHECK_KEY (key);
312         cbuf = StringValuePtr (buf);
313
314         n = eet_write (*ef, ckey,
315                        cbuf, RSTRING (buf)->len,
316                        comp == Qtrue);
317         if (!n)
318                 rb_raise (rb_eIOError, "couldn't write to file");
319         else
320                 return INT2FIX (n);
321 }
322
323 /*
324  * call-seq:
325  *  ef.read_image(key) -> array
326  *
327  * Reads an image entry from _ef_ that is stored as _key_.
328  * If the data cannot be read, an +IOError+ is raised,
329  * otherwise an Array is returned that contains the image data,
330  * the image width, the image height, a boolean that indicates
331  * whether the image has an alpha channel or not and a hash
332  * that contains the compression options that were used to store
333  * the image (see Eet::File#write_image).
334  */
335 static VALUE
336 c_read_image (VALUE self, VALUE key)
337 {
338         VALUE ret, comp;
339         Eet_File **ef = NULL;
340         void *data;
341         char *ckey;
342         int w = 0, h = 0, has_alpha = 0, level = 0, quality = 0, lossy = 0;
343
344         Data_Get_Struct (self, Eet_File *, ef);
345         CHECK_CLOSED (ef);
346
347         ckey = StringValuePtr (key);
348         CHECK_KEY (key);
349
350         data = eet_data_image_read (*ef, ckey, &w, &h,
351                                     &has_alpha, &level, &quality,
352                                     &lossy);
353         if (!data)
354                 rb_raise (rb_eIOError, "cannot read entry - %s", ckey);
355
356         comp = rb_hash_new ();
357         rb_hash_aset (comp, sym_lossy, INT2FIX (lossy));
358         rb_hash_aset (comp, sym_level, INT2FIX (level));
359         rb_hash_aset (comp, sym_quality, INT2FIX (quality));
360
361         ret = rb_ary_new3 (5, rb_str_new (data, w * h * 4),
362                                INT2FIX (w), INT2FIX (h),
363                                has_alpha ? Qtrue : Qfalse, comp);
364         free (data);
365
366         return ret;
367 }
368
369 /*
370  * call-seq:
371  *  ef.write_image(key, image_data, w, h [, alpha] [, comp] ) -> integer
372  *
373  * Stores _image_data_ with width _w_ and height _h_ in _ef_ as _key_.
374  * Pass true for _alpha_ if the image contains an alpha channel.
375  * You can control how the image is compressed by passing _comp_, which
376  * is a hash whose :lossy entry is true if the image should be
377  * compressed lossily. If :lossy is true, the :quality entry
378  * (0-100, default 100) sets the compression quality.
379  * If :lossy is false, the :level entry (0-9, default 9) sets the
380  * compression level. If _comp_ isn't passed, then the
381  * image is stored losslessly.
382  * If the data cannot be written, an +IOError+ is raised,
383  * otherwise a the number of bytes written is returned.
384  */
385 static VALUE
386 c_write_image (int argc, VALUE *argv, VALUE self)
387 {
388         VALUE key = Qnil, buf = Qnil, w = Qnil, h = Qnil, has_alpha = Qnil;
389         VALUE comp = Qnil, tmp;
390         Eet_File **ef = NULL;
391         char *ckey, *cbuf;
392         int n, lossy = 0, level = 9, quality = 100;
393
394         Data_Get_Struct (self, Eet_File *, ef);
395         CHECK_CLOSED (ef);
396
397         rb_scan_args (argc, argv, "42", &key, &buf, &w, &h, &has_alpha,
398                       &comp);
399
400         if (NIL_P (has_alpha))
401                 has_alpha = Qfalse;
402
403         ckey = StringValuePtr (key);
404         CHECK_KEY (key);
405         cbuf = StringValuePtr (buf);
406         Check_Type (w, T_FIXNUM);
407         Check_Type (h, T_FIXNUM);
408
409         if (!NIL_P (comp)) {
410                 Check_Type (comp, T_HASH);
411
412                 tmp = rb_hash_aref (comp, sym_lossy);
413                 if (!NIL_P (tmp))
414                         lossy = FIX2INT (tmp);
415
416                 tmp = rb_hash_aref (comp, sym_level);
417                 if (!NIL_P (tmp))
418                         level = FIX2INT (tmp);
419
420                 tmp = rb_hash_aref (comp, sym_quality);
421                 if (!NIL_P (tmp))
422                         quality = FIX2INT (tmp);
423         }
424
425         if (!RSTRING (buf)->len)
426                 return INT2FIX (0);
427
428         n = eet_data_image_write (*ef, ckey, cbuf,
429                                   FIX2INT (w), FIX2INT (h),
430                                   has_alpha == Qtrue,
431                                   level, quality, lossy);
432         if (!n)
433                 rb_raise (rb_eIOError, "couldn't write to file");
434         else
435                 return INT2FIX (n);
436 }
437
438 static VALUE
439 stream_serialize (VALUE self)
440 {
441         VALUE ret;
442         struct RArray *stream;
443         long i;
444
445         ret = rb_str_new2 ("");
446
447         stream = RARRAY (self);
448         if (!stream->len)
449                 return ret;
450
451         for (i = 0; i < stream->len; i++) {
452                 VALUE str = rb_funcall (stream->ptr[i], id_serialize, 0, NULL);
453
454                 rb_str_append (ret, str);
455         }
456
457         return ret;
458 }
459
460 static VALUE
461 chunk_init (VALUE self, VALUE tag, VALUE data)
462 {
463         unsigned long len;
464
465         StringValue (tag);
466         StringValue (data);
467
468         if (rb_funcall (tag, id_include, 1, INT2FIX (0)) == Qtrue) \
469                 rb_raise (rb_eArgError, "tag must not contain binary zeroes");
470
471         /* libeet uses a signed 32bit integer to store the
472          * chunk size, so make sure we don't overflow it
473          */
474         len = RSTRING (tag)->len + 1 + RSTRING (data)->len;
475         if (len < 0 || len >= 2147483647L)
476                 rb_raise (rb_eArgError, "tag or data too long");
477
478         rb_ivar_set (self, id_tag, rb_str_dup_frozen (tag));
479         rb_ivar_set (self, id_data, rb_str_dup_frozen (data));
480
481         return self;
482 }
483
484 static VALUE
485 chunk_serialize (VALUE self)
486 {
487         VALUE tmp, ret;
488         unsigned int size, buf_len;
489         unsigned char *buf;
490         struct RString *tag, *data;
491
492         tmp = rb_ivar_get (self, id_tag);
493         StringValue (tmp);
494         tag = RSTRING (tmp);
495
496         tmp = rb_ivar_get (self, id_data);
497         StringValue (tmp);
498         data = RSTRING (tmp);
499
500         buf_len = 9 + tag->len + data->len;
501         ret = rb_str_buf_new (buf_len);
502
503         buf = RSTRING (ret)->ptr;
504         RSTRING (ret)->len = buf_len;
505
506         memcpy (buf, "CHnK", 4);
507         buf += 4;
508
509         size = tag->len + data->len + 1;
510         size = BSWAP32 (size);
511         memcpy (buf, &size, 4);
512         buf += 4;
513
514         memcpy (buf, tag->ptr, tag->len);
515         buf += tag->len;
516
517         *buf++ = 0;
518
519         memcpy (buf, data->ptr, data->len);
520
521         return ret;
522 }
523
524 static int
525 for_each_prop (VALUE tag, VALUE arg, VALUE stream)
526 {
527         VALUE value, type, chunks;
528         long len, i;
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         chunks = rb_funcall (value, id_to_eet_chunks, 2, tag, type);
539
540         len = RARRAY (chunks)->len;
541
542         for (i = 0; i < len; i++)
543                 rb_funcall (stream, id_push, 1, rb_ary_entry (chunks, i));
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_funcall (stream, id_serialize, 0);
591         chunk = rb_class_new_instance (2, args, cChunk);
592
593         stream = rb_class_new_instance (1, &chunk, cStream);
594
595         return rb_funcall (stream, id_serialize, 0);
596 }
597
598 static VALUE
599 float_to_eet_chunks (int argc, VALUE *argv, VALUE self)
600 {
601         VALUE tag, type = Qnil, args[2], chunk;
602         char buf[65], *loc;
603         double d;
604         int len;
605
606         rb_scan_args (argc, argv, "11", &tag, &type);
607
608         d = NUM2DBL (self);
609
610         /* switch locale to make sure we get proper snprintf output */
611         loc = setlocale (LC_NUMERIC, "C");
612
613         len = snprintf (buf, sizeof (buf) - 1, "%a",
614                         type == sym_double ? d : (float) d);
615
616         if (loc)
617                 setlocale (LC_NUMERIC, loc);
618
619         buf[++len] = '\0';
620
621         args[0] = tag;
622         args[1] = rb_str_new (buf, len);
623         chunk = rb_class_new_instance (2, args, cChunk);
624
625         return rb_ary_new3 (1, chunk);
626 }
627
628 void
629 Init_eet_ext ()
630 {
631         VALUE m, c;
632
633         m = rb_define_module ("Eet");
634
635         c = rb_define_class_under (m, "File", rb_cObject);
636         rb_define_alloc_func (c, c_alloc);
637         rb_define_singleton_method (c, "open", c_open, -1);
638         rb_define_method (c, "initialize", c_init, -1);
639         rb_define_method (c, "close", c_close, 0);
640         rb_define_method (c, "list", c_list, -1);
641         rb_define_method (c, "delete", c_delete, 1);
642         rb_define_method (c, "read", c_read, 1);
643         rb_define_method (c, "write", c_write, -1);
644         rb_define_method (c, "read_image", c_read_image, 1);
645         rb_define_method (c, "write_image", c_write_image, -1);
646
647         cStream = rb_define_class_under (m, "Stream", rb_cArray);
648         rb_define_method (cStream, "serialize", stream_serialize, 0);
649
650         cChunk = rb_define_class_under (m, "Chunk", rb_cObject);
651         rb_define_method (cChunk, "initialize", chunk_init, 2);
652         rb_define_method (cChunk, "serialize", chunk_serialize, 0);
653
654         rb_define_attr (cChunk, "tag", 1, 0);
655         rb_define_attr (cChunk, "data", 1, 0);
656
657         rb_define_method (rb_cObject, "to_eet", c_to_eet, 0);
658
659         rb_define_method (rb_cFloat, "to_eet_chunks", float_to_eet_chunks, -1);
660
661         eEetError = rb_define_class_under (m, "EetError", rb_eStandardError);
662         eNameError = rb_define_class_under (m, "NameError", eEetError);
663         ePropError = rb_define_class_under (m, "PropertyError", eEetError);
664
665         id_include = rb_intern ("include?");
666         id_serialize = rb_intern ("serialize");
667         id_push = rb_intern ("push");
668         id_keys = rb_intern ("keys");
669         id_to_eet_chunks = rb_intern ("to_eet_chunks");
670         id_to_eet_name = rb_intern ("to_eet_name");
671         id_to_eet_properties = rb_intern ("to_eet_properties");
672         id_tag = rb_intern ("@tag");
673         id_data = rb_intern ("@data");
674         sym_lossy = ID2SYM (rb_intern ("lossy"));
675         sym_level = ID2SYM (rb_intern ("level"));
676         sym_quality =  ID2SYM (rb_intern ("quality"));
677         sym_double = ID2SYM (rb_intern ("double"));
678 }