history: refactor data model.
[profile/ivi/lemolo.git] / dialer / history.c
1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4 #include <Elementary.h>
5 #include <Eet.h>
6 #include <Eina.h>
7 #include <time.h>
8 #include <limits.h>
9 #include <string.h>
10
11 #include "ofono.h"
12 #include "log.h"
13 #include "util.h"
14 #include "gui.h"
15
16 #ifndef EET_COMPRESSION_DEFAULT
17 #define EET_COMPRESSION_DEFAULT 1
18 #endif
19
20 #define HISTORY_ENTRY "history"
21
22 typedef struct _Call_Info_List {
23         Eina_List *list;
24         Eina_Bool dirty;
25 } Call_Info_List;
26
27 typedef struct _History {
28         char *path, *bkp;
29         Eet_Data_Descriptor *edd;
30         Eet_Data_Descriptor *edd_list;
31         Call_Info_List *calls;
32         Elm_Genlist_Item_Class *itc;
33         Evas_Object *genlist_all, *genlist_missed;
34 } History;
35
36 typedef struct _Call_Info {
37         long long start_time;
38         long long end_time;
39         const char *line_id;
40         const char *name;
41         Eina_Bool completed;
42         Eina_Bool incoming;
43         const OFono_Call *call; /* not in edd */
44 } Call_Info;
45
46 static OFono_Callback_List_Call_Node *callback_node_call_removed = NULL;
47 static OFono_Callback_List_Call_Node *callback_node_call_changed = NULL;
48
49 static Call_Info *_history_call_info_search(const History *history,
50                                                 const OFono_Call *call)
51 {
52         Call_Info *call_info;
53         Eina_List *l;
54
55         EINA_LIST_FOREACH(history->calls->list, l, call_info)
56                 if (call_info->call == call)
57                         return call_info;
58
59         return NULL;
60 }
61
62 static Eina_Bool _history_call_info_update(Call_Info *call_info)
63 {
64         OFono_Call_State state;
65
66         EINA_SAFETY_ON_NULL_RETURN_VAL(call_info->call, EINA_FALSE);
67         state = ofono_call_state_get(call_info->call);
68
69         if (state == OFONO_CALL_STATE_INCOMING ||
70                 state == OFONO_CALL_STATE_WAITING) {
71                 if (!call_info->incoming) {
72                         call_info->incoming = EINA_TRUE;
73                         return EINA_TRUE;
74                 }
75         } else if (state == OFONO_CALL_STATE_DIALING ||
76                         state == OFONO_CALL_STATE_ALERTING) {
77                 if (!call_info->incoming) {
78                         call_info->incoming = EINA_FALSE;
79                         return EINA_TRUE;
80                 }
81         } else if (state == OFONO_CALL_STATE_ACTIVE ||
82                         state == OFONO_CALL_STATE_HELD) {
83                 if (!call_info->completed) {
84                         call_info->completed = EINA_TRUE;
85                         return EINA_TRUE;
86                 }
87         }
88
89         return EINA_FALSE;
90 }
91
92 static void _history_call_changed(void *data, OFono_Call *call)
93 {
94         History *history = data;
95         const char *line_id = ofono_call_line_id_get(call);
96         Call_Info *call_info;
97         OFono_Call_State state = ofono_call_state_get(call);
98
99         call_info = _history_call_info_search(history, call);
100         DBG("call=%p, id=%s, state=%d, completed=%d, incoming=%d, info=%p",
101                 call, line_id, state,
102                 call_info ? call_info->completed : EINA_FALSE,
103                 call_info ? call_info->incoming : EINA_FALSE,
104                 call_info);
105
106         if (call_info)
107                 goto end;
108
109         call_info = calloc(1, sizeof(Call_Info));
110         EINA_SAFETY_ON_NULL_RETURN(call_info);
111
112         call_info->call = call;
113         call_info->start_time = time(NULL);
114         call_info->line_id = eina_stringshare_add(line_id);
115         call_info->name = eina_stringshare_add(ofono_call_name_get(call));
116         history->calls->list =
117                 eina_list_prepend(history->calls->list, call_info);
118         history->calls->dirty = EINA_TRUE;
119
120 end:
121         if (_history_call_info_update(call_info))
122                 history->calls->dirty = EINA_TRUE;
123 }
124
125 static void _history_call_log_save(History *history)
126 {
127         Eet_File *efile;
128
129         EINA_SAFETY_ON_NULL_RETURN(history->calls);
130         DBG("save history (%u calls, dirty: %d) to %s",
131                 eina_list_count(history->calls->list), history->calls->dirty,
132                 history->path);
133
134         ecore_file_unlink(history->bkp);
135         ecore_file_mv(history->path, history->bkp);
136         efile = eet_open(history->path, EET_FILE_MODE_WRITE);
137         EINA_SAFETY_ON_NULL_RETURN(efile);
138         if (!(eet_data_write(efile,
139                                 history->edd_list, HISTORY_ENTRY,
140                                 history->calls, EET_COMPRESSION_DEFAULT)))
141                 ERR("Could in the history log file");
142
143         eet_close(efile);
144 }
145
146 static void _history_call_removed(void *data, OFono_Call *call)
147 {
148         History *history = data;
149         const char *line_id = ofono_call_line_id_get(call);
150         Call_Info *call_info;
151         time_t start;
152         char *tm;
153
154         call_info = _history_call_info_search(history, call);
155         DBG("call=%p, id=%s, info=%p", call, line_id, call_info);
156         EINA_SAFETY_ON_NULL_RETURN(call_info);
157         start = call_info->start_time;
158         tm = ctime(&start);
159
160         if (call_info->completed)
161                 INF("Call end:  %s at %s", line_id, tm);
162         else {
163                 if (!call_info->incoming)
164                         INF("Not answered: %s at %s", line_id, tm);
165                 else {
166                         INF("Missed: %s at %s", line_id, tm);
167                         elm_genlist_item_prepend(history->genlist_missed,
168                                                         history->itc,
169                                                         call_info, NULL,
170                                                         ELM_GENLIST_ITEM_NONE,
171                                                         NULL, NULL);
172                 }
173         }
174
175         call_info->end_time = time(NULL);
176         call_info->call = NULL;
177         history->calls->dirty = EINA_TRUE;
178         _history_call_log_save(history);
179         elm_genlist_item_prepend(history->genlist_all, history->itc, call_info,
180                                         NULL, ELM_GENLIST_ITEM_NONE,
181                                         NULL, NULL);
182 }
183
184 static void _call_info_free(Call_Info *call_info)
185 {
186         eina_stringshare_del(call_info->line_id);
187         eina_stringshare_del(call_info->name);
188         free(call_info);
189 }
190
191 static void _on_del(void *data, Evas *e __UNUSED__,
192                         Evas_Object *obj __UNUSED__, void *event __UNUSED__)
193 {
194         History *history = data;
195         Call_Info *call_info;
196
197         if (history->calls->dirty)
198                 _history_call_log_save(history);
199
200         ofono_call_removed_cb_del(callback_node_call_removed);
201         ofono_call_changed_cb_del(callback_node_call_changed);
202         eet_data_descriptor_free(history->edd);
203         eet_data_descriptor_free(history->edd_list);
204         EINA_LIST_FREE(history->calls->list, call_info)
205                 _call_info_free(call_info);
206         free(history->calls);
207         elm_genlist_item_class_free(history->itc);
208         free(history->path);
209         free(history->bkp);
210         free(history);
211         ecore_file_shutdown();
212         eet_shutdown();
213 }
214
215 static void _history_call_info_descriptor_init(Eet_Data_Descriptor **edd,
216                                                 Eet_Data_Descriptor **edd_list)
217 {
218         Eet_Data_Descriptor_Class eddc;
219
220         EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddc, Call_Info);
221         *edd = eet_data_descriptor_stream_new(&eddc);
222
223         EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddc, Call_Info_List);
224         *edd_list = eet_data_descriptor_stream_new(&eddc);
225
226         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
227                                         "completed", completed, EET_T_UCHAR);
228         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
229                                         "incoming", incoming, EET_T_UCHAR);
230
231         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
232                                         "start_time", start_time, EET_T_LONG_LONG);
233         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
234                                         "end_time", end_time, EET_T_LONG_LONG);
235         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
236                                         "line_id", line_id, EET_T_STRING);
237         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
238                                         "name", name, EET_T_STRING);
239
240         EET_DATA_DESCRIPTOR_ADD_LIST(*edd_list, Call_Info_List, "list", list,
241                                         *edd);
242 }
243
244 static void _history_call_log_read(History *history)
245 {
246         Call_Info *call_info;
247         Eina_List *l;
248         Eet_File *efile;
249         Call_Info_List *calls = NULL;
250
251         efile = eet_open(history->path, EET_FILE_MODE_READ);
252
253         if (efile) {
254                 calls = eet_data_read(efile, history->edd_list, HISTORY_ENTRY);
255                 eet_close(efile);
256         }
257
258         if (!calls) {
259                 efile = eet_open(history->bkp, EET_FILE_MODE_READ);
260                 if (efile) {
261                         calls = eet_data_read(efile, history->edd_list,
262                                                 HISTORY_ENTRY);
263                         eet_close(efile);
264                 }
265         }
266
267         if (!calls)
268                 calls = calloc(1, sizeof(Call_Info_List));
269
270         history->calls = calls;
271         EINA_SAFETY_ON_NULL_RETURN(history->calls);
272
273         EINA_LIST_FOREACH(history->calls->list, l, call_info) {
274                 elm_genlist_item_append(history->genlist_all, history->itc,
275                                         call_info, NULL, ELM_GENLIST_ITEM_NONE,
276                                         NULL, NULL);
277                 if (!call_info->completed)
278                         elm_genlist_item_append(history->genlist_missed,
279                                                 history->itc, call_info, NULL,
280                                                 ELM_GENLIST_ITEM_NONE,
281                                                 NULL, NULL);
282         }
283 }
284
285 static char *_item_label_get(void *data, Evas_Object *obj __UNUSED__,
286                                 const char *part)
287 {
288         Call_Info *call_info = data;
289
290         if (strncmp(part, "text.call", strlen("text.call")))
291                 return NULL;
292
293         part += strlen("text.call.");
294
295         if (!strcmp(part, "name")) {
296                 if (!call_info->name || call_info->name[0] == '\0')
297                         return phone_format(call_info->line_id);
298                 return strdup(call_info->name);
299         }
300
301         if (!strcmp(part, "time"))
302                 return date_format(call_info->end_time);
303
304         /* TODO: Fetch phone type from contacts information*/
305         if (!strcmp(part, "type"))
306                 return strdup("TODO:TELEPHONE TYPE");
307
308         ERR("Unexpected text part: %s", part);
309         return NULL;
310 }
311
312
313 static Eina_Bool _item_state_get(void *data, Evas_Object *obj __UNUSED__,
314                                         const char  *part)
315 {
316         Call_Info *call_info = data;
317
318         if (!strcmp(part, "missed"))
319                 return !call_info->completed;
320         else if (!strcmp(part, "completed"))
321                 return call_info->completed;
322         else if (!strcmp(part, "outgoing"))
323                 return !call_info->incoming;
324         else if (!strcmp(part, "incoming"))
325                 return call_info->incoming;
326
327         ERR("Unexpected state part: %s", part);
328         return EINA_FALSE;
329 }
330
331 static void _on_clicked(void *data __UNUSED__, Evas_Object *obj __UNUSED__,
332                         const char *emission, const char *source __UNUSED__)
333 {
334         EINA_SAFETY_ON_NULL_RETURN(emission);
335         emission += strlen("clicked,");
336
337         if (!strcmp(emission, "all"))
338                 elm_object_signal_emit(obj, "show,all", "gui");
339         else
340                 elm_object_signal_emit(obj, "show,missed", "gui");
341 }
342
343 static void _on_more_clicked(void *data __UNUSED__, Evas_Object *obj __UNUSED__,
344                                 const char *emission __UNUSED__,
345                                 const char *source __UNUSED__)
346 {
347         DBG("TODO");
348 }
349
350 static Evas_Object *_item_content_get(void *data __UNUSED__, Evas_Object *obj,
351                                         const char *part __UNUSED__)
352 {
353         Evas_Object *btn;
354
355         btn = gui_layout_add(obj, "history/img");
356         EINA_SAFETY_ON_NULL_RETURN_VAL(obj, NULL);
357         elm_object_signal_callback_add(btn, "clicked,more", "gui",
358                                         _on_more_clicked, NULL);
359
360         return btn;
361 }
362
363 Evas_Object *history_add(Evas_Object *parent)
364 {
365         int r;
366         History *history;
367         const char *config_path;
368         char *path, base_dir[PATH_MAX];
369         Elm_Genlist_Item_Class *itc;
370         Evas_Object *obj, *genlist_all, *genlist_missed;
371
372         eet_init();
373         ecore_file_init();
374         history = calloc(1, sizeof(History));
375         EINA_SAFETY_ON_NULL_RETURN_VAL(history, NULL);
376
377         obj = gui_layout_add(parent, "history_bg");
378         EINA_SAFETY_ON_NULL_GOTO(obj, err_layout);
379
380         genlist_all = elm_genlist_add(obj);
381         EINA_SAFETY_ON_NULL_GOTO(genlist_all, err_object_new);
382         elm_object_style_set(genlist_all, "history");
383
384         genlist_missed = elm_genlist_add(obj);
385         EINA_SAFETY_ON_NULL_GOTO(genlist_missed, err_object_new);
386         elm_object_style_set(genlist_missed, "history");
387
388         itc = elm_genlist_item_class_new();
389         EINA_SAFETY_ON_NULL_GOTO(itc, err_object_new);
390         itc->item_style = "history";
391         itc->func.text_get = _item_label_get;
392         itc->func.content_get = _item_content_get;
393         itc->func.state_get = _item_state_get;
394         itc->func.del = NULL;
395         history->genlist_all = genlist_all;
396         history->genlist_missed = genlist_missed;
397         history->itc = itc;
398
399         elm_object_part_content_set(obj, "elm.swallow.all", genlist_all);
400         elm_object_part_content_set(obj, "elm.swallow.missed", genlist_missed);
401         elm_object_signal_emit(obj, "show,all", "gui");
402         elm_object_signal_callback_add(obj, "clicked,*", "gui",
403                                         _on_clicked, NULL);
404
405         config_path = efreet_config_home_get();
406         snprintf(base_dir, sizeof(base_dir), "%s/%s", config_path,
407                         PACKAGE_NAME);
408         ecore_file_mkpath(base_dir);
409         r = asprintf(&path,  "%s/%s/history.eet", config_path, PACKAGE_NAME);
410
411         if (r < 0)
412                 goto err_item_class;
413
414         history->path = path;
415         r = asprintf(&path,  "%s/%s/history.eet.bkp", config_path,
416                         PACKAGE_NAME);
417
418         if (r < 0)
419                 goto err_path;
420
421         history->bkp = path;
422
423         _history_call_info_descriptor_init(&history->edd, &history->edd_list);
424         _history_call_log_read(history);
425         EINA_SAFETY_ON_NULL_GOTO(history->calls, err_log_read);
426         evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL, _on_del,
427                                         history);
428         callback_node_call_changed =
429                 ofono_call_changed_cb_add(_history_call_changed, history);
430         callback_node_call_removed =
431                 ofono_call_removed_cb_add(_history_call_removed, history);
432         return obj;
433
434 err_log_read:
435         free(history->bkp);
436         eet_data_descriptor_free(history->edd);
437         eet_data_descriptor_free(history->edd_list);
438 err_path:
439         free(history->path);
440 err_item_class:
441         elm_genlist_item_class_free(itc);
442 err_object_new:
443         free(obj);
444 err_layout:
445         free(history);
446         ecore_file_shutdown();
447         eet_shutdown();
448         return NULL;
449 }