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