/* * Copyright (c) 2005-2007 Tilman Sauerbeck (tilman at code-monkey de) * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #define CHECK_KEY(key) \ if (rb_funcall (key, id_include, 1, INT2FIX (0)) == Qtrue) \ rb_raise (rb_eArgError, "key must not contain binary zeroes"); #define CHECK_CLOSED(ef) \ if (!*(ef)) \ rb_raise (rb_eIOError, "closed stream"); #define CHECK_READABLE(ef) \ switch (eet_mode_get (*ef)) { \ case EET_FILE_MODE_READ: \ case EET_FILE_MODE_READ_WRITE: \ break; \ default: \ rb_raise (rb_eIOError, "permission denied"); \ } #ifdef WORDS_BIGENDIAN # define BSWAP32(x) \ ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \ (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24)) #else /* !WORDS_BIGENDIAN */ # define BSWAP32(x) (x) #endif /* WORDS_BIGENDIAN */ static VALUE c_close (VALUE self); static VALUE cStream, cChunk, eEetError, eNameError, ePropError, sym_lossy, sym_level, sym_quality, sym_char, sym_short, sym_long_long, sym_double; static ID id_include, id_to_s, id_keys, id_pack, id_to_eet_chunks, id_to_eet_name, id_to_eet_properties, id_tag, id_data; static void c_free (Eet_File **ef) { if (*ef) { eet_close (*ef); *ef = NULL; eet_shutdown (); } free (ef); } static VALUE c_alloc (VALUE klass) { Eet_File **ef = NULL; return Data_Make_Struct (klass, Eet_File *, NULL, c_free, ef); } /* * call-seq: * Eet::File.open(file [, mode] ) -> ef or nil * Eet::File.open(file [, mode] ) { |ef| block } -> nil * * If a block isn't specified, Eet::File.open is a synonym for * Eet::File.new. * If a block is given, it will be invoked with the * Eet::File object as a parameter, and the file will be * automatically closed when the block terminates. The call always * returns +nil+ in this case. */ static VALUE c_open (int argc, VALUE *argv, VALUE klass) { VALUE obj = rb_class_new_instance (argc, argv, klass); if (rb_block_given_p ()) return rb_ensure (rb_yield, obj, c_close, obj); else return obj; } /* * call-seq: * Eet::File.new(file [, mode] ) -> ef or nil * * Creates an Eet::File object for _file_. * * _file_ is opened with the specified mode (defaulting to "r"). * Possible values for _mode_ are "r" for read-only access, * "w" for write-only access and "r+" for read/write access. */ static VALUE c_init (int argc, VALUE *argv, VALUE self) { VALUE file = Qnil, mode = Qnil; Eet_File **ef = NULL; Eet_File_Mode m = EET_FILE_MODE_READ; const char *tmp, *cfile; Data_Get_Struct (self, Eet_File *, ef); rb_scan_args (argc, argv, "11", &file, &mode); cfile = StringValuePtr (file); if (!NIL_P (mode)) { tmp = StringValuePtr (mode); if (!strcmp (tmp, "r+")) m = EET_FILE_MODE_READ_WRITE; else if (!strcmp (tmp, "w")) m = EET_FILE_MODE_WRITE; else if (strcmp (tmp, "r")) rb_raise (rb_eArgError, "illegal access mode %s", tmp); } eet_init (); *ef = eet_open (cfile, m); if (!*ef) { switch (m) { case EET_FILE_MODE_READ_WRITE: case EET_FILE_MODE_WRITE: tmp = "Permission denied - %s"; break; default: tmp = "File not found - %s"; break; } rb_raise (rb_eRuntimeError, tmp, cfile); } return self; } /* * call-seq: * ef.close -> ef * * Closes _ef_ and flushes any pending writes. * _ef_ is unavailable for any further data operations; * an +IOError+ is raised if such an attempt is made. * * Eet::File objects are automatically closed when they * are claimed by the garbage collector. */ static VALUE c_close (VALUE self) { Eet_File **ef = NULL; Data_Get_Struct (self, Eet_File *, ef); CHECK_CLOSED (ef); eet_close (*ef); *ef = NULL; eet_shutdown (); return self; } static VALUE get_keys (Eet_File *ef, const char *glob) { VALUE ret; char **keys; int i, count = 0; keys = eet_list (ef, glob, &count); ret = rb_ary_new2 (count); for (i = 0; i < count; i++) rb_ary_store (ret, i, rb_str_new2 (keys[i])); free (keys); return ret; } /* * call-seq: * ef.entries -> array * * Returns an Array with the keys of the entries in _ef_. * If the keys cannot be retrieved, an +IOError+ is raised. */ static VALUE c_entries (VALUE self) { Eet_File **ef = NULL; Data_Get_Struct (self, Eet_File *, ef); CHECK_CLOSED (ef); CHECK_READABLE (ef); return get_keys (*ef, "*"); } /* * call-seq: * ef[glob] -> array * * Returns an Array with the keys of entries in _ef_ that match the * shell glob _glob_. * If the keys cannot be retrieved, an +IOError+ is raised. */ static VALUE c_glob (VALUE self, VALUE glob) { Eet_File **ef = NULL; Data_Get_Struct (self, Eet_File *, ef); CHECK_CLOSED (ef); CHECK_READABLE (ef); return get_keys (*ef, StringValuePtr (glob)); } /* * call-seq: * ef.delete(key) -> ef * * Deletes the entry from _ef_ that is stored as _key_. * If the entry cannot be deleted, an +IOError+ is raised, * otherwise _ef_ is returned. */ static VALUE c_delete (VALUE self, VALUE key) { Eet_File **ef = NULL; char *ckey; Data_Get_Struct (self, Eet_File *, ef); CHECK_CLOSED (ef); ckey = StringValuePtr (key); CHECK_KEY (key); if (!eet_delete (*ef, ckey)) rb_raise (rb_eIOError, "cannot delete entry - %s", ckey); return self; } /* * call-seq: * ef.read(key) -> string * * Reads an entry from _ef_ that is stored as _key_. * If the data cannot be read, an +IOError+ is raised, * otherwise a String is returned that contains the data. */ static VALUE c_read (VALUE self, VALUE key) { VALUE ret; Eet_File **ef = NULL; void *data; char *ckey; int size = 0; Data_Get_Struct (self, Eet_File *, ef); CHECK_CLOSED (ef); ckey = StringValuePtr (key); CHECK_KEY (key); data = eet_read (*ef, ckey, &size); if (!data) rb_raise (rb_eIOError, "cannot read entry - %s", ckey); ret = rb_str_new (data, size); free (data); return ret; } /* * call-seq: * ef.write(key, data [, compress] ) -> integer * * Stores _data_ in _ef_ as _key_. * If _compress_ is true (which is the default), the data will be * compressed. * If the data cannot be written, an +IOError+ is raised, * otherwise a the number of bytes written is returned. */ static VALUE c_write (int argc, VALUE *argv, VALUE self) { VALUE key = Qnil, buf = Qnil, comp = Qnil; Eet_File **ef = NULL; char *ckey, *cbuf; int n; Data_Get_Struct (self, Eet_File *, ef); CHECK_CLOSED (ef); rb_scan_args (argc, argv, "21", &key, &buf, &comp); if (NIL_P (comp)) comp = Qtrue; ckey = StringValuePtr (key); CHECK_KEY (key); cbuf = StringValuePtr (buf); n = eet_write (*ef, ckey, cbuf, RSTRING (buf)->len, comp == Qtrue); if (!n) rb_raise (rb_eIOError, "couldn't write to file"); else return INT2FIX (n); } /* * call-seq: * ef.read_image(key) -> array * * Reads an image entry from _ef_ that is stored as _key_. * If the data cannot be read, an +IOError+ is raised, * otherwise an Array is returned that contains the image data, * the image width, the image height, a boolean that indicates * whether the image has an alpha channel or not and a hash * that contains the compression options that were used to store * the image (see Eet::File#write_image). */ static VALUE c_read_image (VALUE self, VALUE key) { VALUE ret, comp; Eet_File **ef = NULL; void *data; char *ckey; unsigned int w = 0, h = 0; int has_alpha = 0, level = 0, quality = 0, lossy = 0; Data_Get_Struct (self, Eet_File *, ef); CHECK_CLOSED (ef); ckey = StringValuePtr (key); CHECK_KEY (key); data = eet_data_image_read (*ef, ckey, &w, &h, &has_alpha, &level, &quality, &lossy); if (!data) rb_raise (rb_eIOError, "cannot read entry - %s", ckey); comp = rb_hash_new (); rb_hash_aset (comp, sym_lossy, INT2FIX (lossy)); rb_hash_aset (comp, sym_level, INT2FIX (level)); rb_hash_aset (comp, sym_quality, INT2FIX (quality)); ret = rb_ary_new3 (5, rb_str_new (data, w * h * 4), INT2FIX (w), INT2FIX (h), has_alpha ? Qtrue : Qfalse, comp); free (data); return ret; } /* * call-seq: * ef.write_image(key, image_data, w, h [, alpha] [, comp] ) -> integer * * Stores _image_data_ with width _w_ and height _h_ in _ef_ as _key_. * Pass true for _alpha_ if the image contains an alpha channel. * You can control how the image is compressed by passing _comp_, which * is a hash whose :lossy entry is true if the image should be * compressed lossily. If :lossy is true, the :quality entry * (0-100, default 100) sets the compression quality. * If :lossy is false, the :level entry (0-9, default 9) sets the * compression level. If _comp_ isn't passed, then the * image is stored losslessly. * If the data cannot be written, an +IOError+ is raised, * otherwise a the number of bytes written is returned. */ static VALUE c_write_image (int argc, VALUE *argv, VALUE self) { VALUE key = Qnil, buf = Qnil, w = Qnil, h = Qnil, has_alpha = Qnil; VALUE comp = Qnil, tmp; Eet_File **ef = NULL; char *ckey, *cbuf; int n, lossy = 0, level = 9, quality = 100; Data_Get_Struct (self, Eet_File *, ef); CHECK_CLOSED (ef); rb_scan_args (argc, argv, "42", &key, &buf, &w, &h, &has_alpha, &comp); if (NIL_P (has_alpha)) has_alpha = Qfalse; ckey = StringValuePtr (key); CHECK_KEY (key); cbuf = StringValuePtr (buf); Check_Type (w, T_FIXNUM); Check_Type (h, T_FIXNUM); if (!NIL_P (comp)) { Check_Type (comp, T_HASH); tmp = rb_hash_aref (comp, sym_lossy); if (!NIL_P (tmp)) lossy = FIX2INT (tmp); tmp = rb_hash_aref (comp, sym_level); if (!NIL_P (tmp)) level = FIX2INT (tmp); tmp = rb_hash_aref (comp, sym_quality); if (!NIL_P (tmp)) quality = FIX2INT (tmp); } if (!RSTRING (buf)->len) return INT2FIX (0); n = eet_data_image_write (*ef, ckey, cbuf, FIX2INT (w), FIX2INT (h), has_alpha == Qtrue, level, quality, lossy); if (!n) rb_raise (rb_eIOError, "couldn't write to file"); else return INT2FIX (n); } static VALUE chunk_init (VALUE self, VALUE tag, VALUE data) { long tag_len, data_len, tmp; StringValue (tag); StringValue (data); if (rb_funcall (tag, id_include, 1, INT2FIX (0)) == Qtrue) rb_raise (rb_eArgError, "tag must not contain binary zeroes"); /* libeet uses a signed 32bit integer to store the * chunk size, so make sure we don't overflow it */ tag_len = RSTRING (tag)->len; data_len = RSTRING (data)->len; tmp = tag_len + 1 + data_len; if (tmp < tag_len || tmp < data_len || tmp < 1 || tmp >= 2147483647L) rb_raise (rb_eArgError, "tag or data too long"); rb_ivar_set (self, id_tag, rb_str_dup_frozen (tag)); rb_ivar_set (self, id_data, rb_str_dup_frozen (data)); return self; } static VALUE chunk_to_s (VALUE self) { VALUE tmp, ret; unsigned int size, buf_len; unsigned char *buf; struct RString *tag, *data; tmp = rb_ivar_get (self, id_tag); tag = RSTRING (tmp); tmp = rb_ivar_get (self, id_data); data = RSTRING (tmp); buf_len = 9 + tag->len + data->len; ret = rb_str_buf_new (buf_len); buf = (unsigned char *) RSTRING (ret)->ptr; RSTRING (ret)->len = buf_len; memcpy (buf, "CHnK", 4); buf += 4; size = tag->len + data->len + 1; size = BSWAP32 (size); memcpy (buf, &size, 4); buf += 4; memcpy (buf, tag->ptr, tag->len); buf += tag->len; *buf++ = 0; memcpy (buf, data->ptr, data->len); return ret; } static int for_each_prop (VALUE tag, VALUE arg, VALUE stream) { VALUE value, type, tmp; if (rb_obj_is_kind_of (arg, rb_cArray) == Qfalse) rb_raise (ePropError, "hash value is not an array"); value = rb_ary_entry (arg, 0); if (NIL_P (value)) return ST_CONTINUE; type = rb_ary_entry (arg, 1); tmp = rb_funcall (value, id_to_eet_chunks, 2, tag, type); rb_ary_concat (stream, tmp); return ST_CONTINUE; } /* * call-seq: * object.to_eet -> string * * Serializes the receiver to EET format. */ static VALUE c_to_eet (VALUE self) { VALUE props, name, stream, chunk, args[2]; #ifndef HAVE_RB_HASH_FOREACH struct RArray *keys; long i; #endif props = rb_funcall (self, id_to_eet_properties, 0); if (rb_obj_is_kind_of (props, rb_cHash) == Qfalse || !RHASH (props)->tbl->num_entries) rb_raise (ePropError, "invalid EET properties"); name = rb_funcall (self, id_to_eet_name, 0); StringValue (name); if (!RSTRING (name)->len || rb_funcall (name, id_include, 1, INT2FIX (0))) rb_raise (eNameError, "invalid EET name"); stream = rb_class_new_instance (0, NULL, cStream); #ifdef HAVE_RB_HASH_FOREACH rb_hash_foreach (props, for_each_prop, stream); #else keys = RARRAY (rb_funcall (props, id_keys, 0)); for (i = 0; i < keys->len; i++) for_each_prop (keys->ptr[i], rb_hash_aref (props, keys->ptr[i]), stream); #endif args[0] = name; args[1] = rb_ary_to_s (stream); rb_ary_clear (stream); /* give the GC a hand... */ chunk = rb_class_new_instance (2, args, cChunk); return rb_funcall (chunk, id_to_s, 0); } static VALUE int_to_eet_chunks (int argc, VALUE *argv, VALUE self) { VALUE tag, type = Qnil, ary, args[2], chunk; const char *cfmt = "V"; rb_scan_args (argc, argv, "11", &tag, &type); ary = rb_ary_new3 (1, self); if (type == sym_char) cfmt = "c"; else if (type == sym_short) cfmt = "v"; else if (type == sym_long_long) cfmt = "q"; args[0] = tag; args[1] = rb_funcall (ary, id_pack, 1, rb_str_new2 (cfmt)); chunk = rb_class_new_instance (2, args, cChunk); return rb_ary_new3 (1, chunk); } static VALUE float_to_eet_chunks (int argc, VALUE *argv, VALUE self) { VALUE tag, type = Qnil, args[2], chunk; char buf[65], *loc; double d; int len; rb_scan_args (argc, argv, "11", &tag, &type); d = NUM2DBL (self); /* switch locale to make sure we get proper snprintf output */ loc = setlocale (LC_NUMERIC, "C"); len = snprintf (buf, sizeof (buf) - 1, "%a", type == sym_double ? d : (float) d); if (loc) setlocale (LC_NUMERIC, loc); buf[++len] = '\0'; args[0] = tag; args[1] = rb_str_new (buf, len); chunk = rb_class_new_instance (2, args, cChunk); return rb_ary_new3 (1, chunk); } void Init_eet_ext () { VALUE m, c; m = rb_define_module ("Eet"); c = rb_define_class_under (m, "File", rb_cObject); rb_define_alloc_func (c, c_alloc); rb_define_singleton_method (c, "open", c_open, -1); rb_define_method (c, "initialize", c_init, -1); rb_define_method (c, "close", c_close, 0); rb_define_method (c, "entries", c_entries, 0); rb_define_method (c, "[]", c_glob, 1); rb_define_method (c, "delete", c_delete, 1); rb_define_method (c, "read", c_read, 1); rb_define_method (c, "write", c_write, -1); rb_define_method (c, "read_image", c_read_image, 1); rb_define_method (c, "write_image", c_write_image, -1); cStream = rb_define_class_under (m, "Stream", rb_cArray); cChunk = rb_define_class_under (m, "Chunk", rb_cObject); rb_define_method (cChunk, "initialize", chunk_init, 2); rb_define_method (cChunk, "to_s", chunk_to_s, 0); rb_define_attr (cChunk, "tag", 1, 0); rb_define_attr (cChunk, "data", 1, 0); rb_define_method (rb_cObject, "to_eet", c_to_eet, 0); rb_define_method (rb_cInteger, "to_eet_chunks", int_to_eet_chunks, -1); rb_define_method (rb_cFloat, "to_eet_chunks", float_to_eet_chunks, -1); eEetError = rb_define_class_under (m, "EetError", rb_eStandardError); eNameError = rb_define_class_under (m, "NameError", eEetError); ePropError = rb_define_class_under (m, "PropertyError", eEetError); id_include = rb_intern ("include?"); id_to_s = rb_intern ("to_s"); id_keys = rb_intern ("keys"); id_pack = rb_intern ("pack"); id_to_eet_chunks = rb_intern ("to_eet_chunks"); id_to_eet_name = rb_intern ("to_eet_name"); id_to_eet_properties = rb_intern ("to_eet_properties"); id_tag = rb_intern ("@tag"); id_data = rb_intern ("@data"); sym_lossy = ID2SYM (rb_intern ("lossy")); sym_level = ID2SYM (rb_intern ("level")); sym_quality = ID2SYM (rb_intern ("quality")); sym_char = ID2SYM (rb_intern ("char")); sym_short = ID2SYM (rb_intern ("short")); sym_long_long = ID2SYM (rb_intern ("long_long")); sym_double = ID2SYM (rb_intern ("double")); }