2 * $Id: ext.c 54 2005-06-02 20:05:38Z tilman $
4 * Copyright (c) 2005 Tilman Sauerbeck (tilman at code-monkey de)
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:
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
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.
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");
34 #define CHECK_CLOSED(ef) \
36 rb_raise (rb_eIOError, "closed stream");
38 #ifdef WORDS_BIGENDIAN
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 */
46 static VALUE c_close (VALUE self);
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,
56 c_free (Eet_File **ef)
73 return Data_Make_Struct (klass, Eet_File *, NULL, c_free, ef);
78 * Eet::File.open(file [, mode] ) -> ef or nil
79 * Eet::File.open(file [, mode] ) { |ef| block } -> nil
81 * If a block isn't specified, Eet::File.open is a synonym for
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.
89 c_open (int argc, VALUE *argv, VALUE klass)
91 VALUE obj = rb_class_new_instance (argc, argv, klass);
93 if (rb_block_given_p ())
94 return rb_ensure (rb_yield, obj, c_close, obj);
101 * Eet::File.new(file [, mode] ) -> ef or nil
103 * Creates an Eet::File object for _file_.
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.
110 c_init (int argc, VALUE *argv, VALUE self)
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;
117 Data_Get_Struct (self, Eet_File *, ef);
119 rb_scan_args (argc, argv, "11", &file, &mode);
121 cfile = StringValuePtr (file);
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);
135 *ef = eet_open (cfile, m);
138 case EET_FILE_MODE_READ_WRITE:
139 case EET_FILE_MODE_WRITE:
140 tmp = "Permission denied - %s";
143 tmp = "File not found - %s";
147 rb_raise (rb_eRuntimeError, tmp, cfile);
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.
161 * Eet::File objects are automatically closed when they
162 * are claimed by the garbage collector.
167 Eet_File **ef = NULL;
169 Data_Get_Struct (self, Eet_File *, ef);
182 * ef.list([glob]) -> array
184 * Returns an Array of entries in _ef_ that match the shell glob
185 * _glob_ (defaulting to "*").
188 c_list (int argc, VALUE *argv, VALUE self)
190 VALUE glob = Qnil, ret;
191 Eet_File **ef = NULL;
192 char **entries, *tmp = "*";
195 Data_Get_Struct (self, Eet_File *, ef);
198 switch (eet_mode_get (*ef)) {
199 case EET_FILE_MODE_READ:
200 case EET_FILE_MODE_READ_WRITE:
203 rb_raise (rb_eIOError, "cannot list entries");
206 rb_scan_args (argc, argv, "01", &glob);
209 tmp = StringValuePtr (glob);
213 entries = eet_list (*ef, tmp, &count);
215 for (i = 0; i < count; i++)
216 rb_ary_push (ret, rb_str_new2 (entries[i]));
225 * ef.delete(key) -> ef
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.
232 c_delete (VALUE self, VALUE key)
234 Eet_File **ef = NULL;
237 Data_Get_Struct (self, Eet_File *, ef);
240 ckey = StringValuePtr (key);
243 if (!eet_delete (*ef, ckey))
244 rb_raise (rb_eIOError, "cannot delete entry - %s", ckey);
251 * ef.read(key) -> string
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.
258 c_read (VALUE self, VALUE key)
261 Eet_File **ef = NULL;
266 Data_Get_Struct (self, Eet_File *, ef);
269 ckey = StringValuePtr (key);
272 data = eet_read (*ef, ckey, &size);
274 rb_raise (rb_eIOError, "cannot read entry - %s", ckey);
276 ret = rb_str_new (data, size);
285 * ef.write(key, data [, compress] ) -> integer
287 * Stores _data_ in _ef_ as _key_.
288 * If _compress_ is true (which is the default), the data will be
290 * If the data cannot be written, an +IOError+ is raised,
291 * otherwise a the number of bytes written is returned.
294 c_write (int argc, VALUE *argv, VALUE self)
296 VALUE key = Qnil, buf = Qnil, comp = Qnil;
297 Eet_File **ef = NULL;
301 Data_Get_Struct (self, Eet_File *, ef);
304 rb_scan_args (argc, argv, "21", &key, &buf, &comp);
309 ckey = StringValuePtr (key);
311 cbuf = StringValuePtr (buf);
313 n = eet_write (*ef, ckey,
314 cbuf, RSTRING (buf)->len,
317 rb_raise (rb_eIOError, "couldn't write to file");
324 * ef.read_image(key) -> array
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).
335 c_read_image (VALUE self, VALUE key)
338 Eet_File **ef = NULL;
341 int w = 0, h = 0, has_alpha = 0, level = 0, quality = 0, lossy = 0;
343 Data_Get_Struct (self, Eet_File *, ef);
346 ckey = StringValuePtr (key);
349 data = eet_data_image_read (*ef, ckey, &w, &h,
350 &has_alpha, &level, &quality,
353 rb_raise (rb_eIOError, "cannot read entry - %s", ckey);
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));
360 ret = rb_ary_new3 (5, rb_str_new (data, w * h * 4),
361 INT2FIX (w), INT2FIX (h),
362 has_alpha ? Qtrue : Qfalse, comp);
370 * ef.write_image(key, image_data, w, h [, alpha] [, comp] ) -> integer
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.
385 c_write_image (int argc, VALUE *argv, VALUE self)
387 VALUE key = Qnil, buf = Qnil, w = Qnil, h = Qnil, has_alpha = Qnil;
388 VALUE comp = Qnil, tmp;
389 Eet_File **ef = NULL;
391 int n, lossy = 0, level = 9, quality = 100;
393 Data_Get_Struct (self, Eet_File *, ef);
396 rb_scan_args (argc, argv, "42", &key, &buf, &w, &h, &has_alpha,
399 if (NIL_P (has_alpha))
402 ckey = StringValuePtr (key);
404 cbuf = StringValuePtr (buf);
405 Check_Type (w, T_FIXNUM);
406 Check_Type (h, T_FIXNUM);
409 Check_Type (comp, T_HASH);
411 tmp = rb_hash_aref (comp, sym_lossy);
413 lossy = FIX2INT (tmp);
415 tmp = rb_hash_aref (comp, sym_level);
417 level = FIX2INT (tmp);
419 tmp = rb_hash_aref (comp, sym_quality);
421 quality = FIX2INT (tmp);
424 if (!RSTRING (buf)->len)
427 n = eet_data_image_write (*ef, ckey, cbuf,
428 FIX2INT (w), FIX2INT (h),
430 level, quality, lossy);
432 rb_raise (rb_eIOError, "couldn't write to file");
438 stream_serialize (VALUE self)
441 struct RArray *stream;
444 ret = rb_str_new2 ("");
446 stream = RARRAY (self);
450 for (i = 0; i < stream->len; i++) {
451 VALUE str = rb_funcall (stream->ptr[i], id_serialize, 0, NULL);
453 rb_str_append (ret, str);
460 chunk_init (VALUE self, VALUE tag, VALUE data)
467 if (rb_funcall (tag, id_include, 1, INT2FIX (0)) == Qtrue) \
468 rb_raise (rb_eArgError, "tag must not contain binary zeroes");
470 /* libeet uses a signed 32bit integer to store the
471 * chunk size, so make sure we don't overflow it
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");
477 rb_ivar_set (self, id_tag, rb_str_dup_frozen (tag));
478 rb_ivar_set (self, id_data, rb_str_dup_frozen (data));
484 chunk_serialize (VALUE self)
487 unsigned int size, buf_len;
489 struct RString *tag, *data;
491 tmp = rb_ivar_get (self, id_tag);
495 tmp = rb_ivar_get (self, id_data);
497 data = RSTRING (tmp);
499 buf_len = 9 + tag->len + data->len;
500 ret = rb_str_buf_new (buf_len);
502 buf = RSTRING (ret)->ptr;
503 RSTRING (ret)->len = buf_len;
505 memcpy (buf, "CHnK", 4);
508 size = tag->len + data->len + 1;
509 size = BSWAP32 (size);
510 memcpy (buf, &size, 4);
513 memcpy (buf, tag->ptr, tag->len);
518 memcpy (buf, data->ptr, data->len);
524 for_each_prop (VALUE tag, VALUE arg, VALUE stream)
526 VALUE value, type, chunks;
529 if (rb_obj_is_kind_of (arg, rb_cArray) == Qfalse)
530 rb_raise (ePropError, "hash value is not an array");
532 value = rb_ary_entry (arg, 0);
536 type = rb_ary_entry (arg, 1);
537 chunks = rb_funcall (value, id_to_eet_chunks, 2, tag, type);
539 len = RARRAY (chunks)->len;
541 for (i = 0; i < len; i++)
542 rb_funcall (stream, id_push, 1, rb_ary_entry (chunks, i));
549 * object.to_eet -> string
551 * Serializes the receiver to EET format.
554 c_to_eet (VALUE self)
556 VALUE props, name, stream, chunk, args[2];
557 #ifndef HAVE_RB_HASH_FOREACH
562 props = rb_funcall (self, id_to_eet_properties, 0);
564 if (rb_obj_is_kind_of (props, rb_cHash) == Qfalse ||
565 !RHASH (props)->tbl->num_entries)
566 rb_raise (ePropError, "invalid EET properties");
568 name = rb_funcall (self, id_to_eet_name, 0);
571 if (!RSTRING (name)->len ||
572 rb_funcall (name, id_include, 1, INT2FIX (0)))
573 rb_raise (eNameError, "invalid EET name");
575 stream = rb_class_new_instance (0, NULL, cStream);
577 #ifdef HAVE_RB_HASH_FOREACH
578 rb_hash_foreach (props, for_each_prop, stream);
580 keys = RARRAY (rb_funcall (props, id_keys, 0));
582 for (i = 0; i < keys->len; i++)
583 for_each_prop (keys->ptr[i],
584 rb_hash_aref (props, keys->ptr[i]),
589 args[1] = rb_funcall (stream, id_serialize, 0);
590 chunk = rb_class_new_instance (2, args, cChunk);
592 stream = rb_class_new_instance (1, &chunk, cStream);
594 return rb_funcall (stream, id_serialize, 0);
602 m = rb_define_module ("Eet");
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);
616 cStream = rb_define_class_under (m, "Stream", rb_cArray);
617 rb_define_method (cStream, "serialize", stream_serialize, 0);
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);
623 rb_define_attr (cChunk, "tag", 1, 0);
624 rb_define_attr (cChunk, "data", 1, 0);
626 rb_define_method (rb_cObject, "to_eet", c_to_eet, 0);
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);
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"));