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