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