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