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