update for beta release
[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 static int _sort_flags = 0;
255
256 static int
257 _evry_items_sort_func(const void *data1, const void *data2)
258 {
259    const Evry_Item *it1 = data1;
260    const Evry_Item *it2 = data2;
261
262    /* if (!((!_sort_flags) &&
263     *       (it1->type == EVRY_TYPE_ACTION) &&
264     *       (it2->type == EVRY_TYPE_ACTION)))
265     *   { */
266    /* only sort actions when there is input otherwise show default order */
267
268    if (((it1->type == EVRY_TYPE_ACTION) || (it1->subtype == EVRY_TYPE_ACTION)) &&
269        ((it2->type == EVRY_TYPE_ACTION) || (it2->subtype == EVRY_TYPE_ACTION)))
270      {
271         const Evry_Action *act1 = data1;
272         const Evry_Action *act2 = data2;
273
274         /* sort actions that match the specific type before
275            those matching general type */
276         if (act1->it1.item && act2->it1.item)
277           {
278              if ((act1->it1.type == act1->it1.item->type) &&
279                  (act2->it1.type != act2->it1.item->type))
280                return -1;
281
282              if ((act1->it1.type != act1->it1.item->type) &&
283                  (act2->it1.type == act2->it1.item->type))
284                return 1;
285           }
286
287         /* sort context specific actions before
288            general actions */
289         if (act1->remember_context)
290           {
291              if (!act2->remember_context)
292                return -1;
293           }
294         else
295           {
296              if (act2->remember_context)
297                return 1;
298           }
299      }
300    /* } */
301
302    if (_sort_flags)
303      {
304         /* when there is no input sort items with higher
305          * plugin priority first */
306         if (it1->type != EVRY_TYPE_ACTION &&
307             it2->type != EVRY_TYPE_ACTION)
308           {
309              int prio1 = it1->plugin->config->priority;
310              int prio2 = it2->plugin->config->priority;
311
312              if (prio1 - prio2)
313                return prio1 - prio2;
314           }
315      }
316
317    /* sort items which match input or which
318       match much better first */
319    if (it1->fuzzy_match > 0 || it2->fuzzy_match > 0)
320      {
321         if (it2->fuzzy_match <= 0)
322           return -1;
323
324         if (it1->fuzzy_match <= 0)
325           return 1;
326
327         if (abs (it1->fuzzy_match - it2->fuzzy_match) > 5)
328           return it1->fuzzy_match - it2->fuzzy_match;
329      }
330
331    /* sort recently/most frequently used items first */
332    if (it1->usage > 0.0 || it2->usage > 0.0)
333      {
334         return it1->usage > it2->usage ? -1 : 1;
335      }
336
337    /* sort items which match input better first */
338    if (it1->fuzzy_match > 0 || it2->fuzzy_match > 0)
339      {
340         if (it1->fuzzy_match - it2->fuzzy_match)
341           return it1->fuzzy_match - it2->fuzzy_match;
342      }
343
344    /* sort itemswith higher priority first */
345    if ((it1->plugin == it2->plugin) &&
346        (it1->priority - it2->priority))
347      return it1->priority - it2->priority;
348
349    /* sort items with higher plugin priority first */
350    if (it1->type != EVRY_TYPE_ACTION &&
351        it2->type != EVRY_TYPE_ACTION)
352      {
353         int prio1 = it1->plugin->config->priority;
354         int prio2 = it2->plugin->config->priority;
355
356         if (prio1 - prio2)
357           return prio1 - prio2;
358      }
359
360    return strcasecmp(it1->label, it2->label);
361 }
362
363 void
364 evry_util_items_sort(Eina_List **items, int flags)
365 {
366    _sort_flags = flags;
367    *items = eina_list_sort(*items, -1, _evry_items_sort_func);
368    _sort_flags = 0;
369 }
370
371 int
372 evry_util_plugin_items_add(Evry_Plugin *p, Eina_List *items, const char *input,
373                            int match_detail, int set_usage)
374 {
375    Eina_List *l;
376    Evry_Item *it;
377    int match = 0;
378
379    EINA_LIST_FOREACH (items, l, it)
380      {
381         it->fuzzy_match = 0;
382
383         if (set_usage)
384           evry_history_item_usage_set(it, input, NULL);
385
386         if (!input)
387           {
388              p->items = eina_list_append(p->items, it);
389              continue;
390           }
391
392         it->fuzzy_match = evry_fuzzy_match(it->label, input);
393
394         if (match_detail)
395           {
396              match = evry_fuzzy_match(it->detail, input);
397
398              if (!(it->fuzzy_match) || (match && (match < it->fuzzy_match)))
399                it->fuzzy_match = match;
400           }
401
402         if (it->fuzzy_match)
403           p->items = eina_list_append(p->items, it);
404      }
405
406    p->items = eina_list_sort(p->items, -1, _evry_items_sort_func);
407
408    return !!(p->items);
409 }
410
411 Evas_Object *
412 evry_icon_theme_get(const char *icon, Evas *e)
413 {
414    Evas_Object *o = NULL;
415
416    if (!icon)
417      return NULL;
418
419    o = e_icon_add(e);
420    e_icon_scale_size_set(o, 128);
421    e_icon_preload_set(o, 1);
422
423    if (icon[0] == '/')
424      {
425         if (!e_icon_file_set(o, icon))
426           {
427              evas_object_del(o);
428              o = NULL;
429           }
430      }
431    else if (!e_util_icon_theme_set(o, icon))
432      {
433         evas_object_del(o);
434         o = NULL;
435      }
436
437    return o;
438 }
439
440 Evas_Object *
441 evry_util_icon_get(Evry_Item *it, Evas *e)
442 {
443    Evas_Object *o = NULL;
444
445    if (it->icon_get)
446      {
447         o = it->icon_get(it, e);
448         if (o) return o;
449      }
450
451    if ((it->icon) && (it->icon[0] == '/'))
452      {
453         o = evry_icon_theme_get(it->icon, e);
454         if (o) return o;
455      }
456
457    if (CHECK_TYPE(it, EVRY_TYPE_FILE))
458      {
459         const char *icon;
460         char *sum;
461
462         GET_FILE(file, it);
463
464         if (it->browseable)
465           {
466              o = evry_icon_theme_get("folder", e);
467              if (o) return o;
468           }
469
470         if ((!it->icon) && (file->mime) &&
471             ( /*(!strncmp(file->mime, "image/", 6)) || */
472               (!strncmp(file->mime, "video/", 6)) ||
473               (!strncmp(file->mime, "application/pdf", 15))) &&
474             (evry_file_url_get(file)))
475           {
476              sum = evry_util_md5_sum(file->url);
477
478              snprintf(thumb_buf, sizeof(thumb_buf),
479                       "%s/.thumbnails/normal/%s.png",
480                       e_user_homedir_get(), sum);
481              free(sum);
482
483              if ((o = evry_icon_theme_get(thumb_buf, e)))
484                {
485                   it->icon = eina_stringshare_add(thumb_buf);
486                   return o;
487                }
488           }
489
490         if ((!it->icon) && (file->mime))
491           {
492              icon = efreet_mime_type_icon_get(file->mime, e_config->icon_theme, 128);
493              /* XXX can do _ref ?*/
494              if ((o = evry_icon_theme_get(icon, e)))
495                {
496                   /* it->icon = eina_stringshare_add(icon); */
497                   return o;
498                }
499           }
500
501         if ((icon = efreet_mime_type_icon_get("unknown", e_config->icon_theme, 128)))
502           it->icon = eina_stringshare_add(icon);
503         else
504           it->icon = eina_stringshare_add("");
505      }
506
507    if (CHECK_TYPE(it, EVRY_TYPE_APP))
508      {
509         GET_APP(app, it);
510
511         o = e_util_desktop_icon_add(app->desktop, 128, e);
512         if (o) return o;
513
514         o = evry_icon_theme_get("system-run", e);
515         if (o) return o;
516      }
517
518    if (it->icon)
519      {
520         o = evry_icon_theme_get(it->icon, e);
521         if (o) return o;
522      }
523
524    if (it->browseable)
525      {
526         o = evry_icon_theme_get("folder", e);
527         if (o) return o;
528      }
529
530    o = evry_icon_theme_get("unknown", e);
531    return o;
532 }
533
534 int
535 evry_util_exec_app(const Evry_Item *it_app, const Evry_Item *it_file)
536 {
537    E_Zone *zone;
538    Eina_List *files = NULL;
539    char *exe = NULL;
540    char *tmp = NULL;
541
542    if (!it_app) return 0;
543    GET_APP(app, it_app);
544    GET_FILE(file, it_file);
545
546    zone = e_util_zone_current_get(e_manager_current_get());
547
548    if (app->desktop)
549      {
550         if (file && evry_file_path_get(file))
551           {
552              Eina_List *l;
553              char *mime;
554              int open_folder = 0;
555
556              /* when the file is no a directory and the app
557                 opens folders, pass only the dir */
558              if (!IS_BROWSEABLE(file))
559                {
560                   EINA_LIST_FOREACH (app->desktop->mime_types, l, mime)
561                     {
562                        if (!mime)
563                          continue;
564
565                        if (!strcmp(mime, "x-directory/normal"))
566                          open_folder = 1;
567
568                        if (file->mime && !strcmp(mime, file->mime))
569                          {
570                             open_folder = 0;
571                             break;
572                          }
573                     }
574                }
575
576              if (open_folder)
577                {
578                   tmp = ecore_file_dir_get(file->path);
579                   files = eina_list_append(files, tmp);
580                }
581              else
582                {
583                   files = eina_list_append(files, file->path);
584                }
585
586              e_exec(zone, app->desktop, NULL, files, NULL);
587
588              if (file && file->mime && !open_folder)
589                e_exehist_mime_desktop_add(file->mime, app->desktop);
590
591              if (files)
592                eina_list_free(files);
593
594              E_FREE(tmp);
595           }
596         else if (app->file)
597           {
598              files = eina_list_append(files, app->file);
599              e_exec(zone, app->desktop, NULL, files, NULL);
600              eina_list_free(files);
601           }
602         else
603           {
604              e_exec(zone, app->desktop, NULL, NULL, NULL);
605           }
606      }
607    else if (app->file)
608      {
609         if (file && evry_file_path_get(file))
610           {
611              int len;
612              len = strlen(app->file) + strlen(file->path) + 4;
613              exe = malloc(len);
614              snprintf(exe, len, "%s \'%s\'", app->file, file->path);
615              e_exec(zone, NULL, exe, NULL, NULL);
616              E_FREE(exe);
617           }
618         else
619           {
620              exe = (char *)app->file;
621              e_exec(zone, NULL, exe, NULL, NULL);
622           }
623      }
624
625    return 1;
626 }
627
628 /* taken from curl:
629  *
630  * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et
631  * al.
632  *
633  * Unescapes the given URL escaped string of given length. Returns a
634  * pointer to a malloced string with length given in *olen.
635  * If length == 0, the length is assumed to be strlen(string).
636  * If olen == NULL, no output length is stored.
637  */
638 #define ISXDIGIT(x) (isxdigit((int)((unsigned char)x)))
639
640 char *
641 evry_util_url_unescape(const char *string, int length)
642 {
643    int alloc = (length ? length : (int)strlen(string)) + 1;
644    char *ns = malloc(alloc);
645    unsigned char in;
646    int strindex = 0;
647    unsigned long hex;
648
649    if ( !ns )
650      return NULL;
651
652    while (--alloc > 0)
653      {
654         in = *string;
655         if (('%' == in) && ISXDIGIT(string[1]) && ISXDIGIT(string[2]))
656           {
657              /* this is two hexadecimal digits following a '%' */
658              char hexstr[3];
659              char *ptr;
660              hexstr[0] = string[1];
661              hexstr[1] = string[2];
662              hexstr[2] = 0;
663
664              hex = strtoul(hexstr, &ptr, 16);
665              in = (unsigned char)(hex & (unsigned long)0xFF);
666              // in = ultouc(hex); /* this long is never bigger than 255 anyway */
667
668              string += 2;
669              alloc -= 2;
670           }
671
672         ns[strindex++] = in;
673         string++;
674      }
675    ns[strindex] = 0; /* terminate it */
676
677    return ns;
678 }
679
680 #undef ISXDIGIT
681
682 static Eina_Bool
683 _isalnum(unsigned char in)
684 {
685    switch (in)
686      {
687       case '0':
688       case '1':
689       case '2':
690       case '3':
691       case '4':
692       case '5':
693       case '6':
694       case '7':
695       case '8':
696       case '9':
697       case 'a':
698       case 'b':
699       case 'c':
700       case 'd':
701       case 'e':
702       case 'f':
703       case 'g':
704       case 'h':
705       case 'i':
706       case 'j':
707       case 'k':
708       case 'l':
709       case 'm':
710       case 'n':
711       case 'o':
712       case 'p':
713       case 'q':
714       case 'r':
715       case 's':
716       case 't':
717       case 'u':
718       case 'v':
719       case 'w':
720       case 'x':
721       case 'y':
722       case 'z':
723       case 'A':
724       case 'B':
725       case 'C':
726       case 'D':
727       case 'E':
728       case 'F':
729       case 'G':
730       case 'H':
731       case 'I':
732       case 'J':
733       case 'K':
734       case 'L':
735       case 'M':
736       case 'N':
737       case 'O':
738       case 'P':
739       case 'Q':
740       case 'R':
741       case 'S':
742       case 'T':
743       case 'U':
744       case 'V':
745       case 'W':
746       case 'X':
747       case 'Y':
748       case 'Z':
749         return EINA_TRUE;
750
751       default:
752         break;
753      }
754    return EINA_FALSE;
755 }
756
757 char *
758 evry_util_url_escape(const char *string, int inlength)
759 {
760    size_t alloc = (inlength ? (size_t)inlength : strlen(string)) + 1;
761    char *ns;
762    char *testing_ptr = NULL;
763    unsigned char in; /* we need to treat the characters unsigned */
764    size_t newlen = alloc;
765    int strindex = 0;
766    size_t length;
767
768    ns = malloc(alloc);
769    if (!ns)
770      return NULL;
771
772    length = alloc - 1;
773    while (length--)
774      {
775         in = *string;
776
777         if (_isalnum(in))
778           {
779              /* just copy this */
780              ns[strindex++] = in;
781           }
782         else {
783              /* encode it */
784              newlen += 2; /* the size grows with two, since this'll become a %XX */
785              if (newlen > alloc)
786                {
787                   alloc *= 2;
788                   testing_ptr = realloc(ns, alloc);
789                   if (!testing_ptr)
790                     {
791                        free(ns);
792                        return NULL;
793                     }
794                   else
795                     {
796                        ns = testing_ptr;
797                     }
798                }
799
800              snprintf(&ns[strindex], 4, "%%%02X", in);
801
802              strindex += 3;
803           }
804         string++;
805      }
806    ns[strindex] = 0; /* terminate it */
807    return ns;
808 }
809
810 const char *
811 evry_file_path_get(Evry_Item_File *file)
812 {
813    const char *tmp;
814    char *path;
815
816    if (file->path)
817      return file->path;
818
819    if (!file->url)
820      return NULL;
821
822    if (!strncmp(file->url, "file://", 7))
823      tmp = file->url + 7;
824    else return NULL;
825
826    if (!(path = evry_util_url_unescape(tmp, 0)))
827      return NULL;
828
829    file->path = eina_stringshare_add(path);
830
831    E_FREE(path);
832
833    return file->path;
834 }
835
836 const char *
837 evry_file_url_get(Evry_Item_File *file)
838 {
839    char dest[PATH_MAX * 3 + 7];
840    const char *p;
841    int i;
842
843    if (file->url)
844      return file->url;
845
846    if (!file->path)
847      return NULL;
848
849    memset(dest, 0, PATH_MAX * 3 + 7);
850
851    snprintf(dest, 8, "file://");
852
853    /* Most app doesn't handle the hostname in the uri so it's put to NULL */
854    for (i = 7, p = file->path; *p != '\0'; p++, i++)
855      {
856         if (isalnum(*p) || strchr("/$-_.+!*'()", *p))
857           dest[i] = *p;
858         else
859           {
860              snprintf(&(dest[i]), 4, "%%%02X", (unsigned char)*p);
861              i += 2;
862           }
863      }
864
865    file->url = eina_stringshare_add(dest);
866
867    return file->url;
868 }
869
870 static void
871 _cb_free_item_changed(void *data __UNUSED__, void *event)
872 {
873    Evry_Event_Item_Changed *ev = event;
874
875    evry_item_free(ev->item);
876    E_FREE(ev);
877 }
878
879 void
880 evry_item_changed(Evry_Item *it, int icon, int selected)
881 {
882    Evry_Event_Item_Changed *ev;
883    ev = E_NEW(Evry_Event_Item_Changed, 1);
884    ev->item = it;
885    ev->changed_selection = selected;
886    ev->changed_icon = icon;
887    evry_item_ref(it);
888    ecore_event_add(_evry_events[EVRY_EVENT_ITEM_CHANGED], ev, _cb_free_item_changed, NULL);
889 }
890
891 static char thumb_buf[4096];
892 static const char hex[] = "0123456789abcdef";
893
894 char *
895 evry_util_md5_sum(const char *str)
896 {
897    MD5_CTX ctx;
898    unsigned char hash[MD5_HASHBYTES];
899    int n;
900    char md5out[(2 * MD5_HASHBYTES) + 1];
901    MD5Init (&ctx);
902    MD5Update (&ctx, (unsigned char const *)str,
903               (unsigned)strlen (str));
904    MD5Final (hash, &ctx);
905
906    for (n = 0; n < MD5_HASHBYTES; n++)
907      {
908         md5out[2 * n] = hex[hash[n] >> 4];
909         md5out[2 * n + 1] = hex[hash[n] & 0x0f];
910      }
911    md5out[2 * n] = '\0';
912
913    return strdup(md5out);
914 }
915