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