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