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