#include "e_mod_main.h" #define HISTORY_VERSION 2 #define SEVEN_DAYS 604800.0 /* old history entries will be removed when threshold is reached */ #define CLEANUP_THRESHOLD 500 #define TIME_FACTOR(_now) (1.0 - (evry_hist->begin / _now)) / 1000000000000000.0 typedef struct _Cleanup_Data Cleanup_Data; struct _Cleanup_Data { double time; Eina_List *keys; Eina_Bool normalize; const char *plugin; }; static E_Config_DD *hist_entry_edd = NULL; static E_Config_DD *hist_item_edd = NULL; static E_Config_DD *hist_types_edd = NULL; static E_Config_DD *hist_edd = NULL; Evry_History *evry_hist = NULL; void evry_history_init(void) { #undef T #undef D hist_item_edd = E_CONFIG_DD_NEW("History_Item", History_Item); #define T History_Item #define D hist_item_edd E_CONFIG_VAL(D, T, plugin, STR); E_CONFIG_VAL(D, T, context, STR); E_CONFIG_VAL(D, T, input, STR); E_CONFIG_VAL(D, T, last_used, DOUBLE); E_CONFIG_VAL(D, T, usage, DOUBLE); E_CONFIG_VAL(D, T, count, INT); E_CONFIG_VAL(D, T, transient, INT); E_CONFIG_VAL(D, T, data, STR); #undef T #undef D hist_entry_edd = E_CONFIG_DD_NEW("History_Entry", History_Entry); #define T History_Entry #define D hist_entry_edd E_CONFIG_LIST(D, T, items, hist_item_edd); #undef T #undef D hist_types_edd = E_CONFIG_DD_NEW("History_Types", History_Types); #define T History_Types #define D hist_types_edd E_CONFIG_HASH(D, T, types, hist_entry_edd); #undef T #undef D hist_edd = E_CONFIG_DD_NEW("History", Evry_History); #define T Evry_History #define D hist_edd E_CONFIG_VAL(D, T, version, INT); E_CONFIG_VAL(D, T, begin, DOUBLE); E_CONFIG_HASH(D, T, subjects, hist_types_edd); #undef T #undef D } static Eina_Bool _hist_entry_free_cb(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata EINA_UNUSED) { History_Entry *he = data; History_Item *hi; EINA_LIST_FREE (he->items, hi) { if (hi->input) eina_stringshare_del(hi->input); if (hi->plugin) eina_stringshare_del(hi->plugin); if (hi->context) eina_stringshare_del(hi->context); if (hi->data) eina_stringshare_del(hi->data); E_FREE(hi); } E_FREE(he); return EINA_TRUE; } static Eina_Bool _hist_free_cb(const Eina_Hash *hash EINA_UNUSED, const void *key EINA_UNUSED, void *data, void *fdata EINA_UNUSED) { History_Types *ht = data; if (ht->types) { eina_hash_foreach(ht->types, _hist_entry_free_cb, NULL); eina_hash_free(ht->types); } E_FREE(ht); return EINA_TRUE; } static Eina_Bool _hist_entry_cleanup_cb(const Eina_Hash *hash EINA_UNUSED, const void *key, void *data, void *fdata) { History_Entry *he = data; Cleanup_Data *d = fdata; History_Item *hi; Eina_List *l, *ll; EINA_LIST_FOREACH_SAFE (he->items, l, ll, hi) { if (hi->last_used < d->time - SEVEN_DAYS) { hi->count--; hi->last_used = d->time - SEVEN_DAYS / 2.0; } /* item is transient or too old */ if ((hi->count < 1) || hi->transient) { if (hi->input) eina_stringshare_del(hi->input); if (hi->plugin) eina_stringshare_del(hi->plugin); if (hi->context) eina_stringshare_del(hi->context); if (hi->data) eina_stringshare_del(hi->data); E_FREE(hi); he->items = eina_list_remove_list(he->items, l); } } if (!he->items) { E_FREE(he); d->keys = eina_list_append(d->keys, key); } return EINA_TRUE; } static Eina_Bool _hist_cleanup_cb(const Eina_Hash *hash EINA_UNUSED, const void *key, void *data, void *fdata) { History_Types *ht = data; Cleanup_Data *d = fdata; if (ht->types) { eina_hash_foreach(ht->types, _hist_entry_cleanup_cb, fdata); EINA_LIST_FREE (d->keys, key) eina_hash_del_by_key(ht->types, key); } return EINA_TRUE; } void evry_history_free(void) { evry_hist = e_config_domain_load("module.everything.cache", hist_edd); if ((evry_hist) && (evry_hist->subjects) && (eina_hash_population(evry_hist->subjects) > CLEANUP_THRESHOLD)) { Cleanup_Data *d; d = E_NEW(Cleanup_Data, 1); d->time = ecore_time_unix_get(); eina_hash_foreach(evry_hist->subjects, _hist_cleanup_cb, d); E_FREE(d); } evry_history_unload(); E_CONFIG_DD_FREE(hist_item_edd); E_CONFIG_DD_FREE(hist_entry_edd); E_CONFIG_DD_FREE(hist_types_edd); E_CONFIG_DD_FREE(hist_edd); } void evry_history_load(void) { if (evry_hist) return; evry_hist = e_config_domain_load("module.everything.cache", hist_edd); if (evry_hist && evry_hist->version != HISTORY_VERSION) { eina_hash_foreach(evry_hist->subjects, _hist_free_cb, NULL); eina_hash_free(evry_hist->subjects); E_FREE(evry_hist); evry_hist = NULL; } if (!evry_hist) { evry_hist = E_NEW(Evry_History, 1); evry_hist->version = HISTORY_VERSION; evry_hist->begin = ecore_time_unix_get() - SEVEN_DAYS; } if (!evry_hist->subjects) evry_hist->subjects = eina_hash_string_superfast_new(NULL); } void evry_history_unload(void) { if (!evry_hist) return; e_config_domain_save("module.everything.cache", hist_edd, evry_hist); eina_hash_foreach(evry_hist->subjects, _hist_free_cb, NULL); eina_hash_free(evry_hist->subjects); E_FREE(evry_hist); evry_hist = NULL; } History_Types * evry_history_types_get(Evry_Type _type) { History_Types *ht; const char *type = evry_type_get(_type); if (!evry_hist) return NULL; if (!type) return NULL; ht = eina_hash_find(evry_hist->subjects, type); if (!ht) { ht = E_NEW(History_Types, 1); eina_hash_add(evry_hist->subjects, type, ht); } if (!ht->types) ht->types = eina_hash_string_superfast_new(NULL); return ht; } History_Item * evry_history_item_add(Evry_Item *it, const char *ctxt, const char *input) { History_Entry *he; History_Types *ht; History_Item *hi = NULL; Eina_List *l; int rem_ctxt = 1; const char *id; if (!evry_hist) return NULL; if (!it) return NULL; if ((!it->plugin->history) && !(CHECK_TYPE(it, EVRY_TYPE_PLUGIN))) return NULL; if (it->type == EVRY_TYPE_ACTION) { GET_ACTION(act, it); if (!act->remember_context) rem_ctxt = 0; } if (it->hi) { /* keep hi when context didn't change */ if ((!rem_ctxt) || (!it->hi->context && !ctxt) || (it->hi->context && ctxt && !strcmp(it->hi->context, ctxt))) hi = it->hi; } if (!hi) { id = (it->id ? it->id : it->label); ht = evry_history_types_get(it->type); if (!ht) return NULL; he = eina_hash_find(ht->types, id); if (!he) { he = E_NEW(History_Entry, 1); eina_hash_add(ht->types, id, he); } else { EINA_LIST_FOREACH (he->items, l, hi) if ((hi->plugin == it->plugin->name) && (!rem_ctxt || (ctxt == hi->context))) break; } } if (!hi) { hi = E_NEW(History_Item, 1); hi->plugin = eina_stringshare_ref(it->plugin->name); he->items = eina_list_append(he->items, hi); } if (hi) { it->hi = hi; hi->last_used = ecore_time_unix_get(); hi->usage /= 4.0; hi->usage += TIME_FACTOR(hi->last_used); hi->transient = it->plugin->transient; hi->count += 1; if (ctxt && !hi->context && rem_ctxt) { hi->context = eina_stringshare_ref(ctxt); } if (input && hi->input) { if (strncmp(hi->input, input, strlen(input))) { eina_stringshare_del(hi->input); hi->input = eina_stringshare_add(input); } } else if (input) { hi->input = eina_stringshare_add(input); } } /* reset usage */ it->usage = 0.0; return hi; } int evry_history_item_usage_set(Evry_Item *it, const char *input, const char *ctxt) { History_Entry *he; History_Types *ht; History_Item *hi = NULL; Eina_List *l; int rem_ctxt = 1; if (evry_conf->history_sort_mode == 3) { it->usage = -1; return 1; } else it->usage = 0.0; if ((!it->plugin->history) && !(CHECK_TYPE(it, EVRY_TYPE_PLUGIN))) return 0; if (it->hi) { /* keep hi when context didn't change */ if ((!rem_ctxt) || (!it->hi->context && !ctxt) || (it->hi->context && ctxt && !strcmp(it->hi->context, ctxt))) hi = it->hi; } if (!hi) { ht = evry_history_types_get(it->type); if (!ht) return 0; if (!(he = eina_hash_find(ht->types, (it->id ? it->id : it->label)))) return 0; if (it->type == EVRY_TYPE_ACTION) { GET_ACTION(act, it); if (!act->remember_context) rem_ctxt = 0; } EINA_LIST_FOREACH (he->items, l, hi) { if (hi->plugin != it->plugin->name) continue; if (rem_ctxt && ctxt && (hi->context != ctxt)) { it->hi = hi; continue; } it->hi = hi; break; } } if (!hi) return 0; if (evry_conf->history_sort_mode == 0) { if (!input || !hi->input) { it->usage += hi->usage * hi->count; } else { /* higher priority for exact matches */ if (!strncmp(input, hi->input, strlen(input))) { it->usage += hi->usage * hi->count; } if (!strncmp(input, hi->input, strlen(hi->input))) { it->usage += hi->usage * hi->count; } } if (ctxt && hi->context && (hi->context == ctxt)) { it->usage += hi->usage * hi->count * 10.0; } } else if (evry_conf->history_sort_mode == 1) { it->usage = hi->count * (hi->last_used / 10000000000.0); if (ctxt && hi->context && (hi->context == ctxt)) { it->usage += hi->usage * hi->count * 10.0; } } else if (evry_conf->history_sort_mode == 2) { if (hi->last_used > it->usage) it->usage = hi->last_used; } if (it->fuzzy_match > 0) it->usage /= (double)it->fuzzy_match; else it->usage /= 100.0; if (it->usage > 0.0) return 1; it->usage = -1.0; return 0; }