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