history: try to recover by reusing items stored in log.
[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         Ecore_Poller *updater;
35 } History;
36
37 typedef struct _Call_Info {
38         long long start_time;
39         long long end_time;
40         long long creation_time; /* not in edd */
41         const char *line_id;
42         const char *name;
43         Eina_Bool completed;
44         Eina_Bool incoming;
45         const OFono_Call *call; /* not in edd */
46         Elm_Object_Item *it_all; /*not in edd */
47         Elm_Object_Item *it_missed; /*not in edd */
48 } Call_Info;
49
50 static OFono_Callback_List_Call_Node *callback_node_call_removed = NULL;
51 static OFono_Callback_List_Call_Node *callback_node_call_changed = NULL;
52
53 static Eina_Bool _history_time_updater(void *data)
54 {
55         History *ctx = data;
56         Elm_Object_Item *it;
57
58         it = elm_genlist_first_item_get(ctx->genlist_all);
59         for (; it != NULL; it = elm_genlist_item_next_get(it))
60                 elm_genlist_item_update(it);
61
62         it = elm_genlist_first_item_get(ctx->genlist_missed);
63         for (; it != NULL; it = elm_genlist_item_next_get(it))
64                 elm_genlist_item_update(it);
65
66         return EINA_TRUE;
67 }
68
69 static Call_Info *_history_call_info_search(const History *history,
70                                                 const OFono_Call *call)
71 {
72         Call_Info *call_info;
73         Eina_List *l;
74         long long t = ofono_call_full_start_time_get(call);
75         const char *line_id = ofono_call_line_id_get(call); /* stringshare */
76
77         EINA_LIST_FOREACH(history->calls->list, l, call_info) {
78                 if (call_info->call == call)
79                         return call_info;
80                 else if (!call_info->call) {
81                         if ((t > 0) && (call_info->start_time == t) &&
82                                 (line_id == call_info->line_id)) {
83                                 DBG("associated existing log %p %s (%lld) with "
84                                         "call %p %s (%lld)",
85                                         call_info,
86                                         call_info->line_id,
87                                         call_info->start_time,
88                                         call, line_id, t);
89                                 call_info->call = call;
90                                 return call_info;
91                         }
92                 }
93         }
94
95         return NULL;
96 }
97
98 static Eina_Bool _history_call_info_update(Call_Info *call_info)
99 {
100         OFono_Call_State state;
101
102         EINA_SAFETY_ON_NULL_RETURN_VAL(call_info->call, EINA_FALSE);
103         state = ofono_call_state_get(call_info->call);
104
105         if (state == OFONO_CALL_STATE_INCOMING ||
106                 state == OFONO_CALL_STATE_WAITING) {
107                 if (!call_info->incoming) {
108                         call_info->incoming = EINA_TRUE;
109                         return EINA_TRUE;
110                 }
111         } else if (state == OFONO_CALL_STATE_DIALING ||
112                         state == OFONO_CALL_STATE_ALERTING) {
113                 if (!call_info->incoming) {
114                         call_info->incoming = EINA_FALSE;
115                         return EINA_TRUE;
116                 }
117         } else if (state == OFONO_CALL_STATE_ACTIVE ||
118                         state == OFONO_CALL_STATE_HELD) {
119                 if (!call_info->completed) {
120                         call_info->start_time = ofono_call_full_start_time_get
121                                 (call_info->call);
122                         if (call_info->start_time == 0)
123                                 call_info->start_time = call_info->creation_time;
124
125                         call_info->completed = EINA_TRUE;
126                         return EINA_TRUE;
127                 }
128         }
129
130         return EINA_FALSE;
131 }
132
133 static void _on_item_clicked(void *data, Evas_Object *obj __UNUSED__,
134                                 void *event_info)
135 {
136         Elm_Object_Item *it = event_info;
137         const char *number = data;
138         gui_number_set(number, EINA_TRUE);
139         elm_genlist_item_selected_set(it, EINA_FALSE);
140 }
141
142 static void _history_call_changed(void *data, OFono_Call *call)
143 {
144         History *history = data;
145         const char *line_id = ofono_call_line_id_get(call);
146         Call_Info *call_info;
147         OFono_Call_State state = ofono_call_state_get(call);
148
149         call_info = _history_call_info_search(history, call);
150         DBG("call=%p, id=%s, state=%d, completed=%d, incoming=%d, info=%p",
151                 call, line_id, state,
152                 call_info ? call_info->completed : EINA_FALSE,
153                 call_info ? call_info->incoming : EINA_FALSE,
154                 call_info);
155
156         if (call_info)
157                 goto end;
158
159         call_info = calloc(1, sizeof(Call_Info));
160         EINA_SAFETY_ON_NULL_RETURN(call_info);
161
162         call_info->call = call;
163         call_info->start_time = ofono_call_full_start_time_get(call);
164         call_info->creation_time = time(NULL);
165         call_info->line_id = eina_stringshare_add(line_id);
166         call_info->name = eina_stringshare_add(ofono_call_name_get(call));
167         history->calls->list =
168                 eina_list_prepend(history->calls->list, call_info);
169         history->calls->dirty = EINA_TRUE;
170
171 end:
172         if (_history_call_info_update(call_info))
173                 history->calls->dirty = EINA_TRUE;
174 }
175
176 static void _history_call_log_save(History *history)
177 {
178         Eet_File *efile;
179
180         EINA_SAFETY_ON_NULL_RETURN(history->calls);
181         DBG("save history (%u calls, dirty: %d) to %s",
182                 eina_list_count(history->calls->list), history->calls->dirty,
183                 history->path);
184
185         ecore_file_unlink(history->bkp);
186         ecore_file_mv(history->path, history->bkp);
187         efile = eet_open(history->path, EET_FILE_MODE_WRITE);
188         EINA_SAFETY_ON_NULL_RETURN(efile);
189         if (!(eet_data_write(efile,
190                                 history->edd_list, HISTORY_ENTRY,
191                                 history->calls, EET_COMPRESSION_DEFAULT)))
192                 ERR("Could in the history log file");
193
194         eet_close(efile);
195 }
196
197 static void _history_call_removed(void *data, OFono_Call *call)
198 {
199         Elm_Object_Item *it;
200         History *history = data;
201         const char *line_id = ofono_call_line_id_get(call);
202         Call_Info *call_info;
203         time_t start;
204         char *tm;
205
206         call_info = _history_call_info_search(history, call);
207         DBG("call=%p, id=%s, info=%p", call, line_id, call_info);
208         EINA_SAFETY_ON_NULL_RETURN(call_info);
209
210         if (call_info->start_time == 0)
211                 call_info->start_time = call_info->creation_time;
212
213         start = call_info->start_time;
214         tm = ctime(&start);
215
216         call_info->end_time = time(NULL);
217         call_info->call = NULL;
218
219         if (call_info->completed)
220                 INF("Call end:  %s at %s", line_id, tm);
221         else {
222                 if (!call_info->incoming)
223                         INF("Not answered: %s at %s", line_id, tm);
224                 else {
225                         INF("Missed: %s at %s", line_id, tm);
226                         if (call_info->it_missed)
227                                 elm_genlist_item_update(call_info->it_missed);
228                         else {
229                                 it = elm_genlist_item_prepend
230                                         (history->genlist_missed,
231                                                 history->itc,
232                                                 call_info, NULL,
233                                                 ELM_GENLIST_ITEM_NONE,
234                                                 _on_item_clicked,
235                                                 call_info->line_id);
236                                 elm_genlist_item_show
237                                         (it, ELM_GENLIST_ITEM_SCROLLTO_IN);
238                                 call_info->it_missed = it;
239                         }
240                 }
241         }
242
243         history->calls->dirty = EINA_TRUE;
244         _history_call_log_save(history);
245
246         if (call_info->it_all)
247                 elm_genlist_item_update(call_info->it_all);
248         else {
249                 it = elm_genlist_item_prepend(history->genlist_all,
250                                                 history->itc,
251                                                 call_info, NULL,
252                                                 ELM_GENLIST_ITEM_NONE,
253                                                 _on_item_clicked,
254                                                 call_info->line_id);
255                 elm_genlist_item_show(it, ELM_GENLIST_ITEM_SCROLLTO_IN);
256                 call_info->it_all = it;
257         }
258 }
259
260 static void _call_info_free(Call_Info *call_info)
261 {
262         eina_stringshare_del(call_info->line_id);
263         eina_stringshare_del(call_info->name);
264         free(call_info);
265 }
266
267 static void _on_del(void *data, Evas *e __UNUSED__,
268                         Evas_Object *obj __UNUSED__, void *event __UNUSED__)
269 {
270         History *history = data;
271         Call_Info *call_info;
272
273         ecore_poller_del(history->updater);
274
275         if (history->calls->dirty)
276                 _history_call_log_save(history);
277
278         ofono_call_removed_cb_del(callback_node_call_removed);
279         ofono_call_changed_cb_del(callback_node_call_changed);
280         eet_data_descriptor_free(history->edd);
281         eet_data_descriptor_free(history->edd_list);
282         EINA_LIST_FREE(history->calls->list, call_info)
283                 _call_info_free(call_info);
284         free(history->calls);
285         elm_genlist_item_class_free(history->itc);
286         free(history->path);
287         free(history->bkp);
288         free(history);
289         ecore_file_shutdown();
290         eet_shutdown();
291 }
292
293 static void _history_call_info_descriptor_init(Eet_Data_Descriptor **edd,
294                                                 Eet_Data_Descriptor **edd_list)
295 {
296         Eet_Data_Descriptor_Class eddc;
297
298         EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddc, Call_Info);
299         *edd = eet_data_descriptor_stream_new(&eddc);
300
301         EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddc, Call_Info_List);
302         *edd_list = eet_data_descriptor_stream_new(&eddc);
303
304         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
305                                         "completed", completed, EET_T_UCHAR);
306         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
307                                         "incoming", incoming, EET_T_UCHAR);
308
309         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
310                                         "start_time", start_time, EET_T_LONG_LONG);
311         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
312                                         "end_time", end_time, EET_T_LONG_LONG);
313         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
314                                         "line_id", line_id, EET_T_STRING);
315         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
316                                         "name", name, EET_T_STRING);
317
318         EET_DATA_DESCRIPTOR_ADD_LIST(*edd_list, Call_Info_List, "list", list,
319                                         *edd);
320 }
321
322 static void _history_call_log_read(History *history)
323 {
324         Call_Info *call_info;
325         Eina_List *l;
326         Eet_File *efile;
327         Call_Info_List *calls = NULL;
328         Elm_Object_Item *it;
329
330         efile = eet_open(history->path, EET_FILE_MODE_READ);
331
332         if (efile) {
333                 calls = eet_data_read(efile, history->edd_list, HISTORY_ENTRY);
334                 eet_close(efile);
335         }
336
337         if (!calls) {
338                 efile = eet_open(history->bkp, EET_FILE_MODE_READ);
339                 if (efile) {
340                         calls = eet_data_read(efile, history->edd_list,
341                                                 HISTORY_ENTRY);
342                         eet_close(efile);
343                 }
344         }
345
346         if (!calls)
347                 calls = calloc(1, sizeof(Call_Info_List));
348
349         history->calls = calls;
350         EINA_SAFETY_ON_NULL_RETURN(history->calls);
351
352         EINA_LIST_FOREACH(history->calls->list, l, call_info) {
353                 it = elm_genlist_item_append(history->genlist_all,
354                                                 history->itc,
355                                                 call_info, NULL,
356                                                 ELM_GENLIST_ITEM_NONE,
357                                                 _on_item_clicked,
358                                                 call_info->line_id);
359                 call_info->it_all = it;
360
361                 if (call_info->completed)
362                         continue;
363
364                 it = elm_genlist_item_append(history->genlist_missed,
365                                                 history->itc, call_info, NULL,
366                                                 ELM_GENLIST_ITEM_NONE,
367                                                 _on_item_clicked,
368                                                 call_info->line_id);
369                 call_info->it_missed = it;
370         }
371
372         it = elm_genlist_first_item_get(history->genlist_all);
373         if (it)
374                 elm_genlist_item_show(it, ELM_GENLIST_ITEM_SCROLLTO_TOP);
375
376         it = elm_genlist_first_item_get(history->genlist_missed);
377         if (it)
378                 elm_genlist_item_show(it, ELM_GENLIST_ITEM_SCROLLTO_TOP);
379 }
380
381 static char *_item_label_get(void *data, Evas_Object *obj __UNUSED__,
382                                 const char *part)
383 {
384         Call_Info *call_info = data;
385
386         if (strncmp(part, "text.call", strlen("text.call")))
387                 return NULL;
388
389         part += strlen("text.call.");
390
391         if (!strcmp(part, "name")) {
392                 if (!call_info->name || call_info->name[0] == '\0')
393                         return phone_format(call_info->line_id);
394                 return strdup(call_info->name);
395         }
396
397         if (!strcmp(part, "time")) {
398                 if ((call_info->completed) && (call_info->end_time))
399                         return date_format(call_info->end_time);
400                 return date_format(call_info->start_time);
401         }
402
403         /* TODO: Fetch phone type from contacts information*/
404         if (!strcmp(part, "type"))
405                 return strdup("TODO:TELEPHONE TYPE");
406
407         ERR("Unexpected text part: %s", part);
408         return NULL;
409 }
410
411
412 static Eina_Bool _item_state_get(void *data, Evas_Object *obj __UNUSED__,
413                                         const char  *part)
414 {
415         Call_Info *call_info = data;
416
417         if (!strcmp(part, "missed"))
418                 return !call_info->completed;
419         else if (!strcmp(part, "completed"))
420                 return call_info->completed;
421         else if (!strcmp(part, "outgoing"))
422                 return !call_info->incoming;
423         else if (!strcmp(part, "incoming"))
424                 return call_info->incoming;
425
426         ERR("Unexpected state part: %s", part);
427         return EINA_FALSE;
428 }
429
430 static void _on_clicked(void *data __UNUSED__, Evas_Object *obj __UNUSED__,
431                         const char *emission, const char *source __UNUSED__)
432 {
433         EINA_SAFETY_ON_NULL_RETURN(emission);
434         emission += strlen("clicked,");
435
436         if (!strcmp(emission, "all"))
437                 elm_object_signal_emit(obj, "show,all", "gui");
438         else
439                 elm_object_signal_emit(obj, "show,missed", "gui");
440 }
441
442 static void _on_more_clicked(void *data __UNUSED__, Evas_Object *obj __UNUSED__,
443                                 const char *emission __UNUSED__,
444                                 const char *source __UNUSED__)
445 {
446         DBG("TODO");
447 }
448
449 static Evas_Object *_item_content_get(void *data __UNUSED__, Evas_Object *obj,
450                                         const char *part __UNUSED__)
451 {
452         Evas_Object *btn;
453
454         btn = gui_layout_add(obj, "history/img");
455         EINA_SAFETY_ON_NULL_RETURN_VAL(obj, NULL);
456         elm_object_signal_callback_add(btn, "clicked,more", "gui",
457                                         _on_more_clicked, NULL);
458         evas_object_propagate_events_set(btn, EINA_FALSE);
459
460         return btn;
461 }
462
463 Evas_Object *history_add(Evas_Object *parent)
464 {
465         int r;
466         History *history;
467         const char *config_path;
468         char *path, base_dir[PATH_MAX];
469         Elm_Genlist_Item_Class *itc;
470         Evas_Object *obj, *genlist_all, *genlist_missed;
471
472         eet_init();
473         ecore_file_init();
474         history = calloc(1, sizeof(History));
475         EINA_SAFETY_ON_NULL_RETURN_VAL(history, NULL);
476
477         obj = gui_layout_add(parent, "history_bg");
478         EINA_SAFETY_ON_NULL_GOTO(obj, err_layout);
479
480         genlist_all = elm_genlist_add(obj);
481         EINA_SAFETY_ON_NULL_GOTO(genlist_all, err_object_new);
482         elm_object_style_set(genlist_all, "history");
483
484         genlist_missed = elm_genlist_add(obj);
485         EINA_SAFETY_ON_NULL_GOTO(genlist_missed, err_object_new);
486         elm_object_style_set(genlist_missed, "history");
487
488         itc = elm_genlist_item_class_new();
489         EINA_SAFETY_ON_NULL_GOTO(itc, err_object_new);
490         itc->item_style = "history";
491         itc->func.text_get = _item_label_get;
492         itc->func.content_get = _item_content_get;
493         itc->func.state_get = _item_state_get;
494         itc->func.del = NULL;
495         history->genlist_all = genlist_all;
496         history->genlist_missed = genlist_missed;
497         history->itc = itc;
498
499         elm_object_part_content_set(obj, "elm.swallow.all", genlist_all);
500         elm_object_part_content_set(obj, "elm.swallow.missed", genlist_missed);
501         elm_object_signal_emit(obj, "show,all", "gui");
502         elm_object_signal_callback_add(obj, "clicked,*", "gui",
503                                         _on_clicked, NULL);
504
505         config_path = efreet_config_home_get();
506         snprintf(base_dir, sizeof(base_dir), "%s/%s", config_path,
507                         PACKAGE_NAME);
508         ecore_file_mkpath(base_dir);
509         r = asprintf(&path,  "%s/%s/history.eet", config_path, PACKAGE_NAME);
510
511         if (r < 0)
512                 goto err_item_class;
513
514         history->path = path;
515         r = asprintf(&path,  "%s/%s/history.eet.bkp", config_path,
516                         PACKAGE_NAME);
517
518         if (r < 0)
519                 goto err_path;
520
521         history->bkp = path;
522
523         _history_call_info_descriptor_init(&history->edd, &history->edd_list);
524         _history_call_log_read(history);
525         EINA_SAFETY_ON_NULL_GOTO(history->calls, err_log_read);
526         evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL, _on_del,
527                                         history);
528         callback_node_call_changed =
529                 ofono_call_changed_cb_add(_history_call_changed, history);
530         callback_node_call_removed =
531                 ofono_call_removed_cb_add(_history_call_removed, history);
532
533         /* ECORE_POLLER_CORE is 1/8th of second. */
534         history->updater = ecore_poller_add(ECORE_POLLER_CORE, 8 * 60,
535                                                 _history_time_updater,
536                                                 history);
537         return obj;
538
539 err_log_read:
540         free(history->bkp);
541         eet_data_descriptor_free(history->edd);
542         eet_data_descriptor_free(history->edd_list);
543 err_path:
544         free(history->path);
545 err_item_class:
546         elm_genlist_item_class_free(itc);
547 err_object_new:
548         free(obj);
549 err_layout:
550         free(history);
551         ecore_file_shutdown();
552         eet_shutdown();
553         return NULL;
554 }