1 #include "e_mod_main.h"
2 
3 #define HISTORY_VERSION   2
4 
5 #define SEVEN_DAYS        604800.0
6 
7 /* old history entries will be removed when threshold is reached */
8 #define CLEANUP_THRESHOLD 500
9 
10 #define TIME_FACTOR(_now) (1.0 - (evry_hist->begin / _now)) / 1000000000000000.0
11 
12 typedef struct _Cleanup_Data Cleanup_Data;
13 
14 struct _Cleanup_Data
15 {
16    double      time;
17    Eina_List  *keys;
18    Eina_Bool   normalize;
19    const char *plugin;
20 };
21 
22 static E_Config_DD *hist_entry_edd = NULL;
23 static E_Config_DD *hist_item_edd = NULL;
24 static E_Config_DD *hist_types_edd = NULL;
25 static E_Config_DD *hist_edd = NULL;
26 
27 Evry_History *evry_hist = NULL;
28 
29 void
evry_history_init(void)30 evry_history_init(void)
31 {
32 #undef T
33 #undef D
34    hist_item_edd = E_CONFIG_DD_NEW("History_Item", History_Item);
35 #define T History_Item
36 #define D hist_item_edd
37    E_CONFIG_VAL(D, T, plugin, STR);
38    E_CONFIG_VAL(D, T, context, STR);
39    E_CONFIG_VAL(D, T, input, STR);
40    E_CONFIG_VAL(D, T, last_used, DOUBLE);
41    E_CONFIG_VAL(D, T, usage, DOUBLE);
42    E_CONFIG_VAL(D, T, count, INT);
43    E_CONFIG_VAL(D, T, transient, INT);
44    E_CONFIG_VAL(D, T, data, STR);
45 #undef T
46 #undef D
47    hist_entry_edd = E_CONFIG_DD_NEW("History_Entry", History_Entry);
48 #define T History_Entry
49 #define D hist_entry_edd
50    E_CONFIG_LIST(D, T, items, hist_item_edd);
51 #undef T
52 #undef D
53    hist_types_edd = E_CONFIG_DD_NEW("History_Types", History_Types);
54 #define T History_Types
55 #define D hist_types_edd
56    E_CONFIG_HASH(D, T, types, hist_entry_edd);
57 #undef T
58 #undef D
59    hist_edd = E_CONFIG_DD_NEW("History", Evry_History);
60 #define T Evry_History
61 #define D hist_edd
62    E_CONFIG_VAL(D, T, version, INT);
63    E_CONFIG_VAL(D, T, begin, DOUBLE);
64    E_CONFIG_HASH(D, T, subjects, hist_types_edd);
65 #undef T
66 #undef D
67 }
68 
69 static Eina_Bool
_hist_entry_free_cb(const Eina_Hash * hash EINA_UNUSED,const void * key EINA_UNUSED,void * data,void * fdata EINA_UNUSED)70 _hist_entry_free_cb(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata EINA_UNUSED)
71 {
72    History_Entry *he = data;
73    History_Item *hi;
74 
75    EINA_LIST_FREE (he->items, hi)
76      {
77         if (hi->input)
78           eina_stringshare_del(hi->input);
79         if (hi->plugin)
80           eina_stringshare_del(hi->plugin);
81         if (hi->context)
82           eina_stringshare_del(hi->context);
83         if (hi->data)
84           eina_stringshare_del(hi->data);
85         E_FREE(hi);
86      }
87 
88    E_FREE(he);
89 
90    return EINA_TRUE;
91 }
92 
93 static Eina_Bool
_hist_free_cb(const Eina_Hash * hash EINA_UNUSED,const void * key EINA_UNUSED,void * data,void * fdata EINA_UNUSED)94 _hist_free_cb(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata EINA_UNUSED)
95 {
96    History_Types *ht = data;
97 
98    if (ht->types)
99      {
100         eina_hash_foreach(ht->types, _hist_entry_free_cb, NULL);
101         eina_hash_free(ht->types);
102      }
103 
104    E_FREE(ht);
105 
106    return EINA_TRUE;
107 }
108 
109 static Eina_Bool
_hist_entry_cleanup_cb(const Eina_Hash * hash EINA_UNUSED,const void * key,void * data,void * fdata)110 _hist_entry_cleanup_cb(const Eina_Hash *hash EINA_UNUSED, const void *key, void *data, void *fdata)
111 {
112    History_Entry *he = data;
113    Cleanup_Data *d = fdata;
114    History_Item *hi;
115    Eina_List *l, *ll;
116 
117    EINA_LIST_FOREACH_SAFE (he->items, l, ll, hi)
118      {
119         if (hi->last_used < d->time - SEVEN_DAYS)
120           {
121              hi->count--;
122              hi->last_used = d->time - SEVEN_DAYS / 2.0;
123           }
124 
125         /* item is transient or too old */
126         if ((hi->count < 1) || hi->transient)
127           {
128              if (hi->input)
129                eina_stringshare_del(hi->input);
130              if (hi->plugin)
131                eina_stringshare_del(hi->plugin);
132              if (hi->context)
133                eina_stringshare_del(hi->context);
134              if (hi->data)
135                eina_stringshare_del(hi->data);
136              E_FREE(hi);
137 
138              he->items = eina_list_remove_list(he->items, l);
139           }
140      }
141 
142    if (!he->items)
143      {
144         E_FREE(he);
145         d->keys = eina_list_append(d->keys, key);
146      }
147 
148    return EINA_TRUE;
149 }
150 
151 static Eina_Bool
_hist_cleanup_cb(const Eina_Hash * hash EINA_UNUSED,const void * key,void * data,void * fdata)152 _hist_cleanup_cb(const Eina_Hash *hash EINA_UNUSED, const void *key, void *data, void *fdata)
153 {
154    History_Types *ht = data;
155    Cleanup_Data *d = fdata;
156 
157    if (ht->types)
158      {
159         eina_hash_foreach(ht->types, _hist_entry_cleanup_cb, fdata);
160 
161         EINA_LIST_FREE (d->keys, key)
162           eina_hash_del_by_key(ht->types, key);
163      }
164 
165    return EINA_TRUE;
166 }
167 
168 void
evry_history_free(void)169 evry_history_free(void)
170 {
171    evry_hist = e_config_domain_load("module.everything.cache", hist_edd);
172 
173    if ((evry_hist) && (evry_hist->subjects) &&
174        (eina_hash_population(evry_hist->subjects) > CLEANUP_THRESHOLD))
175      {
176         Cleanup_Data *d;
177 
178         d = E_NEW(Cleanup_Data, 1);
179         d->time = ecore_time_unix_get();
180         eina_hash_foreach(evry_hist->subjects, _hist_cleanup_cb, d);
181         E_FREE(d);
182      }
183 
184    evry_history_unload();
185 
186    E_CONFIG_DD_FREE(hist_item_edd);
187    E_CONFIG_DD_FREE(hist_entry_edd);
188    E_CONFIG_DD_FREE(hist_types_edd);
189    E_CONFIG_DD_FREE(hist_edd);
190 }
191 
192 void
evry_history_load(void)193 evry_history_load(void)
194 {
195    if (evry_hist) return;
196 
197    evry_hist = e_config_domain_load("module.everything.cache", hist_edd);
198 
199    if (evry_hist && evry_hist->version != HISTORY_VERSION)
200      {
201         eina_hash_foreach(evry_hist->subjects, _hist_free_cb, NULL);
202         eina_hash_free(evry_hist->subjects);
203 
204         E_FREE(evry_hist);
205         evry_hist = NULL;
206      }
207 
208    if (!evry_hist)
209      {
210         evry_hist = E_NEW(Evry_History, 1);
211         evry_hist->version = HISTORY_VERSION;
212         evry_hist->begin = ecore_time_unix_get() - SEVEN_DAYS;
213      }
214    if (!evry_hist->subjects)
215      evry_hist->subjects = eina_hash_string_superfast_new(NULL);
216 }
217 
218 void
evry_history_unload(void)219 evry_history_unload(void)
220 {
221    if (!evry_hist) return;
222 
223    e_config_domain_save("module.everything.cache", hist_edd, evry_hist);
224 
225    eina_hash_foreach(evry_hist->subjects, _hist_free_cb, NULL);
226    eina_hash_free(evry_hist->subjects);
227 
228    E_FREE(evry_hist);
229    evry_hist = NULL;
230 }
231 
232 History_Types *
evry_history_types_get(Evry_Type _type)233 evry_history_types_get(Evry_Type _type)
234 {
235    History_Types *ht;
236    const char *type = evry_type_get(_type);
237 
238    if (!evry_hist)
239      return NULL;
240 
241    if (!type)
242      return NULL;
243 
244    ht = eina_hash_find(evry_hist->subjects, type);
245 
246    if (!ht)
247      {
248         ht = E_NEW(History_Types, 1);
249         eina_hash_add(evry_hist->subjects, type, ht);
250      }
251 
252    if (!ht->types)
253      ht->types = eina_hash_string_superfast_new(NULL);
254 
255    return ht;
256 }
257 
258 History_Item *
evry_history_item_add(Evry_Item * it,const char * ctxt,const char * input)259 evry_history_item_add(Evry_Item *it, const char *ctxt, const char *input)
260 {
261    History_Entry *he;
262    History_Types *ht;
263    History_Item *hi = NULL;
264    Eina_List *l;
265    int rem_ctxt = 1;
266    const char *id;
267 
268    if (!evry_hist)
269      return NULL;
270 
271    if (!it)
272      return NULL;
273 
274    if ((!it->plugin->history) && !(CHECK_TYPE(it, EVRY_TYPE_PLUGIN)))
275      return NULL;
276 
277    if (it->type == EVRY_TYPE_ACTION)
278      {
279         GET_ACTION(act, it);
280         if (!act->remember_context)
281           rem_ctxt = 0;
282      }
283 
284    if (it->hi)
285      {
286         /* keep hi when context didn't change */
287         if ((!rem_ctxt) || (!it->hi->context && !ctxt) ||
288             (it->hi->context && ctxt && !strcmp(it->hi->context, ctxt)))
289           hi = it->hi;
290      }
291 
292    if (!hi)
293      {
294         id = (it->id ? it->id : it->label);
295         ht = evry_history_types_get(it->type);
296         if (!ht)
297           return NULL;
298 
299         he = eina_hash_find(ht->types, id);
300 
301         if (!he)
302           {
303              he = E_NEW(History_Entry, 1);
304              eina_hash_add(ht->types, id, he);
305           }
306         else
307           {
308              EINA_LIST_FOREACH (he->items, l, hi)
309                if ((hi->plugin == it->plugin->name) &&
310                    (!rem_ctxt || (ctxt == hi->context)))
311                  break;
312           }
313      }
314 
315    if (!hi)
316      {
317         hi = E_NEW(History_Item, 1);
318         hi->plugin = eina_stringshare_ref(it->plugin->name);
319         he->items = eina_list_append(he->items, hi);
320      }
321 
322    if (hi)
323      {
324         it->hi = hi;
325 
326         hi->last_used = ecore_time_unix_get();
327         hi->usage /= 4.0;
328         hi->usage += TIME_FACTOR(hi->last_used);
329         hi->transient = it->plugin->transient;
330         hi->count += 1;
331 
332         if (ctxt && !hi->context && rem_ctxt)
333           {
334              hi->context = eina_stringshare_ref(ctxt);
335           }
336 
337         if (input && hi->input)
338           {
339              if (strncmp(hi->input, input, strlen(input)))
340                {
341                   eina_stringshare_del(hi->input);
342                   hi->input = eina_stringshare_add(input);
343                }
344           }
345         else if (input)
346           {
347              hi->input = eina_stringshare_add(input);
348           }
349      }
350 
351    /* reset usage */
352    it->usage = 0.0;
353 
354    return hi;
355 }
356 
357 int
evry_history_item_usage_set(Evry_Item * it,const char * input,const char * ctxt)358 evry_history_item_usage_set(Evry_Item *it, const char *input, const char *ctxt)
359 {
360    History_Entry *he;
361    History_Types *ht;
362    History_Item *hi = NULL;
363    Eina_List *l;
364    int rem_ctxt = 1;
365 
366    if (evry_conf->history_sort_mode == 3)
367      {
368         it->usage = -1;
369         return 1;
370      }
371    else
372      it->usage = 0.0;
373 
374    if ((!it->plugin->history) && !(CHECK_TYPE(it, EVRY_TYPE_PLUGIN)))
375      return 0;
376 
377    if (it->hi)
378      {
379         /* keep hi when context didn't change */
380         if ((!rem_ctxt) || (!it->hi->context && !ctxt) ||
381             (it->hi->context && ctxt && !strcmp(it->hi->context, ctxt)))
382           hi = it->hi;
383      }
384 
385    if (!hi)
386      {
387         ht = evry_history_types_get(it->type);
388 
389         if (!ht)
390           return 0;
391 
392         if (!(he = eina_hash_find(ht->types, (it->id ? it->id : it->label))))
393           return 0;
394 
395         if (it->type == EVRY_TYPE_ACTION)
396           {
397              GET_ACTION(act, it);
398              if (!act->remember_context)
399                rem_ctxt = 0;
400           }
401 
402         EINA_LIST_FOREACH (he->items, l, hi)
403           {
404              if (hi->plugin != it->plugin->name)
405                continue;
406 
407              if (rem_ctxt && ctxt && (hi->context != ctxt))
408                {
409                   it->hi = hi;
410                   continue;
411                }
412 
413              it->hi = hi;
414              break;
415           }
416      }
417 
418    if (!hi) return 0;
419 
420    if (evry_conf->history_sort_mode == 0)
421      {
422         if (!input || !hi->input)
423           {
424              it->usage += hi->usage * hi->count;
425           }
426         else
427           {
428              /* higher priority for exact matches */
429              if (!strncmp(input, hi->input, strlen(input)))
430                {
431                   it->usage += hi->usage * hi->count;
432                }
433              if (!strncmp(input, hi->input, strlen(hi->input)))
434                {
435                   it->usage += hi->usage * hi->count;
436                }
437           }
438 
439         if (ctxt && hi->context && (hi->context == ctxt))
440           {
441              it->usage += hi->usage * hi->count * 10.0;
442           }
443      }
444    else if (evry_conf->history_sort_mode == 1)
445      {
446         it->usage = hi->count * (hi->last_used / 10000000000.0);
447 
448         if (ctxt && hi->context && (hi->context == ctxt))
449           {
450              it->usage += hi->usage * hi->count * 10.0;
451           }
452      }
453    else if (evry_conf->history_sort_mode == 2)
454      {
455         if (hi->last_used > it->usage)
456           it->usage = hi->last_used;
457      }
458    if (it->fuzzy_match > 0)
459      it->usage /= (double)it->fuzzy_match;
460    else
461      it->usage /= 100.0;
462 
463    if (it->usage > 0.0)
464      return 1;
465 
466    it->usage = -1.0;
467 
468    return 0;
469 }
470 
471