history updater: improve algorithm and document constants.
[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 *self;
34         Evas_Object *genlist_all, *genlist_missed;
35         Ecore_Poller *updater;
36         double last_update;
37 } History;
38
39 typedef struct _Call_Info {
40         long long start_time;
41         long long end_time;
42         long long creation_time; /* not in edd */
43         const char *line_id;
44         const char *name;
45         Eina_Bool completed;
46         Eina_Bool incoming;
47         const OFono_Call *call; /* not in edd */
48         Elm_Object_Item *it_all; /*not in edd */
49         Elm_Object_Item *it_missed; /*not in edd */
50 } Call_Info;
51
52 static OFono_Callback_List_Call_Node *callback_node_call_removed = NULL;
53 static OFono_Callback_List_Call_Node *callback_node_call_changed = NULL;
54
55 static Eina_Bool _history_time_updater(void *data)
56 {
57         History *ctx = data;
58         Elm_Object_Item *it;
59         double now = ecore_loop_time_get();
60         const double interval_threshold = 30.0;
61         const long long update_threshold = time(NULL) - WEEK - DAY;
62
63         /*
64          * NOTE ABOUT CONSTANTS:
65          *
66          * - interval_threshold: to avoid updating too often (window
67          *   lost and gained focus, object hidden or shown), we limit
68          *   updates to this minimum interval. The poller should run
69          *   every 60 seconds.
70          *
71          * - update_threshold: since we format strings over a week as
72          *   fixed string (often day-month-year, not relative to
73          *   today), we can stop flagging list items as updated. We
74          *   give it a day of slack so we can be sure to update every
75          *   item (for held and conferences, you may have items that
76          *   are close in time but slightly out of order as items are
77          *   prepended as the calls are removed from ofono, then
78          *   history is not strictly in 'time' order). We must
79          *   stop iterating after update_threshold so users that never
80          *   deleted history and have thousand items will not
81          *   uselessly update all the thousand items.
82          */
83
84         if (now - ctx->last_update < interval_threshold)
85                 return EINA_TRUE;
86         ctx->last_update = now;
87
88         it = elm_genlist_first_item_get(ctx->genlist_all);
89         for (; it != NULL; it = elm_genlist_item_next_get(it)) {
90                 const Call_Info *call_info = elm_object_item_data_get(it);
91                 long long t = call_info->end_time;
92                 if (EINA_UNLIKELY(t == 0)) {
93                         t = call_info->start_time;
94                         if (EINA_UNLIKELY(t == 0))
95                                 t = call_info->creation_time;
96                 }
97                 if (EINA_UNLIKELY(t < update_threshold))
98                         break;
99                 elm_genlist_item_update(it);
100         }
101
102         it = elm_genlist_first_item_get(ctx->genlist_missed);
103         for (; it != NULL; it = elm_genlist_item_next_get(it)) {
104                 const Call_Info *call_info = elm_object_item_data_get(it);
105                 long long t = call_info->end_time;
106                 if (EINA_UNLIKELY(t == 0)) {
107                         t = call_info->start_time;
108                         if (EINA_UNLIKELY(t == 0))
109                                 t = call_info->creation_time;
110                 }
111                 if (EINA_UNLIKELY(t < update_threshold))
112                         break;
113                 elm_genlist_item_update(it);
114         }
115
116         return EINA_TRUE;
117 }
118
119 static void _history_time_updater_stop(History *history)
120 {
121         Evas *e = evas_object_evas_get(history->self);
122         Eina_Bool win_focused = evas_focus_state_get(e);
123         Eina_Bool obj_visible = evas_object_visible_get(history->self);
124
125         DBG("poller %p, win_focused=%hhu, obj_visible=%hhu",
126                 history->updater, win_focused, obj_visible);
127         if (!history->updater)
128                 return;
129         if (win_focused && obj_visible)
130                 return;
131
132         DBG("delete poller %p", history->updater);
133         ecore_poller_del(history->updater);
134         history->updater = NULL;
135 }
136
137 static void _history_time_updater_start(History *history)
138 {
139         Evas *e = evas_object_evas_get(history->self);
140         Eina_Bool win_focused = evas_focus_state_get(e);
141         Eina_Bool obj_visible = evas_object_visible_get(history->self);
142
143         DBG("poller %p, win_focused=%hhu, obj_visible=%hhu",
144                 history->updater, win_focused, obj_visible);
145         if (history->updater)
146                 return;
147         if ((!win_focused) || (!obj_visible))
148                 return;
149
150         DBG("start poller");
151         /* ECORE_POLLER_CORE is 1/8th of second. */
152         history->updater = ecore_poller_add(ECORE_POLLER_CORE, 8 * 60,
153                                                 _history_time_updater,
154                                                 history);
155         _history_time_updater(history);
156 }
157
158 static Call_Info *_history_call_info_search(const History *history,
159                                                 const OFono_Call *call)
160 {
161         Call_Info *call_info;
162         Eina_List *l;
163         long long t = ofono_call_full_start_time_get(call);
164         const char *line_id = ofono_call_line_id_get(call); /* stringshare */
165
166         EINA_LIST_FOREACH(history->calls->list, l, call_info) {
167                 if (call_info->call == call)
168                         return call_info;
169                 else if (!call_info->call) {
170                         if ((t > 0) && (call_info->start_time == t) &&
171                                 (line_id == call_info->line_id)) {
172                                 DBG("associated existing log %p %s (%lld) with "
173                                         "call %p %s (%lld)",
174                                         call_info,
175                                         call_info->line_id,
176                                         call_info->start_time,
177                                         call, line_id, t);
178                                 call_info->call = call;
179                                 return call_info;
180                         }
181                 }
182         }
183
184         return NULL;
185 }
186
187 static Eina_Bool _history_call_info_update(Call_Info *call_info)
188 {
189         OFono_Call_State state;
190
191         EINA_SAFETY_ON_NULL_RETURN_VAL(call_info->call, EINA_FALSE);
192         state = ofono_call_state_get(call_info->call);
193
194         if (state == OFONO_CALL_STATE_INCOMING ||
195                 state == OFONO_CALL_STATE_WAITING) {
196                 if (!call_info->incoming) {
197                         call_info->incoming = EINA_TRUE;
198                         return EINA_TRUE;
199                 }
200         } else if (state == OFONO_CALL_STATE_DIALING ||
201                         state == OFONO_CALL_STATE_ALERTING) {
202                 if (!call_info->incoming) {
203                         call_info->incoming = EINA_FALSE;
204                         return EINA_TRUE;
205                 }
206         } else if (state == OFONO_CALL_STATE_ACTIVE ||
207                         state == OFONO_CALL_STATE_HELD) {
208                 if (!call_info->completed) {
209                         call_info->start_time = ofono_call_full_start_time_get
210                                 (call_info->call);
211                         if (call_info->start_time == 0)
212                                 call_info->start_time = call_info->creation_time;
213
214                         call_info->completed = EINA_TRUE;
215                         return EINA_TRUE;
216                 }
217         }
218
219         return EINA_FALSE;
220 }
221
222 static void _dial_reply(void *data, OFono_Error err,
223                         OFono_Call *call __UNUSED__)
224 {
225         const char *number = data;
226
227         if (err != OFONO_ERROR_NONE) {
228                 char buf[1024];
229                 snprintf(buf, sizeof(buf), "Could not call: %s", number);
230                 gui_simple_popup("Error", buf);
231         }
232 }
233
234 static void _on_item_clicked(void *data, Evas_Object *obj __UNUSED__,
235                                 void *event_info)
236 {
237         Elm_Object_Item *it = event_info;
238         const char *number = data;
239
240         INF("call %s", number);
241         ofono_dial(number, NULL, _dial_reply, number);
242         elm_genlist_item_selected_set(it, EINA_FALSE);
243 }
244
245 static void _history_call_changed(void *data, OFono_Call *call)
246 {
247         History *history = data;
248         const char *line_id = ofono_call_line_id_get(call);
249         Call_Info *call_info;
250         OFono_Call_State state = ofono_call_state_get(call);
251
252         call_info = _history_call_info_search(history, call);
253         DBG("call=%p, id=%s, state=%d, completed=%d, incoming=%d, info=%p",
254                 call, line_id, state,
255                 call_info ? call_info->completed : EINA_FALSE,
256                 call_info ? call_info->incoming : EINA_FALSE,
257                 call_info);
258
259         if (call_info)
260                 goto end;
261
262         call_info = calloc(1, sizeof(Call_Info));
263         EINA_SAFETY_ON_NULL_RETURN(call_info);
264
265         call_info->call = call;
266         call_info->start_time = ofono_call_full_start_time_get(call);
267         call_info->creation_time = time(NULL);
268         if (call_info->start_time == 0)
269                 call_info->start_time = call_info->creation_time;
270         call_info->line_id = eina_stringshare_add(line_id);
271         call_info->name = eina_stringshare_add(ofono_call_name_get(call));
272         history->calls->list =
273                 eina_list_prepend(history->calls->list, call_info);
274         history->calls->dirty = EINA_TRUE;
275
276 end:
277         if (_history_call_info_update(call_info))
278                 history->calls->dirty = EINA_TRUE;
279 }
280
281 static void _history_call_log_save(History *history)
282 {
283         Eet_File *efile;
284
285         EINA_SAFETY_ON_NULL_RETURN(history->calls);
286         DBG("save history (%u calls, dirty: %d) to %s",
287                 eina_list_count(history->calls->list), history->calls->dirty,
288                 history->path);
289
290         ecore_file_unlink(history->bkp);
291         ecore_file_mv(history->path, history->bkp);
292         efile = eet_open(history->path, EET_FILE_MODE_WRITE);
293         EINA_SAFETY_ON_NULL_RETURN(efile);
294         if (!(eet_data_write(efile,
295                                 history->edd_list, HISTORY_ENTRY,
296                                 history->calls, EET_COMPRESSION_DEFAULT)))
297                 ERR("Could in the history log file");
298
299         eet_close(efile);
300 }
301
302 static void _history_call_removed(void *data, OFono_Call *call)
303 {
304         Elm_Object_Item *it;
305         History *history = data;
306         const char *line_id = ofono_call_line_id_get(call);
307         Call_Info *call_info;
308         time_t start;
309         char *tm;
310
311         call_info = _history_call_info_search(history, call);
312         DBG("call=%p, id=%s, info=%p", call, line_id, call_info);
313         EINA_SAFETY_ON_NULL_RETURN(call_info);
314
315         if (call_info->start_time == 0)
316                 call_info->start_time = call_info->creation_time;
317
318         start = call_info->start_time;
319         tm = ctime(&start);
320
321         call_info->end_time = time(NULL);
322         call_info->call = NULL;
323
324         if (call_info->completed)
325                 INF("Call end:  %s at %s", line_id, tm);
326         else {
327                 if (!call_info->incoming)
328                         INF("Not answered: %s at %s", line_id, tm);
329                 else {
330                         INF("Missed: %s at %s", line_id, tm);
331                         if (call_info->it_missed)
332                                 elm_genlist_item_update(call_info->it_missed);
333                         else {
334                                 it = elm_genlist_item_prepend
335                                         (history->genlist_missed,
336                                                 history->itc,
337                                                 call_info, NULL,
338                                                 ELM_GENLIST_ITEM_NONE,
339                                                 _on_item_clicked,
340                                                 call_info->line_id);
341                                 elm_genlist_item_show
342                                         (it, ELM_GENLIST_ITEM_SCROLLTO_IN);
343                                 call_info->it_missed = it;
344                         }
345                 }
346         }
347
348         history->calls->dirty = EINA_TRUE;
349         _history_call_log_save(history);
350
351         if (call_info->it_all)
352                 elm_genlist_item_update(call_info->it_all);
353         else {
354                 it = elm_genlist_item_prepend(history->genlist_all,
355                                                 history->itc,
356                                                 call_info, NULL,
357                                                 ELM_GENLIST_ITEM_NONE,
358                                                 _on_item_clicked,
359                                                 call_info->line_id);
360                 elm_genlist_item_show(it, ELM_GENLIST_ITEM_SCROLLTO_IN);
361                 call_info->it_all = it;
362         }
363 }
364
365 static void _call_info_free(Call_Info *call_info)
366 {
367         eina_stringshare_del(call_info->line_id);
368         eina_stringshare_del(call_info->name);
369         free(call_info);
370 }
371
372 static void _on_del(void *data, Evas *e __UNUSED__,
373                         Evas_Object *obj __UNUSED__, void *event __UNUSED__)
374 {
375         History *history = data;
376         Call_Info *call_info;
377
378         if (history->updater)
379                 ecore_poller_del(history->updater);
380
381         if (history->calls->dirty)
382                 _history_call_log_save(history);
383
384         ofono_call_removed_cb_del(callback_node_call_removed);
385         ofono_call_changed_cb_del(callback_node_call_changed);
386         eet_data_descriptor_free(history->edd);
387         eet_data_descriptor_free(history->edd_list);
388         EINA_LIST_FREE(history->calls->list, call_info)
389                 _call_info_free(call_info);
390         free(history->calls);
391         elm_genlist_item_class_free(history->itc);
392         free(history->path);
393         free(history->bkp);
394         free(history);
395         ecore_file_shutdown();
396         eet_shutdown();
397 }
398
399 static void _on_hide(void *data, Evas *e __UNUSED__,
400                         Evas_Object *obj __UNUSED__, void *event __UNUSED__)
401 {
402         History *history = data;
403         DBG("history became hidden");
404         _history_time_updater_stop(history);
405 }
406
407 static void _on_show(void *data, Evas *e __UNUSED__,
408                         Evas_Object *obj __UNUSED__, void *event __UNUSED__)
409 {
410         History *history = data;
411         DBG("history became visible");
412         _history_time_updater_start(history);
413 }
414
415 static void _on_win_focus_out(void *data, Evas *e __UNUSED__,
416                                 void *event_info __UNUSED__)
417 {
418         History *history = data;
419         DBG("window is unfocused");
420         _history_time_updater_stop(history);
421 }
422
423 static void _on_win_focus_in(void *data, Evas *e __UNUSED__,
424                                 void *event_info __UNUSED__)
425 {
426         History *history = data;
427         DBG("window is focused");
428         _history_time_updater_start(history);
429 }
430
431 static void _history_call_info_descriptor_init(Eet_Data_Descriptor **edd,
432                                                 Eet_Data_Descriptor **edd_list)
433 {
434         Eet_Data_Descriptor_Class eddc;
435
436         EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddc, Call_Info);
437         *edd = eet_data_descriptor_stream_new(&eddc);
438
439         EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddc, Call_Info_List);
440         *edd_list = eet_data_descriptor_stream_new(&eddc);
441
442         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
443                                         "completed", completed, EET_T_UCHAR);
444         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
445                                         "incoming", incoming, EET_T_UCHAR);
446
447         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
448                                         "start_time", start_time, EET_T_LONG_LONG);
449         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
450                                         "end_time", end_time, EET_T_LONG_LONG);
451         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
452                                         "line_id", line_id, EET_T_STRING);
453         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd, Call_Info,
454                                         "name", name, EET_T_STRING);
455
456         EET_DATA_DESCRIPTOR_ADD_LIST(*edd_list, Call_Info_List, "list", list,
457                                         *edd);
458 }
459
460 static void _history_call_log_read(History *history)
461 {
462         Call_Info *call_info;
463         Eina_List *l;
464         Eet_File *efile;
465         Call_Info_List *calls = NULL;
466         Elm_Object_Item *it;
467
468         efile = eet_open(history->path, EET_FILE_MODE_READ);
469
470         if (efile) {
471                 calls = eet_data_read(efile, history->edd_list, HISTORY_ENTRY);
472                 eet_close(efile);
473         }
474
475         if (!calls) {
476                 efile = eet_open(history->bkp, EET_FILE_MODE_READ);
477                 if (efile) {
478                         calls = eet_data_read(efile, history->edd_list,
479                                                 HISTORY_ENTRY);
480                         eet_close(efile);
481                 }
482         }
483
484         if (!calls)
485                 calls = calloc(1, sizeof(Call_Info_List));
486
487         history->calls = calls;
488         EINA_SAFETY_ON_NULL_RETURN(history->calls);
489
490         EINA_LIST_FOREACH(history->calls->list, l, call_info) {
491                 it = elm_genlist_item_append(history->genlist_all,
492                                                 history->itc,
493                                                 call_info, NULL,
494                                                 ELM_GENLIST_ITEM_NONE,
495                                                 _on_item_clicked,
496                                                 call_info->line_id);
497                 call_info->it_all = it;
498
499                 if (call_info->completed)
500                         continue;
501
502                 it = elm_genlist_item_append(history->genlist_missed,
503                                                 history->itc, call_info, NULL,
504                                                 ELM_GENLIST_ITEM_NONE,
505                                                 _on_item_clicked,
506                                                 call_info->line_id);
507                 call_info->it_missed = it;
508         }
509
510         it = elm_genlist_first_item_get(history->genlist_all);
511         if (it)
512                 elm_genlist_item_show(it, ELM_GENLIST_ITEM_SCROLLTO_TOP);
513
514         it = elm_genlist_first_item_get(history->genlist_missed);
515         if (it)
516                 elm_genlist_item_show(it, ELM_GENLIST_ITEM_SCROLLTO_TOP);
517 }
518
519 static char *_item_label_get(void *data, Evas_Object *obj __UNUSED__,
520                                 const char *part)
521 {
522         Call_Info *call_info = data;
523
524         if (strncmp(part, "text.call", strlen("text.call")))
525                 return NULL;
526
527         part += strlen("text.call.");
528
529         if (!strcmp(part, "name")) {
530                 if (!call_info->name || call_info->name[0] == '\0')
531                         return phone_format(call_info->line_id);
532                 return strdup(call_info->name);
533         }
534
535         if (!strcmp(part, "time")) {
536                 if ((call_info->completed) && (call_info->end_time))
537                         return date_format(call_info->end_time);
538                 return date_format(call_info->start_time);
539         }
540
541         /* TODO: Fetch phone type from contacts information*/
542         if (!strcmp(part, "type"))
543                 return strdup("TODO:TELEPHONE TYPE");
544
545         ERR("Unexpected text part: %s", part);
546         return NULL;
547 }
548
549
550 static Eina_Bool _item_state_get(void *data, Evas_Object *obj __UNUSED__,
551                                         const char  *part)
552 {
553         Call_Info *call_info = data;
554
555         if (!strcmp(part, "missed"))
556                 return !call_info->completed;
557         else if (!strcmp(part, "completed"))
558                 return call_info->completed;
559         else if (!strcmp(part, "outgoing"))
560                 return !call_info->incoming;
561         else if (!strcmp(part, "incoming"))
562                 return call_info->incoming;
563
564         ERR("Unexpected state part: %s", part);
565         return EINA_FALSE;
566 }
567
568 static void _on_clicked(void *data __UNUSED__, Evas_Object *obj __UNUSED__,
569                         const char *emission, const char *source __UNUSED__)
570 {
571         EINA_SAFETY_ON_NULL_RETURN(emission);
572         emission += strlen("clicked,");
573
574         if (!strcmp(emission, "all"))
575                 elm_object_signal_emit(obj, "show,all", "gui");
576         else
577                 elm_object_signal_emit(obj, "show,missed", "gui");
578 }
579
580 static void _on_more_clicked(void *data __UNUSED__, Evas_Object *obj __UNUSED__,
581                                 const char *emission __UNUSED__,
582                                 const char *source __UNUSED__)
583 {
584         DBG("TODO");
585 }
586
587 static Evas_Object *_item_content_get(void *data __UNUSED__, Evas_Object *obj,
588                                         const char *part __UNUSED__)
589 {
590         Evas_Object *btn;
591
592         btn = gui_layout_add(obj, "history/img");
593         EINA_SAFETY_ON_NULL_RETURN_VAL(obj, NULL);
594         elm_object_signal_callback_add(btn, "clicked,more", "gui",
595                                         _on_more_clicked, NULL);
596         evas_object_propagate_events_set(btn, EINA_FALSE);
597
598         return btn;
599 }
600
601 Evas_Object *history_add(Evas_Object *parent)
602 {
603         int r;
604         History *history;
605         Evas *e;
606         const char *config_path;
607         char *path, base_dir[PATH_MAX];
608         Elm_Genlist_Item_Class *itc;
609         Evas_Object *obj, *genlist_all, *genlist_missed;
610
611         eet_init();
612         ecore_file_init();
613         history = calloc(1, sizeof(History));
614         EINA_SAFETY_ON_NULL_RETURN_VAL(history, NULL);
615
616         history->self = obj = gui_layout_add(parent, "history_bg");
617         EINA_SAFETY_ON_NULL_GOTO(obj, err_layout);
618
619         genlist_all = elm_genlist_add(obj);
620         EINA_SAFETY_ON_NULL_GOTO(genlist_all, err_object_new);
621         elm_object_style_set(genlist_all, "history");
622
623         genlist_missed = elm_genlist_add(obj);
624         EINA_SAFETY_ON_NULL_GOTO(genlist_missed, err_object_new);
625         elm_object_style_set(genlist_missed, "history");
626
627         itc = elm_genlist_item_class_new();
628         EINA_SAFETY_ON_NULL_GOTO(itc, err_object_new);
629         itc->item_style = "history";
630         itc->func.text_get = _item_label_get;
631         itc->func.content_get = _item_content_get;
632         itc->func.state_get = _item_state_get;
633         itc->func.del = NULL;
634         history->genlist_all = genlist_all;
635         history->genlist_missed = genlist_missed;
636         history->itc = itc;
637
638         elm_object_part_content_set(obj, "elm.swallow.all", genlist_all);
639         elm_object_part_content_set(obj, "elm.swallow.missed", genlist_missed);
640         elm_object_signal_emit(obj, "show,all", "gui");
641         elm_object_signal_callback_add(obj, "clicked,*", "gui",
642                                         _on_clicked, NULL);
643
644         config_path = efreet_config_home_get();
645         snprintf(base_dir, sizeof(base_dir), "%s/%s", config_path,
646                         PACKAGE_NAME);
647         ecore_file_mkpath(base_dir);
648         r = asprintf(&path,  "%s/%s/history.eet", config_path, PACKAGE_NAME);
649
650         if (r < 0)
651                 goto err_item_class;
652
653         history->path = path;
654         r = asprintf(&path,  "%s/%s/history.eet.bkp", config_path,
655                         PACKAGE_NAME);
656
657         if (r < 0)
658                 goto err_path;
659
660         history->bkp = path;
661
662         _history_call_info_descriptor_init(&history->edd, &history->edd_list);
663         _history_call_log_read(history);
664         EINA_SAFETY_ON_NULL_GOTO(history->calls, err_log_read);
665         evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL, _on_del,
666                                         history);
667         evas_object_event_callback_add(obj, EVAS_CALLBACK_HIDE, _on_hide,
668                                         history);
669         evas_object_event_callback_add(obj, EVAS_CALLBACK_SHOW, _on_show,
670                                         history);
671
672         e = evas_object_evas_get(obj);
673         evas_event_callback_add(e, EVAS_CALLBACK_CANVAS_FOCUS_OUT,
674                                 _on_win_focus_out, history);
675         evas_event_callback_add(e, EVAS_CALLBACK_CANVAS_FOCUS_IN,
676                                 _on_win_focus_in, history);
677
678         callback_node_call_changed =
679                 ofono_call_changed_cb_add(_history_call_changed, history);
680         callback_node_call_removed =
681                 ofono_call_removed_cb_add(_history_call_removed, history);
682
683         return obj;
684
685 err_log_read:
686         free(history->bkp);
687         eet_data_descriptor_free(history->edd);
688         eet_data_descriptor_free(history->edd_list);
689 err_path:
690         free(history->path);
691 err_item_class:
692         elm_genlist_item_class_free(itc);
693 err_object_new:
694         free(obj);
695 err_layout:
696         free(history);
697         ecore_file_shutdown();
698         eet_shutdown();
699         return NULL;
700 }