20ae4c46c614d501a0dc135e9cdd0198c05a17d8
[framework/uifw/e17.git] / src / modules / everything / evry_util.c
1 #include "e_mod_main.h"
2 #include "md5.h"
3
4 #define MAX_FUZZ 100
5 #define MAX_WORDS 5
6
7 static const char  *home_dir = NULL;
8 static int home_dir_len;
9 static char dir_buf[1024];
10 static char thumb_buf[4096];
11
12 void
13 evry_util_file_detail_set(Evry_Item_File *file)
14 {
15    char *dir = NULL;
16    const char *tmp;
17
18    if (EVRY_ITEM(file)->detail)
19      return;
20
21    if (!home_dir)
22      {
23         home_dir = e_user_homedir_get();
24         home_dir_len = strlen(home_dir);
25      }
26
27    dir = ecore_file_dir_get(file->path);
28    if (!dir || !home_dir) return;
29
30    if (!strncmp(dir, home_dir, home_dir_len))
31      {
32         tmp = dir + home_dir_len;
33
34         if (*(tmp) == '\0')
35           snprintf(dir_buf, sizeof(dir_buf), "~%s", tmp);
36         else
37           snprintf(dir_buf, sizeof(dir_buf), "~%s/", tmp);
38
39         EVRY_ITEM(file)->detail = eina_stringshare_add(dir_buf);
40      }
41    else
42      {
43         if (!strncmp(dir, "//", 2))
44           EVRY_ITEM(file)->detail = eina_stringshare_add(dir + 1);
45         else
46           EVRY_ITEM(file)->detail = eina_stringshare_add(dir);
47      }
48
49    E_FREE(dir);
50 }
51
52 int
53 evry_fuzzy_match(const char *str, const char *match)
54 {
55    const char *p, *m, *next;
56    int sum = 0;
57
58    unsigned int last = 0;
59    unsigned int offset = 0;
60    unsigned int min = 0;
61    unsigned char first = 0;
62    /* ignore punctuation */
63    unsigned char ip = 1;
64
65    unsigned int cnt = 0;
66    /* words in match */
67    unsigned int m_num = 0;
68    unsigned int m_cnt = 0;
69    unsigned int m_min[MAX_WORDS];
70    unsigned int m_len = 0;
71
72    if (!match || !str || !match[0] || !str[0])
73      return 0;
74
75    /* remove white spaces at the beginning */
76    for (; (*match != 0) && isspace(*match); match++);
77    for (; (*str != 0)   && isspace(*str);   str++);
78
79    /* count words in match */
80    for (m = match; (*m != 0) && (m_num < MAX_WORDS);)
81      {
82         for (; (*m != 0) && !isspace(*m); m++);
83         for (; (*m != 0) &&  isspace(*m); m++);
84         m_min[m_num++] = MAX_FUZZ;
85      }
86    for (m = match; ip && (*m != 0); m++)
87      if (ip && ispunct(*m)) ip = 0;
88
89    m_len = strlen(match);
90
91    /* with less than 3 chars match must be a prefix */
92    if (m_len < 3) m_len = 0;
93
94    next = str;
95    m = match;
96
97    while((m_cnt < m_num) && (*next != 0))
98      {
99         /* reset match */
100         if (m_cnt == 0) m = match;
101
102         /* end of matching */
103         if (*m == 0) break;
104
105         offset = 0;
106         last = 0;
107         min = 1;
108         first = 0;
109         /* m_len = 0; */
110
111         /* match current word of string against current match */
112         for (p = next; *next != 0; p++)
113           {
114              /* new word of string begins */
115              if ((*p == 0) || isspace(*p) || (ip && ispunct(*p)))
116                {
117                   if (m_cnt < m_num - 1)
118                     {
119                        /* test next match */
120                        for (; (*m != 0) && !isspace(*m); m++);
121                        for (; (*m != 0) &&  isspace(*m); m++);
122                        m_cnt++;
123                        break;
124                     }
125                   else
126                     {
127                        /* go to next word */
128                        for (; (*p != 0) && ((isspace(*p) || (ip && ispunct(*p)))); p++);
129                        cnt++;
130                        next = p;
131                        m_cnt = 0;
132                        break;
133                     }
134                }
135
136              /* current char matches? */
137              if (tolower(*p) != tolower(*m))
138                {
139                   if (!first)
140                     offset += 1;
141                   else
142                     offset += 3;
143
144                   /* m_len++; */
145
146                   if (offset <= m_len * 3)
147                     continue;
148                }
149
150              if (min < MAX_FUZZ && offset <= m_len * 3)
151                {
152                   /* first offset of match in word */
153                   if (!first)
154                     {
155                        first = 1;
156                        last = offset;
157                     }
158
159                   min += offset + (offset - last) * 5;
160                   last = offset;
161
162                   /* try next char of match */
163                   if (*(++m) != 0 && !isspace(*m))
164                     continue;
165
166                   /* end of match: store min weight of match */
167                   min += (cnt - m_cnt) > 0 ? (cnt - m_cnt) : 0;
168
169                   if (min < m_min[m_cnt])
170                     m_min[m_cnt] = min;
171                }
172              else
173                {
174                   /* go to next match */
175                   for (; (*m != 0) && !isspace(*m); m++);
176                }
177
178              if (m_cnt < m_num - 1)
179                {
180                   /* test next match */
181                   for (; (*m != 0) && isspace(*m); m++);
182                   m_cnt++;
183                   break;
184                }
185              else if(*p != 0)
186                {
187                   /* go to next word */
188                   for (; (*p != 0) && !((isspace(*p) || (ip && ispunct(*p)))); p++);
189                   for (; (*p != 0) &&  ((isspace(*p) || (ip && ispunct(*p)))); p++);
190                   cnt++;
191                   next = p;
192                   m_cnt = 0;
193                   break;
194                }
195              else
196                {
197                   next = p;
198                   break;
199                }
200           }
201      }
202
203    for (m_cnt = 0; m_cnt < m_num; m_cnt++)
204      {
205         sum += m_min[m_cnt];
206
207         if (sum >= MAX_FUZZ)
208           {
209              sum = 0;
210              break;
211           }
212      }
213
214    if (sum > 0)
215      {
216         /* exact match ? */
217         if (strcmp(match, str))
218           sum += 10;
219      }
220
221    return sum;
222 }
223
224 static int
225 _evry_fuzzy_match_sort_cb(const void *data1, const void *data2)
226 {
227    const Evry_Item *it1 = data1;
228    const Evry_Item *it2 = data2;
229
230    if (it1->priority - it2->priority)
231      return (it1->priority - it2->priority);
232
233    if (it1->fuzzy_match || it2->fuzzy_match)
234      {
235         if (it1->fuzzy_match && !it2->fuzzy_match)
236           return -1;
237
238         if (!it1->fuzzy_match && it2->fuzzy_match)
239           return 1;
240
241         if (it1->fuzzy_match - it2->fuzzy_match)
242           return (it1->fuzzy_match - it2->fuzzy_match);
243      }
244
245    return 0;
246 }
247
248 Eina_List *
249 evry_fuzzy_match_sort(Eina_List *items)
250 {
251    return eina_list_sort(items, -1, _evry_fuzzy_match_sort_cb);
252 }
253
254 int
255 evry_items_sort_func(const void *data1, const void *data2)
256 {
257    const Evry_Item *it1 = data1;
258    const Evry_Item *it2 = data2;
259
260    if ((it1->type == EVRY_TYPE_ACTION ||
261         it1->subtype == EVRY_TYPE_ACTION) &&
262        (it2->type == EVRY_TYPE_ACTION ||
263         it2->subtype == EVRY_TYPE_ACTION))
264      {
265         const Evry_Action *act1 = data1;
266         const Evry_Action *act2 = data2;
267
268         /* sort actions that match the specific type before
269            those matching general type */
270         if (act1->it1.item && act2->it1.item)
271           {
272              if ((act1->it1.type == act1->it1.item->type) &&
273                  (act2->it1.type != act2->it1.item->type))
274                return -1;
275
276              if ((act1->it1.type != act1->it1.item->type) &&
277                  (act2->it1.type == act2->it1.item->type))
278                return 1;
279           }
280
281         /* sort context specific actions before
282            general actions */
283         if (act1->remember_context)
284           {
285              if (!act2->remember_context)
286                return -1;
287           }
288         else
289           {
290              if (act2->remember_context)
291                return 1;
292           }
293      }
294
295    /* if (it1->type == EVRY_TYPE_PLUGIN &&
296     *     it2->type != EVRY_TYPE_PLUGIN)
297     *   {
298     *   return (it1->usage > it2->usage ? -1 : 1);
299     *   }
300     * else if (it2->type == EVRY_TYPE_PLUGIN &&
301     *       it1->type != EVRY_TYPE_PLUGIN)
302     *   {
303     *   return (it1->usage > it2->usage ? -1 : 1);
304     *   } */
305
306    /* sort items which match input or which
307       match much better first */
308    if (it1->fuzzy_match > 0 || it2->fuzzy_match > 0)
309      {
310         if (it2->fuzzy_match <= 0)
311           return -1;
312
313         if (it1->fuzzy_match <= 0)
314           return 1;
315
316         if (abs (it1->fuzzy_match - it2->fuzzy_match) > 5)
317           return (it1->fuzzy_match - it2->fuzzy_match);
318      }
319
320    /* sort recently/most frequently used items first */
321    if (it1->usage > 0.0 || it2->usage > 0.0)
322      {
323         return (it1->usage > it2->usage ? -1 : 1);
324      }
325
326    /* sort items which match input better first */
327    if (it1->fuzzy_match > 0 || it2->fuzzy_match > 0)
328      {
329         if (it1->fuzzy_match - it2->fuzzy_match)
330           return (it1->fuzzy_match - it2->fuzzy_match);
331      }
332
333    /* sort itemswith higher priority first */
334    if ((it1->plugin == it2->plugin) &&
335        (it1->priority - it2->priority))
336      return (it1->priority - it2->priority);
337
338    /* sort items with higher plugin priority first */
339    if (it1->type != EVRY_TYPE_ACTION &&
340        it2->type != EVRY_TYPE_ACTION)
341      {
342         int prio1 = it1->plugin->config->priority;
343         int prio2 = it2->plugin->config->priority;
344
345         if (prio1 - prio2)
346           return (prio1 - prio2);
347      }
348
349    return strcasecmp(it1->label, it2->label);
350 }
351
352 int
353 evry_util_plugin_items_add(Evry_Plugin *p, Eina_List *items, const char *input,
354                       int match_detail, int set_usage)
355 {
356    Eina_List *l;
357    Evry_Item *it;
358    int match = 0;
359
360    EINA_LIST_FOREACH(items, l, it)
361      {
362         it->fuzzy_match = 0;
363
364         if (set_usage)
365           evry_history_item_usage_set(it, input, NULL);
366
367         if (!input)
368           {
369              p->items = eina_list_append(p->items, it);
370              continue;
371           }
372
373         it->fuzzy_match = evry_fuzzy_match(it->label, input);
374
375         if (match_detail)
376           {
377              match = evry_fuzzy_match(it->detail, input);
378
379              if (!(it->fuzzy_match) || (match && (match < it->fuzzy_match)))
380                it->fuzzy_match = match;
381           }
382
383         if (it->fuzzy_match)
384           p->items = eina_list_append(p->items, it);
385      }
386
387    p->items = eina_list_sort(p->items, -1, evry_items_sort_func);
388
389    return !!(p->items);
390 }
391
392 Evas_Object *
393 evry_icon_theme_get(const char *icon, Evas *e)
394 {
395    Evas_Object *obj = e_icon_add(e);
396    e_icon_preload_set(obj, 1);
397    e_icon_scale_size_set(obj, 128); 
398
399    if (!e_util_icon_theme_set(obj, icon))
400      {
401         evas_object_del(obj);
402         obj = NULL;
403      }
404
405    return obj;
406 }
407
408 static Evas_Object *
409 _evry_icon_mime_theme_get(const char *mime, Evas *e)
410 {
411    Evas_Object *o = NULL;
412
413    char buf[1024];
414    const char *file;
415
416    if (snprintf(buf, sizeof(buf), "e/icons/fileman/mime/%s", mime) >= (int)sizeof(buf))
417      return NULL;
418
419    file = e_theme_edje_file_get("base/theme/icons", buf);
420    if (file && file[0])
421      {
422         o = edje_object_add(e);
423         if (!o) return NULL;
424         if (!edje_object_file_set(o, file, buf))
425           {
426              evas_object_del(o);
427              return NULL;
428           }
429         return o;
430      }
431
432    return NULL;
433 }
434
435 Evas_Object *
436 evry_icon_mime_get(const char *mime, Evas *e)
437 {
438    Evas_Object *o = NULL;
439    const char *icon;
440
441    if (!e_config->icon_theme_overrides)
442      o = _evry_icon_mime_theme_get(mime, e);
443
444    if (o) return o;
445
446    icon = efreet_mime_type_icon_get(mime, e_config->icon_theme, 128);
447    if (icon)
448      o = e_util_icon_add(icon, e);
449    if (o) return o;
450
451    return _evry_icon_mime_theme_get(mime, e);
452 }
453
454 static Evas_Object *
455 _file_icon_get(Evry_Item *it, Evas *e)
456 {
457    Evas_Object *o = NULL;
458    GET_FILE(file, it);
459
460    if (it->icon)
461      {
462         if (it->icon[0] == '/')
463           {
464              o = e_icon_add(e);
465              e_icon_preload_set(o, 1);
466
467              if (!e_icon_file_set(o, it->icon))
468                {
469                   evas_object_del(o);
470                   o = NULL;
471                }
472           }
473      }
474
475    if (!(o) && (!it->icon) && file->mime &&
476        (/*(!strncmp(file->mime, "image/", 6)) || */
477         (!strncmp(file->mime, "video/", 6)) ||
478         (!strncmp(file->mime, "application/pdf", 15))) &&
479        (evry_file_url_get(file)))
480      {
481         char *sum = evry_util_md5_sum(file->url);
482
483         snprintf(thumb_buf, sizeof(thumb_buf),
484                  "%s/.thumbnails/normal/%s.png",
485                  e_user_homedir_get(), sum);
486         free(sum);
487
488         if (ecore_file_exists(thumb_buf))
489           it->icon = eina_stringshare_add(thumb_buf);
490         else
491           it->icon = eina_stringshare_add("");
492      }
493
494    if (!(o) &&it->browseable)
495      o = evry_icon_theme_get("folder", e);
496
497    if (!(o) && file->mime)
498      o = evry_icon_mime_get(file->mime, e);
499
500    if (!o)
501      o = evry_icon_mime_get("unknown", e);
502
503    return o;
504 }
505
506 Evas_Object *
507 evry_util_icon_get(Evry_Item *it, Evas *e)
508 {
509    Evas_Object *o = NULL;
510
511    if (!o && it->icon_get)
512      o = it->icon_get(it, e);
513    if (o) return o;
514
515    if (CHECK_TYPE(it, EVRY_TYPE_FILE))
516      o = _file_icon_get(it, e);
517    if (o) return o;
518
519    if (!o && it->icon && it->icon[0] == '/')
520      {
521         o = e_icon_add(e);
522         e_icon_preload_set(o, 1);
523
524         if (!e_icon_file_set(o, it->icon))
525           {
526              evas_object_del(o);
527              o = NULL;
528           }
529      }
530
531    if (!o && it->icon)
532      o = evry_icon_theme_get(it->icon, e);
533
534    return o;
535 }
536
537 int
538 evry_util_exec_app(const Evry_Item *it_app, const Evry_Item *it_file)
539 {
540    E_Zone *zone;
541    Eina_List *files = NULL;
542    char *exe = NULL;
543    char *tmp = NULL;
544
545    if (!it_app) return 0;
546    GET_APP(app, it_app);
547    GET_FILE(file, it_file);
548
549    zone = e_util_zone_current_get(e_manager_current_get());
550
551    if (app->desktop)
552      {
553         if (file && evry_file_path_get(file))
554           {
555              Eina_List *l;
556              char *mime;
557              int open_folder = 0;
558
559              /* when the file is no a directory and the app
560                 opens folders, pass only the dir */
561              if (!IS_BROWSEABLE(file))
562                {
563                   EINA_LIST_FOREACH(app->desktop->mime_types, l, mime)
564                     {
565                        if (!mime)
566                          continue;
567
568                        if (!strcmp(mime, "x-directory/normal"))
569                          open_folder = 1;
570
571                        if (file->mime && !strcmp(mime, file->mime))
572                          {
573                             open_folder = 0;
574                             break;
575                          }
576                     }
577                }
578
579              if (open_folder)
580                {
581                   tmp = ecore_file_dir_get(file->path);
582                   files = eina_list_append(files, tmp);
583                }
584              else
585                {
586                   files = eina_list_append(files, file->path);
587                }
588
589              e_exec(zone, app->desktop, NULL, files, NULL);
590
591              if (file && file->mime && !open_folder)
592                e_exehist_mime_desktop_add(file->mime, app->desktop);
593
594              if (files)
595                eina_list_free(files);
596
597              E_FREE(tmp);
598           }
599         else if (app->file)
600           {
601              files = eina_list_append(files, app->file);
602              e_exec(zone, app->desktop, NULL, files, NULL);
603              eina_list_free(files);
604           }
605         else
606           {
607              e_exec(zone, app->desktop, NULL, NULL, NULL);
608           }
609      }
610    else if (app->file)
611      {
612         if (file && evry_file_path_get(file))
613           {
614              int len;
615              len = strlen(app->file) + strlen(file->path) + 4;
616              exe = malloc(len);
617              snprintf(exe, len, "%s \'%s\'", app->file, file->path);
618              e_exec(zone, NULL, exe, NULL, NULL);
619              E_FREE(exe);
620           }
621         else
622           {
623              exe = (char *) app->file;
624              e_exec(zone, NULL, exe, NULL, NULL);
625           }
626      }
627
628    return 1;
629 }
630
631 /* taken from curl:
632  *
633  * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et
634  * al.
635  *
636  * Unescapes the given URL escaped string of given length. Returns a
637  * pointer to a malloced string with length given in *olen.
638  * If length == 0, the length is assumed to be strlen(string).
639  * If olen == NULL, no output length is stored.
640  */
641 #define ISXDIGIT(x) (isxdigit((int) ((unsigned char)x)))
642
643 char *
644 evry_util_url_unescape(const char *string, int length)
645 {
646    int alloc = (length?length:(int)strlen(string))+1;
647    char *ns = malloc(alloc);
648    unsigned char in;
649    int strindex=0;
650    unsigned long hex;
651
652    if( !ns )
653      return NULL;
654
655    while(--alloc > 0)
656      {
657         in = *string;
658         if(('%' == in) && ISXDIGIT(string[1]) && ISXDIGIT(string[2]))
659           {
660              /* this is two hexadecimal digits following a '%' */
661              char hexstr[3];
662              char *ptr;
663              hexstr[0] = string[1];
664              hexstr[1] = string[2];
665              hexstr[2] = 0;
666
667              hex = strtoul(hexstr, &ptr, 16);
668              in = (unsigned char)(hex & (unsigned long) 0xFF);
669              // in = ultouc(hex); /* this long is never bigger than 255 anyway */
670
671              string+=2;
672              alloc-=2;
673           }
674
675         ns[strindex++] = in;
676         string++;
677      }
678    ns[strindex]=0; /* terminate it */
679
680    return ns;
681 }
682
683 #undef ISXDIGIT
684
685 static Eina_Bool
686 _isalnum(unsigned char in)
687 {
688    switch (in)
689      {
690       case '0': case '1': case '2': case '3': case '4':
691       case '5': case '6': case '7': case '8': case '9':
692       case 'a': case 'b': case 'c': case 'd': case 'e':
693       case 'f': case 'g': case 'h': case 'i': case 'j':
694       case 'k': case 'l': case 'm': case 'n': case 'o':
695       case 'p': case 'q': case 'r': case 's': case 't':
696       case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':
697       case 'A': case 'B': case 'C': case 'D': case 'E':
698       case 'F': case 'G': case 'H': case 'I': case 'J':
699       case 'K': case 'L': case 'M': case 'N': case 'O':
700       case 'P': case 'Q': case 'R': case 'S': case 'T':
701       case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
702          return EINA_TRUE;
703       default:
704          break;
705      }
706    return EINA_FALSE;
707 }
708
709 char *
710 evry_util_url_escape(const char *string, int inlength)
711 {
712    size_t alloc = (inlength?(size_t)inlength:strlen(string))+1;
713    char *ns;
714    char *testing_ptr = NULL;
715    unsigned char in; /* we need to treat the characters unsigned */
716    size_t newlen = alloc;
717    int strindex=0;
718    size_t length;
719
720    ns = malloc(alloc);
721    if(!ns)
722      return NULL;
723
724    length = alloc-1;
725    while(length--)
726      {
727         in = *string;
728
729         if (_isalnum(in))
730           {
731              /* just copy this */
732              ns[strindex++]=in;
733           }
734         else {
735            /* encode it */
736            newlen += 2; /* the size grows with two, since this'll become a %XX */
737            if(newlen > alloc)
738              {
739                 alloc *= 2;
740                 testing_ptr = realloc(ns, alloc);
741                 if(!testing_ptr)
742                   {
743                      free( ns );
744                      return NULL;
745                   }
746                 else
747                   {
748                      ns = testing_ptr;
749                   }
750              }
751
752            snprintf(&ns[strindex], 4, "%%%02X", in);
753
754            strindex+=3;
755         }
756         string++;
757      }
758    ns[strindex]=0; /* terminate it */
759    return ns;
760 }
761
762 const char*
763 evry_file_path_get(Evry_Item_File *file)
764 {
765    const char *tmp;
766    char *path;
767
768    if (file->path)
769      return file->path;
770
771    if (!file->url)
772      return NULL;
773
774    if (!strncmp(file->url, "file://", 7))
775      tmp = file->url + 7;
776    else return NULL;
777
778    if (!(path = evry_util_url_unescape(tmp, 0)))
779      return NULL;
780
781    file->path = eina_stringshare_add(path);
782
783    E_FREE(path);
784
785    return file->path;
786 }
787
788 const char*
789 evry_file_url_get(Evry_Item_File *file)
790 {
791    char dest[PATH_MAX * 3 + 7];
792    const char *p;
793    int i;
794
795    if (file->url)
796      return file->url;
797
798    if (!file->path)
799      return NULL;
800
801    memset(dest, 0, PATH_MAX * 3 + 7);
802
803    snprintf(dest, 8, "file://");
804
805    /* Most app doesn't handle the hostname in the uri so it's put to NULL */
806    for (i = 7, p = file->path; *p != '\0'; p++, i++)
807      {
808         if (isalnum(*p) || strchr("/$-_.+!*'()", *p))
809           dest[i] = *p;
810         else
811           {
812              snprintf(&(dest[i]), 4, "%%%02X", (unsigned char)*p);
813              i += 2;
814           }
815      }
816
817    file->url = eina_stringshare_add(dest);
818
819    return file->url;
820 }
821
822 static void
823 _cb_free_item_changed(void *data __UNUSED__, void *event)
824 {
825    Evry_Event_Item_Changed *ev = event;
826
827    evry_item_free(ev->item);
828    E_FREE(ev);
829 }
830
831 void
832 evry_item_changed(Evry_Item *it, int icon, int selected)
833 {
834    Evry_Event_Item_Changed *ev;
835    ev = E_NEW(Evry_Event_Item_Changed, 1);
836    ev->item = it;
837    ev->changed_selection = selected;
838    ev->changed_icon = icon;
839    evry_item_ref(it);
840    ecore_event_add(_evry_events[EVRY_EVENT_ITEM_CHANGED], ev, _cb_free_item_changed, NULL);
841 }
842
843 static char thumb_buf[4096];
844 static const char hex[] = "0123456789abcdef";
845
846 char *
847 evry_util_md5_sum(const char *str)
848 {
849    MD5_CTX ctx;
850    unsigned char hash[MD5_HASHBYTES];
851    int n;
852    char md5out[(2 * MD5_HASHBYTES) + 1];
853    MD5Init (&ctx);
854    MD5Update (&ctx, (unsigned char const*)str,
855               (unsigned)strlen (str));
856    MD5Final (hash, &ctx);
857
858    for (n = 0; n < MD5_HASHBYTES; n++)
859      {
860         md5out[2 * n] = hex[hash[n] >> 4];
861         md5out[2 * n + 1] = hex[hash[n] & 0x0f];
862      }
863    md5out[2 * n] = '\0';
864
865    return strdup(md5out);
866 }