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