+/*
+ * $Id: ext.c 1 2005-03-26 01:45:38Z tilman $
+ *
+ * Copyright (c) 2005 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 <Eet.h>
+#include <ruby.h>
+
+#define CHECK_KEY(key) \
+ Check_Type ((key), T_STRING); \
+ if (rb_funcall ((key), rb_intern ("include?"), \
+ 1, INT2FIX (0)) == Qtrue) \
+ rb_raise (rb_eArgError, "key must not contain binary zeroes");
+
+static VALUE c_close (VALUE self);
+
+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;
+
+ Data_Get_Struct (self, Eet_File *, ef);
+
+ rb_scan_args (argc, argv, "11", &file, &mode);
+
+ Check_Type (file, T_STRING);
+
+ if (!NIL_P (mode)) {
+ Check_Type (mode, T_STRING);
+
+ 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 (StringValuePtr (file), 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, file);
+ }
+
+ 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);
+
+ if (!*ef)
+ rb_raise (rb_eIOError, "closed stream");
+ else {
+ eet_close (*ef);
+ *ef = NULL;
+
+ eet_shutdown ();
+ }
+
+ return self;
+}
+
+/*
+ * call-seq:
+ * ef.list([glob]) -> array
+ *
+ * Returns an Array of entries in _ef_ that match the shell glob
+ * _glob_ (defaulting to "*").
+ */
+static VALUE
+c_list (int argc, VALUE *argv, VALUE self)
+{
+ VALUE glob = Qnil, ret;
+ Eet_File **ef = NULL;
+ char **entries, *tmp = "*";
+ int i, count = 0;
+
+ Data_Get_Struct (self, Eet_File *, ef);
+
+ if (!*ef)
+ rb_raise (rb_eIOError, "closed stream");
+
+ switch (eet_mode_get (*ef)) {
+ case EET_FILE_MODE_READ:
+ case EET_FILE_MODE_READ_WRITE:
+ break;
+ default:
+ rb_raise (rb_eIOError, "cannot list entries");
+ }
+
+ rb_scan_args (argc, argv, "01", &glob);
+
+ if (!NIL_P (glob)) {
+ Check_Type (glob, T_STRING);
+ tmp = StringValuePtr (glob);
+ }
+
+ ret = rb_ary_new ();
+
+ entries = eet_list (*ef, tmp, &count);
+
+ for (i = 0; i < count; i++)
+ rb_ary_push (ret, rb_str_new2 (entries[i]));
+
+ free (entries);
+
+ return ret;
+}
+
+/*
+ * 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 *tmp;
+
+ Data_Get_Struct (self, Eet_File *, ef);
+
+ if (!*ef)
+ rb_raise (rb_eIOError, "closed stream");
+
+ CHECK_KEY (key);
+
+ tmp = StringValuePtr (key);
+
+ if (!eet_delete (*ef, tmp))
+ rb_raise (rb_eIOError, "cannot delete entry - %s", tmp);
+
+ 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;
+ int size = 0;
+
+ Data_Get_Struct (self, Eet_File *, ef);
+
+ if (!*ef)
+ rb_raise (rb_eIOError, "closed stream");
+
+ CHECK_KEY (key);
+
+ data = eet_read (*ef, StringValuePtr (key), &size);
+ if (!data)
+ rb_raise (rb_eIOError, "cannot read entry - %s", key);
+
+ 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;
+ int n;
+
+ Data_Get_Struct (self, Eet_File *, ef);
+
+ if (!*ef)
+ rb_raise (rb_eIOError, "closed stream");
+
+ rb_scan_args (argc, argv, "21", &key, &buf, &comp);
+
+ if (NIL_P (comp))
+ comp = Qtrue;
+
+ CHECK_KEY (key);
+ Check_Type (buf, T_STRING);
+
+ n = eet_write (*ef, StringValuePtr (key),
+ StringValuePtr (buf), 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 and a boolean that indicates
+ * whether the image has an alpha channel or not.
+ */
+static VALUE
+c_read_image (VALUE self, VALUE key)
+{
+ VALUE ret;
+ Eet_File **ef = NULL;
+ void *data;
+ int w = 0, h = 0, has_alpha = 0;
+
+ Data_Get_Struct (self, Eet_File *, ef);
+
+ if (!*ef)
+ rb_raise (rb_eIOError, "closed stream");
+
+ CHECK_KEY (key);
+
+ data = eet_data_image_read (*ef, StringValuePtr (key), &w, &h,
+ &has_alpha, NULL, NULL, NULL);
+ if (!data)
+ rb_raise (rb_eIOError, "cannot read entry - %s", key);
+
+ ret = rb_ary_new3 (4, rb_str_new (data, w * h * 4),
+ INT2FIX (w), INT2FIX (h),
+ has_alpha ? Qtrue : Qfalse);
+ free (data);
+
+ return ret;
+}
+
+/*
+ * call-seq:
+ * ef.write_image(key, image_data, w, h [, alpha] ) -> 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.
+ * 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;
+ Eet_File **ef = NULL;
+ int n;
+
+ Data_Get_Struct (self, Eet_File *, ef);
+
+ if (!*ef)
+ rb_raise (rb_eIOError, "closed stream");
+
+ rb_scan_args (argc, argv, "41", &key, &buf, &w, &h, &has_alpha);
+
+ if (NIL_P (has_alpha))
+ has_alpha = Qfalse;
+
+ CHECK_KEY (key);
+ Check_Type (buf, T_STRING);
+ Check_Type (w, T_FIXNUM);
+ Check_Type (h, T_FIXNUM);
+
+ if (!RSTRING (buf)->len)
+ return INT2FIX (0);
+
+ n = eet_data_image_write (*ef, StringValuePtr (key),
+ StringValuePtr (buf),
+ FIX2INT (w), FIX2INT (h),
+ has_alpha == Qtrue, 9, 0, 0);
+ if (!n)
+ rb_raise (rb_eIOError, "couldn't write to file");
+ else
+ return INT2FIX (n);
+}
+
+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, "list", c_list, -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);
+}