Welcome Ethumb, it's ready to get out of PROTO.
[framework/uifw/ethumb.git] / src / lib / Ethumb.c
1 /**
2  * @file
3  *
4  * Copyright (C) 2009 by ProFUSION embedded systems
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or (at your
9  * option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,  but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13  * or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License
14  * for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
19  * USA.
20  *
21  * @author Rafael Antognolli <antognolli@profusion.mobi>
22  */
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 #include <Eina.h>
27 #include <eina_safety_checks.h>
28 #include "Ethumb.h"
29 #include "ethumb_private.h"
30 #include "Ethumb_Plugin.h"
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <limits.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <errno.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <dirent.h>
40 #include <dlfcn.h>
41 #include "md5.h"
42
43 #ifndef PATH_MAX
44 #define PATH_MAX 4096
45 #endif
46
47 #include <Ecore.h>
48 #include <Evas.h>
49 #include <Ecore_Evas.h>
50 #include <Ecore_File.h>
51 #include <Edje.h>
52
53 static int _log_dom = -1;
54 #define DBG(...) EINA_LOG_DOM_DBG(_log_dom, __VA_ARGS__)
55 #define INF(...) EINA_LOG_DOM_INFO(_log_dom, __VA_ARGS__)
56 #define WRN(...) EINA_LOG_DOM_WARN(_log_dom, __VA_ARGS__)
57 #define ERR(...) EINA_LOG_DOM_ERR(_log_dom, __VA_ARGS__)
58
59 static int initcount = 0;
60 static const char *_home_thumb_dir = NULL;
61 static const char *_thumb_category_normal = NULL;
62 static const char *_thumb_category_large = NULL;
63
64 static const int THUMB_SIZE_NORMAL = 128;
65 static const int THUMB_SIZE_LARGE = 256;
66
67 static Eina_Hash *_plugins_ext = NULL;
68 static Eina_Array *_plugins = NULL;
69
70 static Eina_Bool
71 _ethumb_plugin_list_cb(Eina_Module *m, void *data __UNUSED__)
72 {
73    const char *file;
74    const char **ext;
75    Ethumb_Plugin *plugin;
76    Ethumb_Plugin *(*plugin_get)(void);
77
78    file = eina_module_file_get(m);
79    if (!eina_module_load(m))
80      {
81         ERR("could not load module \"%s\": %s\n",
82             file, eina_error_msg_get(eina_error_get()));
83         return EINA_FALSE;
84      }
85
86    plugin_get = eina_module_symbol_get(m, "ethumb_plugin_get");
87    if (!plugin_get)
88      {
89         ERR("could not find ethumb_plugin_get() in module \"%s\": %s\n",
90             file, eina_error_msg_get(eina_error_get()));
91         eina_module_unload(m);
92         return EINA_FALSE;
93      }
94
95    plugin = plugin_get();
96    if (!plugin)
97      {
98         ERR("plugin \"%s\" failed to init.\n", file);
99         eina_module_unload(m);
100         return EINA_FALSE;
101      }
102
103    DBG("loaded plugin \"%s\" (%p) with extensions:\n", file, plugin);
104    for (ext = plugin->extensions; *ext; ext++)
105      {
106         DBG("   extension \"%s\"\n", *ext);
107         eina_hash_add(_plugins_ext, *ext, plugin);
108      }
109
110    return EINA_TRUE;
111 }
112
113 static void
114 _ethumb_plugins_load(void)
115 {
116    _plugins_ext = eina_hash_string_small_new(NULL);
117    EINA_SAFETY_ON_NULL_RETURN(_plugins_ext);
118
119    _plugins = eina_module_list_get(_plugins, PLUGINSDIR, 1,
120                                    &_ethumb_plugin_list_cb, NULL);
121 }
122
123 static void
124 _ethumb_plugins_unload(void)
125 {
126    eina_hash_free(_plugins_ext);
127    _plugins_ext = NULL;
128    eina_module_list_unload(_plugins);
129    eina_module_list_flush(_plugins);
130    eina_array_free(_plugins);
131    _plugins = NULL;
132 }
133
134 EAPI int
135 ethumb_init(void)
136 {
137    const char *home;
138    char buf[PATH_MAX];
139
140    if (initcount)
141      return ++initcount;
142
143    if (!eina_init())
144      {
145         fprintf(stderr, "ERROR: Could not initialize eina.\n");
146         return 0;
147      }
148    _log_dom = eina_log_domain_register("ethumb", EINA_COLOR_GREEN);
149    if (_log_dom < 0)
150      {
151         EINA_LOG_ERR("Could not register log domain: ethumb");
152         eina_shutdown();
153         return 0;
154      }
155
156    evas_init();
157    ecore_init();
158    ecore_evas_init();
159    edje_init();
160
161    home = getenv("HOME");
162    snprintf(buf, sizeof(buf), "%s/.thumbnails", home);
163
164    _home_thumb_dir = eina_stringshare_add(buf);
165    _thumb_category_normal = eina_stringshare_add("normal");
166    _thumb_category_large = eina_stringshare_add("large");
167
168    _ethumb_plugins_load();
169    return ++initcount;
170 }
171
172 EAPI int
173 ethumb_shutdown(void)
174 {
175    initcount--;
176    if (initcount == 0)
177      {
178         _ethumb_plugins_unload();
179         eina_stringshare_del(_home_thumb_dir);
180         eina_stringshare_del(_thumb_category_normal);
181         eina_stringshare_del(_thumb_category_large);
182         evas_shutdown();
183         ecore_shutdown();
184         ecore_evas_shutdown();
185         edje_shutdown();
186         eina_log_domain_unregister(_log_dom);
187         _log_dom = -1;
188         eina_shutdown();
189      }
190
191    return initcount;
192 }
193
194 EAPI Ethumb *
195 ethumb_new(void)
196 {
197    Ethumb *ethumb;
198    Ecore_Evas *ee, *sub_ee;
199    Evas *e, *sub_e;
200    Evas_Object *o, *img;
201
202    ethumb = calloc(1, sizeof(Ethumb));
203    EINA_SAFETY_ON_NULL_RETURN_VAL(ethumb, NULL);
204
205    ethumb->tw = THUMB_SIZE_NORMAL;
206    ethumb->th = THUMB_SIZE_NORMAL;
207    ethumb->crop_x = 0.5;
208    ethumb->crop_y = 0.5;
209    ethumb->quality = 80;
210    ethumb->compress = 9;
211    ethumb->video.start = 0.1;
212    ethumb->video.time = 3;
213    ethumb->video.interval = 0.05;
214    ethumb->video.ntimes = 3;
215    ethumb->video.fps = 10;
216
217    ee = ecore_evas_buffer_new(1, 1);
218    e = ecore_evas_get(ee);
219    if (!e)
220      {
221         ERR("could not create ecore evas buffer\n");
222         return NULL;
223      }
224
225    evas_image_cache_set(e, 0);
226    evas_font_cache_set(e, 0);
227
228    o = ecore_evas_object_image_new(ee);
229    if (!o)
230      {
231         ERR("could not create sub ecore evas buffer\n");
232         ecore_evas_free(ee);
233         free(ethumb);
234         return NULL;
235      }
236
237    sub_ee = evas_object_data_get(o, "Ecore_Evas");
238    sub_e = ecore_evas_get(sub_ee);
239
240    evas_image_cache_set(sub_e, 0);
241    evas_font_cache_set(sub_e, 0);
242
243    img = evas_object_image_add(sub_e);
244    if (!img)
245      {
246         ERR("could not create source objects.\n");
247         ecore_evas_free(ee);
248         free(ethumb);
249         return NULL;
250      }
251
252    ethumb->ee = ee;
253    ethumb->e = e;
254    ethumb->sub_ee = sub_ee;
255    ethumb->sub_e = sub_e;
256    ethumb->o = o;
257    ethumb->img = img;
258
259    DBG("ethumb=%p\n", ethumb);
260
261    return ethumb;
262 }
263
264 static void
265 _ethumb_frame_free(Ethumb_Frame *frame)
266 {
267    Evas_Object *o;
268
269    if (!frame)
270      return;
271
272    if (frame->swallow && frame->edje)
273      {
274      o = edje_object_part_swallow_get(frame->edje, frame->swallow);
275      if (o)
276        edje_object_part_unswallow(frame->edje, o);
277      }
278    eina_stringshare_del(frame->file);
279    eina_stringshare_del(frame->group);
280    eina_stringshare_del(frame->swallow);
281
282    if (frame->edje)
283      evas_object_del(frame->edje);
284
285    free(frame);
286 }
287
288 EAPI void
289 ethumb_free(Ethumb *ethumb)
290 {
291    EINA_SAFETY_ON_NULL_RETURN(ethumb);
292
293    DBG("ethumb=%p", ethumb);
294
295    if (ethumb->frame)
296      _ethumb_frame_free(ethumb->frame);
297    ethumb_file_free(ethumb);
298    ecore_evas_free(ethumb->ee);
299    eina_stringshare_del(ethumb->thumb_dir);
300    eina_stringshare_del(ethumb->category);
301    if (ethumb->finished_idler)
302      ecore_idler_del(ethumb->finished_idler);
303    free(ethumb);
304 }
305
306 EAPI void
307 ethumb_thumb_fdo_set(Ethumb *e, Ethumb_Thumb_FDO_Size s)
308 {
309    EINA_SAFETY_ON_NULL_RETURN(e);
310    EINA_SAFETY_ON_FALSE_RETURN(s == ETHUMB_THUMB_NORMAL ||
311                                s == ETHUMB_THUMB_LARGE);
312    DBG("ethumb=%p, size=%d", e, s);
313
314    if (s == ETHUMB_THUMB_NORMAL)
315      {
316         e->tw = THUMB_SIZE_NORMAL;
317         e->th = THUMB_SIZE_NORMAL;
318      }
319    else
320      {
321         e->tw = THUMB_SIZE_LARGE;
322         e->th = THUMB_SIZE_LARGE;
323      }
324
325    e->format = ETHUMB_THUMB_FDO;
326    e->aspect = ETHUMB_THUMB_KEEP_ASPECT;
327    _ethumb_frame_free(e->frame);
328    e->frame = NULL;
329    eina_stringshare_del(e->thumb_dir);
330    eina_stringshare_del(e->category);
331    e->thumb_dir = NULL;
332    e->category = NULL;
333 }
334
335 EAPI void
336 ethumb_thumb_size_set(Ethumb *e, int tw, int th)
337 {
338    EINA_SAFETY_ON_NULL_RETURN(e);
339    EINA_SAFETY_ON_FALSE_RETURN(tw > 0);
340    EINA_SAFETY_ON_FALSE_RETURN(th > 0);
341
342    DBG("ethumb=%p, w=%d, h=%d", e, tw, th);
343    e->tw = tw;
344    e->th = th;
345 }
346
347 EAPI void
348 ethumb_thumb_size_get(const Ethumb *e, int *tw, int *th)
349 {
350    EINA_SAFETY_ON_NULL_RETURN(e);
351
352    if (tw) *tw = e->tw;
353    if (th) *th = e->th;
354 }
355
356 EAPI void
357 ethumb_thumb_format_set(Ethumb *e, Ethumb_Thumb_Format f)
358 {
359    EINA_SAFETY_ON_NULL_RETURN(e);
360    EINA_SAFETY_ON_FALSE_RETURN(f == ETHUMB_THUMB_FDO ||
361                                f == ETHUMB_THUMB_JPEG ||
362                                f == ETHUMB_THUMB_EET);
363
364    DBG("ethumb=%p, format=%d", e, f);
365    e->format = f;
366 }
367
368 EAPI Ethumb_Thumb_Format
369 ethumb_thumb_format_get(const Ethumb *e)
370 {
371    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
372    return e->format;
373 }
374
375 EAPI void
376 ethumb_thumb_aspect_set(Ethumb *e, Ethumb_Thumb_Aspect a)
377 {
378    EINA_SAFETY_ON_NULL_RETURN(e);
379    EINA_SAFETY_ON_FALSE_RETURN(a == ETHUMB_THUMB_KEEP_ASPECT ||
380                                a == ETHUMB_THUMB_IGNORE_ASPECT ||
381                                a == ETHUMB_THUMB_CROP);
382
383    DBG("ethumb=%p, aspect=%d", e, a);
384    e->aspect = a;
385 }
386
387 EAPI Ethumb_Thumb_Aspect
388 ethumb_thumb_aspect_get(const Ethumb *e)
389 {
390    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
391    return e->aspect;
392 }
393
394 EAPI void
395 ethumb_thumb_crop_align_set(Ethumb *e, float x, float y)
396 {
397    EINA_SAFETY_ON_NULL_RETURN(e);
398
399    DBG("ethumb=%p, x=%f, y=%f", e, x, y);
400    e->crop_x = x;
401    e->crop_y = y;
402 }
403
404 EAPI void
405 ethumb_thumb_crop_align_get(const Ethumb *e, float *x, float *y)
406 {
407    EINA_SAFETY_ON_NULL_RETURN(e);
408
409    if (x) *x = e->crop_x;
410    if (y) *y = e->crop_y;
411 }
412
413 EAPI void
414 ethumb_thumb_quality_set(Ethumb *e, int quality)
415 {
416    EINA_SAFETY_ON_NULL_RETURN(e);
417
418    DBG("ethumb=%p, quality=%d", e, quality);
419    e->quality = quality;
420 }
421
422 EAPI int
423 ethumb_thumb_quality_get(const Ethumb *e)
424 {
425    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
426    return e->quality;
427 }
428
429 EAPI void
430 ethumb_thumb_compress_set(Ethumb *e, int compress)
431 {
432    EINA_SAFETY_ON_NULL_RETURN(e);
433
434    DBG("ethumb=%p, compress=%d", e, compress);
435    e->compress = compress;
436 }
437
438 EAPI int
439 ethumb_thumb_compress_get(const Ethumb *e)
440 {
441    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
442    return e->compress;
443 }
444
445 EAPI Eina_Bool
446 ethumb_frame_set(Ethumb *e, const char *theme_file, const char *group, const char *swallow)
447 {
448    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
449
450    Ethumb_Frame *frame;
451    frame = e->frame;
452
453    DBG("ethumb=%p, theme_file=%s, group=%s, swallow=%s",
454        e, theme_file ? theme_file : "", group ? group : "",
455        swallow ? swallow : "");
456
457    if (frame)
458      {
459         edje_object_part_unswallow(frame->edje, e->img);
460         if (!theme_file)
461           _ethumb_frame_free(frame);
462      }
463
464    if (!theme_file)
465      {
466         e->frame = NULL;
467         return EINA_TRUE;
468      }
469
470    if (!frame)
471      {
472         frame = calloc(1, sizeof(Ethumb_Frame));
473         if (!frame)
474           {
475              ERR("could not allocate Ethumb_Frame structure.\n");
476              return EINA_FALSE;
477           }
478
479         frame->edje = edje_object_add(e->sub_e);
480         if (!frame->edje)
481           {
482              ERR("could not create edje frame object.\n");
483              _ethumb_frame_free(frame);
484              e->frame = NULL;
485              return EINA_FALSE;
486           }
487      }
488
489    if (!edje_object_file_set(frame->edje, theme_file, group))
490      {
491         ERR("could not load frame theme.\n");
492         _ethumb_frame_free(frame);
493         e->frame = NULL;
494         return EINA_FALSE;
495      }
496
497    edje_object_part_swallow(frame->edje, swallow, e->img);
498    if (!edje_object_part_swallow_get(frame->edje, swallow))
499      {
500         ERR("could not swallow image to edje frame.\n");
501         _ethumb_frame_free(frame);
502         e->frame = NULL;
503         return EINA_FALSE;
504      }
505
506    eina_stringshare_replace(&frame->file, theme_file);
507    eina_stringshare_replace(&frame->group, group);
508    eina_stringshare_replace(&frame->swallow, swallow);
509
510    e->frame = frame;
511
512    return EINA_TRUE;
513 }
514
515 EAPI void
516 ethumb_frame_get(const Ethumb *e, const char **theme_file, const char **group, const char **swallow)
517 {
518    EINA_SAFETY_ON_NULL_RETURN(e);
519
520    if (e->frame)
521      {
522         if (theme_file) *theme_file = e->frame->file;
523         if (group) *group = e->frame->group;
524         if (swallow) *swallow = e->frame->swallow;
525      }
526    else
527      {
528         if (theme_file) *theme_file = NULL;
529         if (group) *group = NULL;
530         if (swallow) *swallow = NULL;
531      }
532 }
533
534 static const char *
535 _ethumb_build_absolute_path(const char *path, char buf[])
536 {
537    char *p;
538    int len;
539
540    if (!path)
541      return NULL;
542
543    p = buf;
544
545    if (path[0] == '/')
546      strcpy(p, path);
547    else if (path[0] == '~')
548      {
549         strcpy(p, getenv("HOME"));
550         len = strlen(p);
551         p += len;
552         p[0] = '/';
553         p++;
554         strcpy(p, path + 2);
555      }
556    else
557      {
558         getcwd(p, PATH_MAX);
559         len = strlen(p);
560         p += len;
561         p[0] = '/';
562         p++;
563         strcpy(p, path);
564      }
565
566    return buf;
567 }
568
569 EAPI void
570 ethumb_thumb_dir_path_set(Ethumb *e, const char *path)
571 {
572    char buf[PATH_MAX];
573    EINA_SAFETY_ON_NULL_RETURN(e);
574
575    DBG("ethumb=%p, path=%s", e, path ? path : "");
576    path = _ethumb_build_absolute_path(path, buf);
577    eina_stringshare_replace(&e->thumb_dir, path);
578 }
579
580 EAPI const char *
581 ethumb_thumb_dir_path_get(const Ethumb *e)
582 {
583    EINA_SAFETY_ON_NULL_RETURN_VAL(e, NULL);
584
585    return e->thumb_dir;
586 }
587
588 EAPI void
589 ethumb_thumb_category_set(Ethumb *e, const char *category)
590 {
591    EINA_SAFETY_ON_NULL_RETURN(e);
592
593    DBG("ethumb=%p, category=%s", e, category ? category : "");
594    eina_stringshare_replace(&e->category, category);
595 }
596
597 EAPI const char *
598 ethumb_thumb_category_get(const Ethumb *e)
599 {
600    EINA_SAFETY_ON_NULL_RETURN_VAL(e, NULL);
601
602    return e->category;
603 }
604
605 EAPI void
606 ethumb_video_start_set(Ethumb *e, float start)
607 {
608    EINA_SAFETY_ON_NULL_RETURN(e);
609
610    DBG("ethumb=%p, video_start=%f", e, start);
611    e->video.start = start;
612 }
613
614 EAPI float
615 ethumb_video_start_get(const Ethumb *e)
616 {
617    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
618
619    return e->video.start;
620 }
621
622 EAPI void
623 ethumb_video_time_set(Ethumb *e, float time)
624 {
625    EINA_SAFETY_ON_NULL_RETURN(e);
626
627    DBG("ethumb=%p, video_start=%f", e, time);
628    e->video.time = time;
629 }
630
631 EAPI float
632 ethumb_video_time_get(const Ethumb *e)
633 {
634    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
635
636    return e->video.time;
637 }
638
639 EAPI void
640 ethumb_video_interval_set(Ethumb *e, float interval)
641 {
642    EINA_SAFETY_ON_NULL_RETURN(e);
643
644    DBG("ethumb=%p, video_interval=%f", e, interval);
645    e->video.interval = interval;
646 }
647
648 EAPI float
649 ethumb_video_interval_get(const Ethumb *e)
650 {
651    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
652
653    return e->video.interval;
654 }
655
656 EAPI void
657 ethumb_video_ntimes_set(Ethumb *e, int ntimes)
658 {
659    EINA_SAFETY_ON_NULL_RETURN(e);
660
661    DBG("ethumb=%p, video_ntimes=%d", e, ntimes);
662    e->video.ntimes = ntimes;
663 }
664
665 EAPI int
666 ethumb_video_ntimes_get(const Ethumb *e)
667 {
668    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
669
670    return e->video.ntimes;
671 }
672
673 EAPI void
674 ethumb_video_fps_set(Ethumb *e, int fps)
675 {
676    EINA_SAFETY_ON_NULL_RETURN(e);
677
678    DBG("ethumb=%p, video_fps=%d", e, fps);
679    e->video.fps = fps;
680 }
681
682 EAPI int
683 ethumb_video_fps_get(const Ethumb *e)
684 {
685    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
686
687    return e->video.fps;
688 }
689
690 EAPI void
691 ethumb_document_page_set(Ethumb *e, int page)
692 {
693    EINA_SAFETY_ON_NULL_RETURN(e);
694
695    DBG("ethumb=%p, document_page=%d", e, page);
696    e->document.page = page;
697 }
698
699 EAPI int
700 ethumb_document_page_get(const Ethumb *e)
701 {
702    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
703
704    return e->document.page;
705 }
706
707 EAPI Eina_Bool
708 ethumb_file_set(Ethumb *e, const char *path, const char *key)
709 {
710    char buf[PATH_MAX];
711    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
712
713    DBG("ethumb=%p, path=%s, key=%s", e, path ? path : "", key ? key : "");
714    if (path && access(path, R_OK))
715      {
716         ERR("couldn't access file \"%s\"\n", path);
717         return EINA_FALSE;
718      }
719
720    path = _ethumb_build_absolute_path(path, buf);
721    eina_stringshare_replace(&e->src_path, path);
722    eina_stringshare_replace(&e->src_key, key);
723    eina_stringshare_replace(&e->thumb_path, NULL);
724    eina_stringshare_replace(&e->thumb_key, NULL);
725
726    return EINA_TRUE;
727 }
728
729 EAPI void
730 ethumb_file_get(const Ethumb *e, const char **path, const char **key)
731 {
732    EINA_SAFETY_ON_NULL_RETURN(e);
733
734    if (path) *path = e->src_path;
735    if (key) *key = e->src_key;
736 }
737
738 static const char ACCEPTABLE_URI_CHARS[96] = {
739      /*      !    "    #    $    %    &    '    (    )    *    +    ,    -    .    / */ 
740      0x00,0x3F,0x20,0x20,0x28,0x00,0x2C,0x3F,0x3F,0x3F,0x3F,0x2A,0x28,0x3F,0x3F,0x1C,
741      /* 0    1    2    3    4    5    6    7    8    9    :    ;    <    =    >    ? */
742      0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x38,0x20,0x20,0x2C,0x20,0x20,
743      /* @    A    B    C    D    E    F    G    H    I    J    K    L    M    N    O */
744      0x38,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
745      /* P    Q    R    S    T    U    V    W    X    Y    Z    [    \    ]    ^    _ */
746      0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x20,0x20,0x20,0x20,0x3F,
747      /* `    a    b    c    d    e    f    g    h    i    j    k    l    m    n    o */
748      0x20,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
749      /* p    q    r    s    t    u    v    w    x    y    z    {    |    }    ~  DEL */
750      0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x20,0x20,0x20,0x3F,0x20
751 };
752
753 static const char *
754 _ethumb_generate_hash(const char *file)
755 {
756   int n;
757   MD5_CTX ctx;
758   char md5out[(2 * MD5_HASHBYTES) + 1];
759   unsigned char hash[MD5_HASHBYTES];
760   static const char hex[] = "0123456789abcdef";
761
762   char *uri;
763   char *t;
764   const unsigned char *c;
765
766 #define _check_uri_char(c) \
767   ((c) >= 32 && (c) < 128 && (ACCEPTABLE_URI_CHARS[(c) - 32] & 0x08))
768
769   EINA_SAFETY_ON_NULL_RETURN_VAL(file, NULL);
770
771   uri = alloca(3 * strlen(file) + 9);
772   strncpy(uri, "file://", sizeof(uri));
773   t = uri + 7;
774
775   for (c = (const unsigned char *)file; *c != '\0'; c++)
776     {
777        if (!_check_uri_char(*c))
778          {
779             *t++ = '%';
780             *t++ = hex[*c >> 4];
781             *t++ = hex[*c & 15];
782          }
783        else
784          *t++ = *c;
785     }
786   *t = '\0';
787
788 #undef _check_uri_char
789
790   MD5Init (&ctx);
791   MD5Update (&ctx, (unsigned char const*)uri, (unsigned)strlen (uri));
792   MD5Final (hash, &ctx);
793
794   for (n = 0; n < MD5_HASHBYTES; n++)
795     {
796       md5out[2 * n] = hex[hash[n] >> 4];
797       md5out[2 * n + 1] = hex[hash[n] & 0x0f];
798     }
799   md5out[2 * n] = '\0';
800
801   DBG("md5=%s, file=%s\n", md5out, file);
802   return eina_stringshare_add(md5out);
803 }
804
805 static int
806 _ethumb_file_check_fdo(Ethumb *e)
807 {
808    if (!((e->tw == THUMB_SIZE_NORMAL && e->th == THUMB_SIZE_NORMAL) ||
809        (e->tw == THUMB_SIZE_LARGE && e->th == THUMB_SIZE_LARGE)))
810      return 0;
811
812    if (e->format != ETHUMB_THUMB_FDO)
813      return 0;
814
815    if (e->aspect != ETHUMB_THUMB_KEEP_ASPECT)
816      return 0;
817
818    if (e->frame)
819      return 0;
820
821    return 1;
822 }
823
824 static const char *
825 _ethumb_file_generate_custom_category(Ethumb *e)
826 {
827    char buf[PATH_MAX];
828    const char *aspect, *format;
829    const char *frame;
830
831    if (e->aspect == ETHUMB_THUMB_KEEP_ASPECT)
832      aspect = "keep_aspect";
833    else if (e->aspect == ETHUMB_THUMB_IGNORE_ASPECT)
834      aspect = "ignore_aspect";
835    else
836      aspect = "crop";
837
838    if (e->format == ETHUMB_THUMB_FDO)
839      format = "png";
840    else if (e->format == ETHUMB_THUMB_JPEG)
841      format = "jpg";
842    else
843      format = "eet";
844
845    if (e->frame)
846      frame = "-framed";
847    else
848      frame = "";
849
850    snprintf(buf, sizeof(buf), "%dx%d-%s%s-%s",
851             e->tw, e->th, aspect, frame, format);
852
853    return eina_stringshare_add(buf);
854 }
855
856 static void
857 _ethumb_file_generate_path(Ethumb *e)
858 {
859    char buf[PATH_MAX];
860    char *fullname;
861    const char *hash;
862    const char *thumb_dir, *category;
863    const char *ext;
864    int fdo_format;
865
866
867    fdo_format = _ethumb_file_check_fdo(e);
868
869    if (e->thumb_dir)
870      thumb_dir = eina_stringshare_ref(e->thumb_dir);
871    else
872      thumb_dir = eina_stringshare_ref(_home_thumb_dir);
873
874    if (e->category)
875      category = eina_stringshare_ref(e->category);
876    else if (!fdo_format)
877      category = _ethumb_file_generate_custom_category(e);
878    else
879      {
880         if (e->tw == THUMB_SIZE_NORMAL)
881           category = eina_stringshare_ref(_thumb_category_normal);
882         else if (e->tw == THUMB_SIZE_LARGE)
883           category = eina_stringshare_ref(_thumb_category_large);
884      }
885
886    if (e->format == ETHUMB_THUMB_FDO)
887      ext = "png";
888    else if (e->format == ETHUMB_THUMB_JPEG)
889      ext = "jpg";
890    else
891      ext = "eet";
892
893
894    fullname = ecore_file_realpath(e->src_path);
895    hash = _ethumb_generate_hash(fullname);
896    snprintf(buf, sizeof(buf), "%s/%s/%s.%s", thumb_dir, category, hash, ext);
897    free(fullname);
898    DBG("ethumb=%p, path=%s", e, buf);
899    eina_stringshare_replace(&e->thumb_path, buf);
900    if (e->format == ETHUMB_THUMB_EET)
901      eina_stringshare_replace(&e->thumb_key, "thumbnail");
902    else
903      {
904         eina_stringshare_del(e->thumb_key);
905         e->thumb_key = NULL;
906      }
907
908    eina_stringshare_del(thumb_dir);
909    eina_stringshare_del(category);
910    eina_stringshare_del(hash);
911 }
912
913 EAPI void
914 ethumb_file_free(Ethumb *e)
915 {
916    EINA_SAFETY_ON_NULL_RETURN(e);
917    DBG("ethumb=%p", e);
918
919    eina_stringshare_replace(&e->src_path, NULL);
920    eina_stringshare_replace(&e->src_key, NULL);
921    eina_stringshare_replace(&e->thumb_path, NULL);
922    eina_stringshare_replace(&e->thumb_key, NULL);
923 }
924
925 EAPI void
926 ethumb_thumb_path_set(Ethumb *e, const char *path, const char *key)
927 {
928    char buf[PATH_MAX];
929
930    EINA_SAFETY_ON_NULL_RETURN(e);
931    DBG("ethumb=%p, path=%s, key=%s", e, path ? path : "", key ? key : "");
932
933    if (!path)
934      {
935         eina_stringshare_replace(&e->thumb_path, NULL);
936         eina_stringshare_replace(&e->thumb_key, NULL);
937      }
938    else
939      {
940         path = _ethumb_build_absolute_path(path, buf);
941         eina_stringshare_replace(&e->thumb_path, path);
942         eina_stringshare_replace(&e->thumb_key, key);
943      }
944 }
945
946 EAPI void
947 ethumb_thumb_path_get(Ethumb *e, const char **path, const char **key)
948 {
949    EINA_SAFETY_ON_NULL_RETURN(e);
950    if (!e->thumb_path)
951      _ethumb_file_generate_path(e);
952
953    if (path) *path = e->thumb_path;
954    if (key) *key = e->thumb_key;
955 }
956
957 void
958 ethumb_calculate_aspect_from_ratio(Ethumb *e, float ia, int *w, int *h)
959 {
960    float a;
961
962    *w = e->tw;
963    *h = e->th;
964
965    if (ia == 0)
966      return;
967
968    a = e->tw / (float)e->th;
969
970    if (e->aspect == ETHUMB_THUMB_KEEP_ASPECT)
971      {
972         if ((ia > a && e->tw > 0) || e->th <= 0)
973           *h = e->tw / ia;
974         else
975           *w = e->th * ia;
976      }
977 }
978
979 void
980 ethumb_calculate_aspect(Ethumb *e, int iw, int ih, int *w, int *h)
981 {
982    float ia;
983
984    ia = iw / (float)ih;
985
986    ethumb_calculate_aspect_from_ratio(e, ia, w, h);
987 }
988
989 void
990 ethumb_calculate_fill_from_ratio(Ethumb *e, float ia, int *fx, int *fy, int *fw, int *fh)
991 {
992    float a;
993
994    if (ia == 0)
995      return;
996
997    *fw = e->tw;
998    *fh = e->th;
999    *fx = 0;
1000    *fy = 0;
1001    a = e->tw / (float)e->th;
1002
1003    if (e->aspect == ETHUMB_THUMB_CROP)
1004      {
1005         if ((ia > a && e->tw > 0) || e->th <= 0)
1006           *fw = e->th * ia;
1007         else
1008           *fh = e->tw / ia;
1009
1010         *fx = - e->crop_x * (*fw - e->tw);
1011         *fy = - e->crop_y * (*fh - e->th);
1012      }
1013    else if (e->aspect == ETHUMB_THUMB_KEEP_ASPECT)
1014      {
1015         if ((ia > a && e->tw > 0) || e->th <= 0)
1016           *fh = e->tw / ia;
1017         else
1018           *fw = e->th * ia;
1019      }
1020 }
1021
1022 void
1023 ethumb_calculate_fill(Ethumb *e, int iw, int ih, int *fx, int *fy, int *fw, int *fh)
1024 {
1025    float ia;
1026    ia = iw / (float)ih;
1027
1028    ethumb_calculate_fill_from_ratio(e, ia, fx, fy, fw, fh);
1029 }
1030
1031 static Eina_Bool
1032 _ethumb_plugin_generate(Ethumb *e)
1033 {
1034    const char *ext;
1035    Ethumb_Plugin *plugin;
1036
1037    ext = strrchr(e->src_path, '.');
1038    if (!ext)
1039      {
1040         ERR("could not get extension for file \"%s\"\n", e->src_path);
1041         return EINA_FALSE;
1042      }
1043
1044    plugin = eina_hash_find(_plugins_ext, ext + 1);
1045    if (!plugin)
1046      {
1047         DBG("no plugin for extension: \"%s\"\n", ext + 1);
1048         return EINA_FALSE;
1049      }
1050
1051    if (e->frame)
1052      evas_object_hide(e->frame->edje);
1053    else
1054      evas_object_hide(e->img);
1055
1056    plugin->generate_thumb(e);
1057
1058    return EINA_TRUE;
1059 }
1060
1061 Eina_Bool
1062 ethumb_plugin_image_resize(Ethumb *e, int w, int h)
1063 {
1064    Evas_Object *img;
1065
1066    img = e->img;
1067
1068    if (e->frame)
1069      {
1070         edje_extern_object_min_size_set(img, w, h);
1071         edje_extern_object_max_size_set(img, w, h);
1072         edje_object_calc_force(e->frame->edje);
1073         evas_object_move(e->frame->edje, 0, 0);
1074         evas_object_resize(e->frame->edje, w, h);
1075      }
1076    else
1077      {
1078         evas_object_move(img, 0, 0);
1079         evas_object_resize(img, w, h);
1080      }
1081
1082    evas_object_image_size_set(e->o, w, h);
1083    ecore_evas_resize(e->sub_ee, w, h);
1084
1085    e->rw = w;
1086    e->rh = h;
1087
1088    return EINA_TRUE;
1089 }
1090
1091 Eina_Bool
1092 ethumb_image_save(Ethumb *e)
1093 {
1094    Eina_Bool r;
1095    char *dname;
1096    char flags[256];
1097
1098    evas_damage_rectangle_add(e->sub_e, 0, 0, e->rw, e->rh);
1099    evas_render(e->sub_e);
1100
1101    if (!e->thumb_path)
1102      _ethumb_file_generate_path(e);
1103
1104    if (!e->thumb_path)
1105      {
1106         ERR("could not create file path...\n");
1107         return EINA_FALSE;
1108      }
1109
1110    dname = ecore_file_dir_get(e->thumb_path);
1111    r = ecore_file_mkpath(dname);
1112    free(dname);
1113    if (!r)
1114      {
1115         ERR("could not create directory '%s'\n", dname);
1116         return EINA_FALSE;
1117      }
1118
1119    snprintf(flags, sizeof(flags), "quality=%d compress=%d",
1120             e->quality, e->compress);
1121    r = evas_object_image_save(e->o, e->thumb_path, e->thumb_key, flags);
1122
1123    if (!r)
1124      {
1125         ERR("could not save image.\n");
1126         return EINA_FALSE;
1127      }
1128
1129    return EINA_TRUE;
1130 }
1131
1132 static int
1133 _ethumb_image_load(Ethumb *e)
1134 {
1135    int error;
1136    Evas_Coord w, h, ww, hh, fx, fy, fw, fh;
1137    Evas_Object *img;
1138
1139    img = e->img;
1140
1141    if (e->frame)
1142      evas_object_hide(e->frame->edje);
1143    else
1144      evas_object_hide(img);
1145    evas_object_image_file_set(img, NULL, NULL);
1146    evas_object_image_load_size_set(img, e->tw, e->th);
1147    evas_object_image_file_set(img, e->src_path, e->src_key);
1148
1149    if (e->frame)
1150      evas_object_show(e->frame->edje);
1151    else
1152      evas_object_show(img);
1153
1154    error = evas_object_image_load_error_get(img);
1155    if (error != EVAS_LOAD_ERROR_NONE)
1156      {
1157         ERR("could not load image '%s': %d\n", e->src_path, error);
1158         return 0;
1159      }
1160
1161    evas_object_image_size_get(img, &w, &h);
1162    if ((w <= 0) || (h <= 0))
1163      return 0;
1164
1165    ethumb_calculate_aspect(e, w, h, &ww, &hh);
1166
1167    if (e->frame)
1168      {
1169         edje_extern_object_min_size_set(img, ww, hh);
1170         edje_extern_object_max_size_set(img, ww, hh);
1171         edje_object_calc_force(e->frame->edje);
1172         evas_object_move(e->frame->edje, 0, 0);
1173         evas_object_resize(e->frame->edje, ww, hh);
1174      }
1175    else
1176      {
1177         evas_object_move(img, 0, 0);
1178         evas_object_resize(img, ww, hh);
1179      }
1180
1181    ethumb_calculate_fill(e, w, h, &fx, &fy, &fw, &fh);
1182    evas_object_image_fill_set(img, fx, fy, fw, fh);
1183
1184    evas_object_image_size_set(e->o, ww, hh);
1185    ecore_evas_resize(e->sub_ee, ww, hh);
1186
1187    e->rw = ww;
1188    e->rh = hh;
1189
1190    return 1;
1191 }
1192
1193 static int
1194 _ethumb_finished_idler_cb(void *data)
1195 {
1196    Ethumb *e = data;
1197
1198    e->finished_cb(e->cb_data, e, e->cb_result);
1199    if (e->cb_data_free)
1200      e->cb_data_free(e->cb_data);
1201    e->finished_idler = NULL;
1202    e->finished_cb = NULL;
1203    e->cb_data = NULL;
1204    e->cb_data_free = NULL;
1205
1206    return 0;
1207 }
1208
1209 void
1210 ethumb_finished_callback_call(Ethumb *e, int result)
1211 {
1212    EINA_SAFETY_ON_NULL_RETURN(e);
1213
1214    e->cb_result = result;
1215    if (e->finished_idler)
1216      ecore_idler_del(e->finished_idler);
1217    e->finished_idler = ecore_idler_add(_ethumb_finished_idler_cb, e);
1218 }
1219
1220 EAPI Eina_Bool
1221 ethumb_generate(Ethumb *e, Ethumb_Generate_Cb finished_cb, const void *data, Eina_Free_Cb free_data)
1222 {
1223    int r;
1224
1225    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
1226    EINA_SAFETY_ON_NULL_RETURN_VAL(finished_cb, 0);
1227    DBG("ethumb=%p, finished_cb=%p, data=%p, free_data=%p, path=%s, key=%s",
1228        e, finished_cb, data, free_data,
1229        e->src_path ? e->src_path : "", e->src_key ? e->src_key : "");
1230
1231    if (e->finished_idler)
1232      {
1233         ERR("thumbnail generation already in progress.\n");
1234         return EINA_FALSE;
1235      }
1236    e->finished_cb = finished_cb;
1237    e->cb_data = (void *)data;
1238    e->cb_data_free = free_data;
1239
1240    if (!e->src_path)
1241      {
1242         ERR("no file set.\n");
1243         ethumb_finished_callback_call(e, 0);
1244         return EINA_TRUE;
1245      }
1246
1247    r = _ethumb_plugin_generate(e);
1248    if (r)
1249      return EINA_TRUE;
1250
1251    if (!_ethumb_image_load(e))
1252      {
1253         ERR("could not load input image.\n");
1254         ethumb_finished_callback_call(e, 0);
1255         return EINA_TRUE;
1256      }
1257
1258    r = ethumb_image_save(e);
1259
1260    ethumb_finished_callback_call(e, r);
1261
1262    return EINA_TRUE;
1263 }
1264
1265 EAPI Eina_Bool
1266 ethumb_exists(Ethumb *e)
1267 {
1268    struct stat thumb, src;
1269    int r_thumb, r_src;
1270    Eina_Bool r = EINA_FALSE;
1271
1272    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
1273    EINA_SAFETY_ON_NULL_RETURN_VAL(e->src_path, 0);
1274    DBG("ethumb=%p, path=%s", e, e->src_path ? e->src_path : "");
1275
1276    if (!e->thumb_path)
1277      _ethumb_file_generate_path(e);
1278
1279    EINA_SAFETY_ON_NULL_RETURN_VAL(e->thumb_path, 0);
1280
1281    r_thumb = stat(e->thumb_path, &thumb);
1282    r_src = stat(e->src_path, &src);
1283
1284    EINA_SAFETY_ON_TRUE_RETURN_VAL(r_src, 0);
1285
1286    if (r_thumb && errno != ENOENT)
1287      ERR("could not access file \"%s\": %s\n", e->thumb_path, strerror(errno));
1288    else if (!r_thumb && thumb.st_mtime > src.st_mtime)
1289      r = EINA_TRUE;
1290
1291    return r;
1292 }
1293
1294 Evas *
1295 ethumb_evas_get(const Ethumb *e)
1296 {
1297    EINA_SAFETY_ON_NULL_RETURN_VAL(e, NULL);
1298
1299    return e->sub_e;
1300 }
1301
1302 Ecore_Evas *
1303 ethumb_ecore_evas_get(const Ethumb *e)
1304 {
1305    EINA_SAFETY_ON_NULL_RETURN_VAL(e, NULL);
1306
1307    return e->sub_ee;
1308 }