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