oops! strncpy is tricky and uri was a pointer not an array.
[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",
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",
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.", file);
99         eina_module_unload(m);
100         return EINA_FALSE;
101      }
102
103    DBG("loaded plugin \"%s\" (%p) with extensions:", file, plugin);
104    for (ext = plugin->extensions; *ext; ext++)
105      {
106         DBG("   extension \"%s\"", *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    /* IF CHANGED, UPDATE DOCS in (Ethumb.c, Ethumb_Client.c, python...)!!! */
206    ethumb->tw = THUMB_SIZE_NORMAL;
207    ethumb->th = THUMB_SIZE_NORMAL;
208    ethumb->crop_x = 0.5;
209    ethumb->crop_y = 0.5;
210    ethumb->quality = 80;
211    ethumb->compress = 9;
212    ethumb->video.start = 0.1;
213    ethumb->video.time = 3;
214    ethumb->video.interval = 0.05;
215    ethumb->video.ntimes = 3;
216    ethumb->video.fps = 10;
217
218    ee = ecore_evas_buffer_new(1, 1);
219    e = ecore_evas_get(ee);
220    if (!e)
221      {
222         ERR("could not create ecore evas buffer");
223         return NULL;
224      }
225
226    evas_image_cache_set(e, 0);
227    evas_font_cache_set(e, 0);
228
229    o = ecore_evas_object_image_new(ee);
230    if (!o)
231      {
232         ERR("could not create sub ecore evas buffer");
233         ecore_evas_free(ee);
234         free(ethumb);
235         return NULL;
236      }
237
238    sub_ee = evas_object_data_get(o, "Ecore_Evas");
239    sub_e = ecore_evas_get(sub_ee);
240
241    evas_image_cache_set(sub_e, 0);
242    evas_font_cache_set(sub_e, 0);
243
244    img = evas_object_image_add(sub_e);
245    if (!img)
246      {
247         ERR("could not create source objects.");
248         ecore_evas_free(ee);
249         free(ethumb);
250         return NULL;
251      }
252
253    ethumb->ee = ee;
254    ethumb->e = e;
255    ethumb->sub_ee = sub_ee;
256    ethumb->sub_e = sub_e;
257    ethumb->o = o;
258    ethumb->img = img;
259
260    DBG("ethumb=%p", ethumb);
261
262    return ethumb;
263 }
264
265 static void
266 _ethumb_frame_free(Ethumb_Frame *frame)
267 {
268    Evas_Object *o;
269
270    if (!frame)
271      return;
272
273    if (frame->swallow && frame->edje)
274      {
275      o = edje_object_part_swallow_get(frame->edje, frame->swallow);
276      if (o)
277        edje_object_part_unswallow(frame->edje, o);
278      }
279    eina_stringshare_del(frame->file);
280    eina_stringshare_del(frame->group);
281    eina_stringshare_del(frame->swallow);
282
283    if (frame->edje)
284      evas_object_del(frame->edje);
285
286    free(frame);
287 }
288
289 EAPI void
290 ethumb_free(Ethumb *ethumb)
291 {
292    EINA_SAFETY_ON_NULL_RETURN(ethumb);
293
294    DBG("ethumb=%p", ethumb);
295
296    if (ethumb->frame)
297      _ethumb_frame_free(ethumb->frame);
298    ethumb_file_free(ethumb);
299    ecore_evas_free(ethumb->ee);
300    eina_stringshare_del(ethumb->thumb_dir);
301    eina_stringshare_del(ethumb->category);
302    if (ethumb->finished_idler)
303      ecore_idler_del(ethumb->finished_idler);
304    free(ethumb);
305 }
306
307 EAPI void
308 ethumb_thumb_fdo_set(Ethumb *e, Ethumb_Thumb_FDO_Size s)
309 {
310    EINA_SAFETY_ON_NULL_RETURN(e);
311    EINA_SAFETY_ON_FALSE_RETURN(s == ETHUMB_THUMB_NORMAL ||
312                                s == ETHUMB_THUMB_LARGE);
313    DBG("ethumb=%p, size=%d", e, s);
314
315    if (s == ETHUMB_THUMB_NORMAL)
316      {
317         e->tw = THUMB_SIZE_NORMAL;
318         e->th = THUMB_SIZE_NORMAL;
319      }
320    else
321      {
322         e->tw = THUMB_SIZE_LARGE;
323         e->th = THUMB_SIZE_LARGE;
324      }
325
326    e->format = ETHUMB_THUMB_FDO;
327    e->aspect = ETHUMB_THUMB_KEEP_ASPECT;
328    _ethumb_frame_free(e->frame);
329    e->frame = NULL;
330    eina_stringshare_del(e->thumb_dir);
331    eina_stringshare_del(e->category);
332    e->thumb_dir = NULL;
333    e->category = NULL;
334 }
335
336 EAPI void
337 ethumb_thumb_size_set(Ethumb *e, int tw, int th)
338 {
339    EINA_SAFETY_ON_NULL_RETURN(e);
340    EINA_SAFETY_ON_FALSE_RETURN(tw > 0);
341    EINA_SAFETY_ON_FALSE_RETURN(th > 0);
342
343    DBG("ethumb=%p, w=%d, h=%d", e, tw, th);
344    e->tw = tw;
345    e->th = th;
346 }
347
348 EAPI void
349 ethumb_thumb_size_get(const Ethumb *e, int *tw, int *th)
350 {
351    EINA_SAFETY_ON_NULL_RETURN(e);
352
353    if (tw) *tw = e->tw;
354    if (th) *th = e->th;
355 }
356
357 EAPI void
358 ethumb_thumb_format_set(Ethumb *e, Ethumb_Thumb_Format f)
359 {
360    EINA_SAFETY_ON_NULL_RETURN(e);
361    EINA_SAFETY_ON_FALSE_RETURN(f == ETHUMB_THUMB_FDO ||
362                                f == ETHUMB_THUMB_JPEG ||
363                                f == ETHUMB_THUMB_EET);
364
365    DBG("ethumb=%p, format=%d", e, f);
366    e->format = f;
367 }
368
369 EAPI Ethumb_Thumb_Format
370 ethumb_thumb_format_get(const Ethumb *e)
371 {
372    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
373    return e->format;
374 }
375
376 EAPI void
377 ethumb_thumb_aspect_set(Ethumb *e, Ethumb_Thumb_Aspect a)
378 {
379    EINA_SAFETY_ON_NULL_RETURN(e);
380    EINA_SAFETY_ON_FALSE_RETURN(a == ETHUMB_THUMB_KEEP_ASPECT ||
381                                a == ETHUMB_THUMB_IGNORE_ASPECT ||
382                                a == ETHUMB_THUMB_CROP);
383
384    DBG("ethumb=%p, aspect=%d", e, a);
385    e->aspect = a;
386 }
387
388 EAPI Ethumb_Thumb_Aspect
389 ethumb_thumb_aspect_get(const Ethumb *e)
390 {
391    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
392    return e->aspect;
393 }
394
395 EAPI void
396 ethumb_thumb_crop_align_set(Ethumb *e, float x, float y)
397 {
398    EINA_SAFETY_ON_NULL_RETURN(e);
399
400    DBG("ethumb=%p, x=%f, y=%f", e, x, y);
401    e->crop_x = x;
402    e->crop_y = y;
403 }
404
405 EAPI void
406 ethumb_thumb_crop_align_get(const Ethumb *e, float *x, float *y)
407 {
408    EINA_SAFETY_ON_NULL_RETURN(e);
409
410    if (x) *x = e->crop_x;
411    if (y) *y = e->crop_y;
412 }
413
414 EAPI void
415 ethumb_thumb_quality_set(Ethumb *e, int quality)
416 {
417    EINA_SAFETY_ON_NULL_RETURN(e);
418
419    DBG("ethumb=%p, quality=%d", e, quality);
420    e->quality = quality;
421 }
422
423 EAPI int
424 ethumb_thumb_quality_get(const Ethumb *e)
425 {
426    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
427    return e->quality;
428 }
429
430 EAPI void
431 ethumb_thumb_compress_set(Ethumb *e, int compress)
432 {
433    EINA_SAFETY_ON_NULL_RETURN(e);
434
435    DBG("ethumb=%p, compress=%d", e, compress);
436    e->compress = compress;
437 }
438
439 EAPI int
440 ethumb_thumb_compress_get(const Ethumb *e)
441 {
442    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
443    return e->compress;
444 }
445
446 EAPI Eina_Bool
447 ethumb_frame_set(Ethumb *e, const char *theme_file, const char *group, const char *swallow)
448 {
449    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
450
451    Ethumb_Frame *frame;
452    frame = e->frame;
453
454    DBG("ethumb=%p, theme_file=%s, group=%s, swallow=%s",
455        e, theme_file ? theme_file : "", group ? group : "",
456        swallow ? swallow : "");
457
458    if (frame)
459      {
460         edje_object_part_unswallow(frame->edje, e->img);
461         if (!theme_file)
462           _ethumb_frame_free(frame);
463      }
464
465    if (!theme_file)
466      {
467         e->frame = NULL;
468         return EINA_TRUE;
469      }
470
471    if (!frame)
472      {
473         frame = calloc(1, sizeof(Ethumb_Frame));
474         if (!frame)
475           {
476              ERR("could not allocate Ethumb_Frame structure.");
477              return EINA_FALSE;
478           }
479
480         frame->edje = edje_object_add(e->sub_e);
481         if (!frame->edje)
482           {
483              ERR("could not create edje frame object.");
484              _ethumb_frame_free(frame);
485              e->frame = NULL;
486              return EINA_FALSE;
487           }
488      }
489
490    if (!edje_object_file_set(frame->edje, theme_file, group))
491      {
492         ERR("could not load frame theme.");
493         _ethumb_frame_free(frame);
494         e->frame = NULL;
495         return EINA_FALSE;
496      }
497
498    edje_object_part_swallow(frame->edje, swallow, e->img);
499    if (!edje_object_part_swallow_get(frame->edje, swallow))
500      {
501         ERR("could not swallow image to edje frame.");
502         _ethumb_frame_free(frame);
503         e->frame = NULL;
504         return EINA_FALSE;
505      }
506
507    eina_stringshare_replace(&frame->file, theme_file);
508    eina_stringshare_replace(&frame->group, group);
509    eina_stringshare_replace(&frame->swallow, swallow);
510
511    e->frame = frame;
512
513    return EINA_TRUE;
514 }
515
516 EAPI void
517 ethumb_frame_get(const Ethumb *e, const char **theme_file, const char **group, const char **swallow)
518 {
519    EINA_SAFETY_ON_NULL_RETURN(e);
520
521    if (e->frame)
522      {
523         if (theme_file) *theme_file = e->frame->file;
524         if (group) *group = e->frame->group;
525         if (swallow) *swallow = e->frame->swallow;
526      }
527    else
528      {
529         if (theme_file) *theme_file = NULL;
530         if (group) *group = NULL;
531         if (swallow) *swallow = NULL;
532      }
533 }
534
535 static const char *
536 _ethumb_build_absolute_path(const char *path, char buf[PATH_MAX])
537 {
538    char *p;
539    int len;
540
541    if (!path)
542      return NULL;
543
544    p = buf;
545
546    if (path[0] == '/')
547      strcpy(p, path);
548    else if (path[0] == '~')
549      {
550         const char *home = getenv("HOME");
551         if (!home)
552           return NULL;
553         strcpy(p, home);
554         len = strlen(p);
555         p += len;
556         p[0] = '/';
557         p++;
558         strcpy(p, path + 2);
559      }
560    else
561      {
562         if (!getcwd(p, PATH_MAX))
563           return NULL;
564         len = strlen(p);
565         p += len;
566         p[0] = '/';
567         p++;
568         strcpy(p, path);
569      }
570
571    return buf;
572 }
573
574 EAPI void
575 ethumb_thumb_dir_path_set(Ethumb *e, const char *path)
576 {
577    char buf[PATH_MAX];
578    EINA_SAFETY_ON_NULL_RETURN(e);
579
580    DBG("ethumb=%p, path=%s", e, path ? path : "");
581    path = _ethumb_build_absolute_path(path, buf);
582    eina_stringshare_replace(&e->thumb_dir, path);
583 }
584
585 EAPI const char *
586 ethumb_thumb_dir_path_get(const Ethumb *e)
587 {
588    EINA_SAFETY_ON_NULL_RETURN_VAL(e, NULL);
589
590    return e->thumb_dir;
591 }
592
593 EAPI void
594 ethumb_thumb_category_set(Ethumb *e, const char *category)
595 {
596    EINA_SAFETY_ON_NULL_RETURN(e);
597
598    DBG("ethumb=%p, category=%s", e, category ? category : "");
599    eina_stringshare_replace(&e->category, category);
600 }
601
602 EAPI const char *
603 ethumb_thumb_category_get(const Ethumb *e)
604 {
605    EINA_SAFETY_ON_NULL_RETURN_VAL(e, NULL);
606
607    return e->category;
608 }
609
610 EAPI void
611 ethumb_video_start_set(Ethumb *e, float start)
612 {
613    EINA_SAFETY_ON_NULL_RETURN(e);
614    EINA_SAFETY_ON_FALSE_RETURN(start >= 0.0);
615    EINA_SAFETY_ON_FALSE_RETURN(start <= 1.0);
616
617    DBG("ethumb=%p, video_start=%f", e, start);
618    e->video.start = start;
619 }
620
621 EAPI float
622 ethumb_video_start_get(const Ethumb *e)
623 {
624    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
625
626    return e->video.start;
627 }
628
629 EAPI void
630 ethumb_video_time_set(Ethumb *e, float time)
631 {
632    EINA_SAFETY_ON_NULL_RETURN(e);
633
634    DBG("ethumb=%p, video_start=%f", e, time);
635    e->video.time = time;
636 }
637
638 EAPI float
639 ethumb_video_time_get(const Ethumb *e)
640 {
641    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
642
643    return e->video.time;
644 }
645
646 EAPI void
647 ethumb_video_interval_set(Ethumb *e, float interval)
648 {
649    EINA_SAFETY_ON_NULL_RETURN(e);
650
651    DBG("ethumb=%p, video_interval=%f", e, interval);
652    e->video.interval = interval;
653 }
654
655 EAPI float
656 ethumb_video_interval_get(const Ethumb *e)
657 {
658    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
659
660    return e->video.interval;
661 }
662
663 EAPI void
664 ethumb_video_ntimes_set(Ethumb *e, unsigned int ntimes)
665 {
666    EINA_SAFETY_ON_NULL_RETURN(e);
667    EINA_SAFETY_ON_FALSE_RETURN(ntimes > 0);
668
669    DBG("ethumb=%p, video_ntimes=%d", e, ntimes);
670    e->video.ntimes = ntimes;
671 }
672
673 EAPI unsigned int
674 ethumb_video_ntimes_get(const Ethumb *e)
675 {
676    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
677
678    return e->video.ntimes;
679 }
680
681 EAPI void
682 ethumb_video_fps_set(Ethumb *e, unsigned int fps)
683 {
684    EINA_SAFETY_ON_NULL_RETURN(e);
685    EINA_SAFETY_ON_FALSE_RETURN(fps > 0);
686
687    DBG("ethumb=%p, video_fps=%d", e, fps);
688    e->video.fps = fps;
689 }
690
691 EAPI unsigned int
692 ethumb_video_fps_get(const Ethumb *e)
693 {
694    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
695
696    return e->video.fps;
697 }
698
699 EAPI void
700 ethumb_document_page_set(Ethumb *e, unsigned int page)
701 {
702    EINA_SAFETY_ON_NULL_RETURN(e);
703
704    DBG("ethumb=%p, document_page=%d", e, page);
705    e->document.page = page;
706 }
707
708 EAPI unsigned int
709 ethumb_document_page_get(const Ethumb *e)
710 {
711    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
712
713    return e->document.page;
714 }
715
716 EAPI Eina_Bool
717 ethumb_file_set(Ethumb *e, const char *path, const char *key)
718 {
719    char buf[PATH_MAX];
720    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
721
722    DBG("ethumb=%p, path=%s, key=%s", e, path ? path : "", key ? key : "");
723    if (path && access(path, R_OK))
724      {
725         ERR("couldn't access file \"%s\"", path);
726         return EINA_FALSE;
727      }
728
729    path = _ethumb_build_absolute_path(path, buf);
730    eina_stringshare_replace(&e->src_path, path);
731    eina_stringshare_replace(&e->src_key, key);
732    eina_stringshare_replace(&e->thumb_path, NULL);
733    eina_stringshare_replace(&e->thumb_key, NULL);
734
735    return EINA_TRUE;
736 }
737
738 EAPI void
739 ethumb_file_get(const Ethumb *e, const char **path, const char **key)
740 {
741    EINA_SAFETY_ON_NULL_RETURN(e);
742
743    if (path) *path = e->src_path;
744    if (key) *key = e->src_key;
745 }
746
747 static const char ACCEPTABLE_URI_CHARS[96] = {
748      /*      !    "    #    $    %    &    '    (    )    *    +    ,    -    .    / */ 
749      0x00,0x3F,0x20,0x20,0x28,0x00,0x2C,0x3F,0x3F,0x3F,0x3F,0x2A,0x28,0x3F,0x3F,0x1C,
750      /* 0    1    2    3    4    5    6    7    8    9    :    ;    <    =    >    ? */
751      0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x38,0x20,0x20,0x2C,0x20,0x20,
752      /* @    A    B    C    D    E    F    G    H    I    J    K    L    M    N    O */
753      0x38,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
754      /* P    Q    R    S    T    U    V    W    X    Y    Z    [    \    ]    ^    _ */
755      0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x20,0x20,0x20,0x20,0x3F,
756      /* `    a    b    c    d    e    f    g    h    i    j    k    l    m    n    o */
757      0x20,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,
758      /* p    q    r    s    t    u    v    w    x    y    z    {    |    }    ~  DEL */
759      0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x20,0x20,0x20,0x3F,0x20
760 };
761
762 static const char *
763 _ethumb_generate_hash(const char *file)
764 {
765   int n;
766   MD5_CTX ctx;
767   char md5out[(2 * MD5_HASHBYTES) + 1];
768   unsigned char hash[MD5_HASHBYTES];
769   static const char hex[] = "0123456789abcdef";
770
771   char *uri;
772   char *t;
773   const unsigned char *c;
774
775 #define _check_uri_char(c) \
776   ((c) >= 32 && (c) < 128 && (ACCEPTABLE_URI_CHARS[(c) - 32] & 0x08))
777
778   EINA_SAFETY_ON_NULL_RETURN_VAL(file, NULL);
779
780   uri = alloca(3 * strlen(file) + 9);
781   memcpy(uri, "file://", sizeof("file://") - 1);
782   t = uri + sizeof("file://") - 1;
783
784   for (c = (const unsigned char *)file; *c != '\0'; c++)
785     {
786        if (!_check_uri_char(*c))
787          {
788             *t++ = '%';
789             *t++ = hex[*c >> 4];
790             *t++ = hex[*c & 15];
791          }
792        else
793          *t++ = *c;
794     }
795   *t = '\0';
796
797 #undef _check_uri_char
798
799   MD5Init (&ctx);
800   MD5Update (&ctx, (unsigned char const*)uri, (unsigned)strlen (uri));
801   MD5Final (hash, &ctx);
802
803   for (n = 0; n < MD5_HASHBYTES; n++)
804     {
805       md5out[2 * n] = hex[hash[n] >> 4];
806       md5out[2 * n + 1] = hex[hash[n] & 0x0f];
807     }
808   md5out[2 * n] = '\0';
809
810   DBG("md5=%s, file=%s", md5out, file);
811   return eina_stringshare_add(md5out);
812 }
813
814 static int
815 _ethumb_file_check_fdo(Ethumb *e)
816 {
817    if (!((e->tw == THUMB_SIZE_NORMAL && e->th == THUMB_SIZE_NORMAL) ||
818        (e->tw == THUMB_SIZE_LARGE && e->th == THUMB_SIZE_LARGE)))
819      return 0;
820
821    if (e->format != ETHUMB_THUMB_FDO)
822      return 0;
823
824    if (e->aspect != ETHUMB_THUMB_KEEP_ASPECT)
825      return 0;
826
827    if (e->frame)
828      return 0;
829
830    return 1;
831 }
832
833 static const char *
834 _ethumb_file_generate_custom_category(Ethumb *e)
835 {
836    char buf[PATH_MAX];
837    const char *aspect, *format;
838    const char *frame;
839
840    if (e->aspect == ETHUMB_THUMB_KEEP_ASPECT)
841      aspect = "keep_aspect";
842    else if (e->aspect == ETHUMB_THUMB_IGNORE_ASPECT)
843      aspect = "ignore_aspect";
844    else
845      aspect = "crop";
846
847    if (e->format == ETHUMB_THUMB_FDO)
848      format = "png";
849    else if (e->format == ETHUMB_THUMB_JPEG)
850      format = "jpg";
851    else
852      format = "eet";
853
854    if (e->frame)
855      frame = "-framed";
856    else
857      frame = "";
858
859    snprintf(buf, sizeof(buf), "%dx%d-%s%s-%s",
860             e->tw, e->th, aspect, frame, format);
861
862    return eina_stringshare_add(buf);
863 }
864
865 static void
866 _ethumb_file_generate_path(Ethumb *e)
867 {
868    char buf[PATH_MAX];
869    char *fullname;
870    const char *hash;
871    const char *thumb_dir, *category;
872    const char *ext;
873    int fdo_format;
874
875
876    fdo_format = _ethumb_file_check_fdo(e);
877
878    if (e->thumb_dir)
879      thumb_dir = eina_stringshare_ref(e->thumb_dir);
880    else
881      thumb_dir = eina_stringshare_ref(_home_thumb_dir);
882
883    if (e->category)
884      category = eina_stringshare_ref(e->category);
885    else if (!fdo_format)
886      category = _ethumb_file_generate_custom_category(e);
887    else
888      {
889         if (e->tw == THUMB_SIZE_NORMAL)
890           category = eina_stringshare_ref(_thumb_category_normal);
891         else if (e->tw == THUMB_SIZE_LARGE)
892           category = eina_stringshare_ref(_thumb_category_large);
893      }
894
895    if (e->format == ETHUMB_THUMB_FDO)
896      ext = "png";
897    else if (e->format == ETHUMB_THUMB_JPEG)
898      ext = "jpg";
899    else
900      ext = "eet";
901
902
903    fullname = ecore_file_realpath(e->src_path);
904    hash = _ethumb_generate_hash(fullname);
905    snprintf(buf, sizeof(buf), "%s/%s/%s.%s", thumb_dir, category, hash, ext);
906    free(fullname);
907    DBG("ethumb=%p, path=%s", e, buf);
908    eina_stringshare_replace(&e->thumb_path, buf);
909    if (e->format == ETHUMB_THUMB_EET)
910      eina_stringshare_replace(&e->thumb_key, "thumbnail");
911    else
912      {
913         eina_stringshare_del(e->thumb_key);
914         e->thumb_key = NULL;
915      }
916
917    eina_stringshare_del(thumb_dir);
918    eina_stringshare_del(category);
919    eina_stringshare_del(hash);
920 }
921
922 EAPI void
923 ethumb_file_free(Ethumb *e)
924 {
925    EINA_SAFETY_ON_NULL_RETURN(e);
926    DBG("ethumb=%p", e);
927
928    eina_stringshare_replace(&e->src_path, NULL);
929    eina_stringshare_replace(&e->src_key, NULL);
930    eina_stringshare_replace(&e->thumb_path, NULL);
931    eina_stringshare_replace(&e->thumb_key, NULL);
932 }
933
934 EAPI void
935 ethumb_thumb_path_set(Ethumb *e, const char *path, const char *key)
936 {
937    char buf[PATH_MAX];
938
939    EINA_SAFETY_ON_NULL_RETURN(e);
940    DBG("ethumb=%p, path=%s, key=%s", e, path ? path : "", key ? key : "");
941
942    if (!path)
943      {
944         eina_stringshare_replace(&e->thumb_path, NULL);
945         eina_stringshare_replace(&e->thumb_key, NULL);
946      }
947    else
948      {
949         path = _ethumb_build_absolute_path(path, buf);
950         eina_stringshare_replace(&e->thumb_path, path);
951         eina_stringshare_replace(&e->thumb_key, key);
952      }
953 }
954
955 EAPI void
956 ethumb_thumb_path_get(Ethumb *e, const char **path, const char **key)
957 {
958    EINA_SAFETY_ON_NULL_RETURN(e);
959    if (!e->thumb_path)
960      _ethumb_file_generate_path(e);
961
962    if (path) *path = e->thumb_path;
963    if (key) *key = e->thumb_key;
964 }
965
966 void
967 ethumb_calculate_aspect_from_ratio(Ethumb *e, float ia, int *w, int *h)
968 {
969    float a;
970
971    *w = e->tw;
972    *h = e->th;
973
974    if (ia == 0)
975      return;
976
977    a = e->tw / (float)e->th;
978
979    if (e->aspect == ETHUMB_THUMB_KEEP_ASPECT)
980      {
981         if ((ia > a && e->tw > 0) || e->th <= 0)
982           *h = e->tw / ia;
983         else
984           *w = e->th * ia;
985      }
986 }
987
988 void
989 ethumb_calculate_aspect(Ethumb *e, int iw, int ih, int *w, int *h)
990 {
991    float ia;
992
993    ia = iw / (float)ih;
994
995    ethumb_calculate_aspect_from_ratio(e, ia, w, h);
996 }
997
998 void
999 ethumb_calculate_fill_from_ratio(Ethumb *e, float ia, int *fx, int *fy, int *fw, int *fh)
1000 {
1001    float a;
1002
1003    if (ia == 0)
1004      return;
1005
1006    *fw = e->tw;
1007    *fh = e->th;
1008    *fx = 0;
1009    *fy = 0;
1010    a = e->tw / (float)e->th;
1011
1012    if (e->aspect == ETHUMB_THUMB_CROP)
1013      {
1014         if ((ia > a && e->tw > 0) || e->th <= 0)
1015           *fw = e->th * ia;
1016         else
1017           *fh = e->tw / ia;
1018
1019         *fx = - e->crop_x * (*fw - e->tw);
1020         *fy = - e->crop_y * (*fh - e->th);
1021      }
1022    else if (e->aspect == ETHUMB_THUMB_KEEP_ASPECT)
1023      {
1024         if ((ia > a && e->tw > 0) || e->th <= 0)
1025           *fh = e->tw / ia;
1026         else
1027           *fw = e->th * ia;
1028      }
1029 }
1030
1031 void
1032 ethumb_calculate_fill(Ethumb *e, int iw, int ih, int *fx, int *fy, int *fw, int *fh)
1033 {
1034    float ia;
1035    ia = iw / (float)ih;
1036
1037    ethumb_calculate_fill_from_ratio(e, ia, fx, fy, fw, fh);
1038 }
1039
1040 static Eina_Bool
1041 _ethumb_plugin_generate(Ethumb *e)
1042 {
1043    const char *ext;
1044    Ethumb_Plugin *plugin;
1045
1046    ext = strrchr(e->src_path, '.');
1047    if (!ext)
1048      {
1049         ERR("could not get extension for file \"%s\"", e->src_path);
1050         return EINA_FALSE;
1051      }
1052
1053    plugin = eina_hash_find(_plugins_ext, ext + 1);
1054    if (!plugin)
1055      {
1056         DBG("no plugin for extension: \"%s\"", ext + 1);
1057         return EINA_FALSE;
1058      }
1059
1060    if (e->frame)
1061      evas_object_hide(e->frame->edje);
1062    else
1063      evas_object_hide(e->img);
1064
1065    plugin->generate_thumb(e);
1066
1067    return EINA_TRUE;
1068 }
1069
1070 Eina_Bool
1071 ethumb_plugin_image_resize(Ethumb *e, int w, int h)
1072 {
1073    Evas_Object *img;
1074
1075    img = e->img;
1076
1077    if (e->frame)
1078      {
1079         edje_extern_object_min_size_set(img, w, h);
1080         edje_extern_object_max_size_set(img, w, h);
1081         edje_object_calc_force(e->frame->edje);
1082         evas_object_move(e->frame->edje, 0, 0);
1083         evas_object_resize(e->frame->edje, w, h);
1084      }
1085    else
1086      {
1087         evas_object_move(img, 0, 0);
1088         evas_object_resize(img, w, h);
1089      }
1090
1091    evas_object_image_size_set(e->o, w, h);
1092    ecore_evas_resize(e->sub_ee, w, h);
1093
1094    e->rw = w;
1095    e->rh = h;
1096
1097    return EINA_TRUE;
1098 }
1099
1100 Eina_Bool
1101 ethumb_image_save(Ethumb *e)
1102 {
1103    Eina_Bool r;
1104    char *dname;
1105    char flags[256];
1106
1107    evas_damage_rectangle_add(e->sub_e, 0, 0, e->rw, e->rh);
1108    evas_render(e->sub_e);
1109
1110    if (!e->thumb_path)
1111      _ethumb_file_generate_path(e);
1112
1113    if (!e->thumb_path)
1114      {
1115         ERR("could not create file path...");
1116         return EINA_FALSE;
1117      }
1118
1119    dname = ecore_file_dir_get(e->thumb_path);
1120    r = ecore_file_mkpath(dname);
1121    free(dname);
1122    if (!r)
1123      {
1124         ERR("could not create directory '%s'", dname);
1125         return EINA_FALSE;
1126      }
1127
1128    snprintf(flags, sizeof(flags), "quality=%d compress=%d",
1129             e->quality, e->compress);
1130    r = evas_object_image_save(e->o, e->thumb_path, e->thumb_key, flags);
1131
1132    if (!r)
1133      {
1134         ERR("could not save image.");
1135         return EINA_FALSE;
1136      }
1137
1138    return EINA_TRUE;
1139 }
1140
1141 static int
1142 _ethumb_image_load(Ethumb *e)
1143 {
1144    int error;
1145    Evas_Coord w, h, ww, hh, fx, fy, fw, fh;
1146    Evas_Object *img;
1147
1148    img = e->img;
1149
1150    if (e->frame)
1151      evas_object_hide(e->frame->edje);
1152    else
1153      evas_object_hide(img);
1154    evas_object_image_file_set(img, NULL, NULL);
1155    evas_object_image_load_size_set(img, e->tw, e->th);
1156    evas_object_image_file_set(img, e->src_path, e->src_key);
1157
1158    if (e->frame)
1159      evas_object_show(e->frame->edje);
1160    else
1161      evas_object_show(img);
1162
1163    error = evas_object_image_load_error_get(img);
1164    if (error != EVAS_LOAD_ERROR_NONE)
1165      {
1166         ERR("could not load image '%s': %d", e->src_path, error);
1167         return 0;
1168      }
1169
1170    evas_object_image_size_get(img, &w, &h);
1171    if ((w <= 0) || (h <= 0))
1172      return 0;
1173
1174    ethumb_calculate_aspect(e, w, h, &ww, &hh);
1175
1176    if (e->frame)
1177      {
1178         edje_extern_object_min_size_set(img, ww, hh);
1179         edje_extern_object_max_size_set(img, ww, hh);
1180         edje_object_calc_force(e->frame->edje);
1181         evas_object_move(e->frame->edje, 0, 0);
1182         evas_object_resize(e->frame->edje, ww, hh);
1183      }
1184    else
1185      {
1186         evas_object_move(img, 0, 0);
1187         evas_object_resize(img, ww, hh);
1188      }
1189
1190    ethumb_calculate_fill(e, w, h, &fx, &fy, &fw, &fh);
1191    evas_object_image_fill_set(img, fx, fy, fw, fh);
1192
1193    evas_object_image_size_set(e->o, ww, hh);
1194    ecore_evas_resize(e->sub_ee, ww, hh);
1195
1196    e->rw = ww;
1197    e->rh = hh;
1198
1199    return 1;
1200 }
1201
1202 static int
1203 _ethumb_finished_idler_cb(void *data)
1204 {
1205    Ethumb *e = data;
1206
1207    e->finished_cb(e->cb_data, e, e->cb_result);
1208    if (e->cb_data_free)
1209      e->cb_data_free(e->cb_data);
1210    e->finished_idler = NULL;
1211    e->finished_cb = NULL;
1212    e->cb_data = NULL;
1213    e->cb_data_free = NULL;
1214
1215    return 0;
1216 }
1217
1218 void
1219 ethumb_finished_callback_call(Ethumb *e, int result)
1220 {
1221    EINA_SAFETY_ON_NULL_RETURN(e);
1222
1223    e->cb_result = result;
1224    if (e->finished_idler)
1225      ecore_idler_del(e->finished_idler);
1226    e->finished_idler = ecore_idler_add(_ethumb_finished_idler_cb, e);
1227 }
1228
1229 EAPI Eina_Bool
1230 ethumb_generate(Ethumb *e, Ethumb_Generate_Cb finished_cb, const void *data, Eina_Free_Cb free_data)
1231 {
1232    int r;
1233
1234    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
1235    EINA_SAFETY_ON_NULL_RETURN_VAL(finished_cb, 0);
1236    DBG("ethumb=%p, finished_cb=%p, data=%p, free_data=%p, path=%s, key=%s",
1237        e, finished_cb, data, free_data,
1238        e->src_path ? e->src_path : "", e->src_key ? e->src_key : "");
1239
1240    if (e->finished_idler)
1241      {
1242         ERR("thumbnail generation already in progress.");
1243         return EINA_FALSE;
1244      }
1245    e->finished_cb = finished_cb;
1246    e->cb_data = (void *)data;
1247    e->cb_data_free = free_data;
1248
1249    if (!e->src_path)
1250      {
1251         ERR("no file set.");
1252         ethumb_finished_callback_call(e, 0);
1253         return EINA_TRUE;
1254      }
1255
1256    r = _ethumb_plugin_generate(e);
1257    if (r)
1258      return EINA_TRUE;
1259
1260    if (!_ethumb_image_load(e))
1261      {
1262         ERR("could not load input image.");
1263         ethumb_finished_callback_call(e, 0);
1264         return EINA_TRUE;
1265      }
1266
1267    r = ethumb_image_save(e);
1268
1269    ethumb_finished_callback_call(e, r);
1270
1271    return EINA_TRUE;
1272 }
1273
1274 EAPI Eina_Bool
1275 ethumb_exists(Ethumb *e)
1276 {
1277    struct stat thumb, src;
1278    int r_thumb, r_src;
1279    Eina_Bool r = EINA_FALSE;
1280
1281    EINA_SAFETY_ON_NULL_RETURN_VAL(e, 0);
1282    EINA_SAFETY_ON_NULL_RETURN_VAL(e->src_path, 0);
1283    DBG("ethumb=%p, path=%s", e, e->src_path ? e->src_path : "");
1284
1285    if (!e->thumb_path)
1286      _ethumb_file_generate_path(e);
1287
1288    EINA_SAFETY_ON_NULL_RETURN_VAL(e->thumb_path, 0);
1289
1290    r_thumb = stat(e->thumb_path, &thumb);
1291    r_src = stat(e->src_path, &src);
1292
1293    EINA_SAFETY_ON_TRUE_RETURN_VAL(r_src, 0);
1294
1295    if (r_thumb && errno != ENOENT)
1296      ERR("could not access file \"%s\": %s", e->thumb_path, strerror(errno));
1297    else if (!r_thumb && thumb.st_mtime > src.st_mtime)
1298      r = EINA_TRUE;
1299
1300    return r;
1301 }
1302
1303 Evas *
1304 ethumb_evas_get(const Ethumb *e)
1305 {
1306    EINA_SAFETY_ON_NULL_RETURN_VAL(e, NULL);
1307
1308    return e->sub_e;
1309 }
1310
1311 Ecore_Evas *
1312 ethumb_ecore_evas_get(const Ethumb *e)
1313 {
1314    EINA_SAFETY_ON_NULL_RETURN_VAL(e, NULL);
1315
1316    return e->sub_ee;
1317 }