Fix TC-2372 Dialer crashes when BT phone is offline
[profile/ivi/lemolo.git] / messages / overview.c
1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif
4
5 #include <Elementary.h>
6 #include <Eet.h>
7 #include <Eina.h>
8 #include <stdio.h>
9 #include <Ecore.h>
10
11 #include "overview.h"
12 #include "log.h"
13 #include "util.h"
14 #include "gui.h"
15 #include "contacts-ofono-efl.h"
16
17 #define ALL_MESSAGES "all_messages"
18
19 #ifndef EET_COMPRESSION_DEFAULT
20 #define EET_COMPRESSION_DEFAULT 1
21 #endif
22
23 /* This struct hold the content of the messages that will be showed
24  * in the main screen
25  */
26 typedef struct _Messages_List
27 {
28         Eina_List *list;
29         Eina_Bool dirty; /* not in eet */
30         Ecore_Poller *save_poller; /* not in eet */
31 } Messages_List;
32
33 struct _Message
34 {
35         const char *content;
36         unsigned char outgoing;
37         unsigned char state;
38         long long time;
39         const char *phone; /* phone number -  not in eet */
40         int refcount; /* not in eet */
41         void *data; /* not in eet */
42         Elm_Object_Item *it; /* not in eet */
43 };
44
45 typedef struct _Overview
46 {
47         Eet_Data_Descriptor *edd_msg_info;
48         Eet_Data_Descriptor *edd_msg_list;
49         Eet_Data_Descriptor *edd_msg;
50         Eet_Data_Descriptor *edd_c_msg;
51         Messages_List *messages;
52         /* Pending conversations, not saved in eet yet */
53         Messages_List *p_conversations;
54         Evas_Object *layout, *genlist;
55         char *msg_path, *base_dir, *msg_bkp;
56         Ecore_Poller *updater;
57         double last_update;
58         Elm_Genlist_Item_Class *itc;
59         Eina_Hash *pending_sms;
60 } Overview;
61
62 /* Messages showed in the main screen */
63 typedef struct _Message_Info
64 {
65         const char *sender, *last_msg;
66         long long time;
67         int count;
68         Overview *ov; /*not in eet */
69         Elm_Object_Item *it; /* not in eet */
70 } Message_Info;
71
72 static OFono_Callback_List_Incoming_SMS_Node *incoming_sms = NULL;
73 static OFono_Callback_List_Sent_SMS_Node *sent_sms = NULL;
74
75 static void _overview_messages_save(Overview *ov);
76 static Message_Info *_message_info_search(Overview *ov, const char *sender);
77 static void _message_info_del(Message_Info *m_info);
78 static void _overview_messages_save(Overview *ov);
79 static void _conversation_eet_update_save(Overview *ov, Message *msg);
80 static void _message_free(Message *msg);
81
82 void message_ref(Message *msg)
83 {
84         EINA_SAFETY_ON_NULL_RETURN(msg);
85         msg->refcount++;
86 }
87
88 static Eina_Bool _overview_messages_save_do(void *data)
89 {
90         Overview *ov = data;
91         Eet_File *efile;
92         EINA_SAFETY_ON_NULL_RETURN_VAL(ov->messages, ECORE_CALLBACK_DONE);
93
94         ov->messages->save_poller = NULL;
95         ov->messages->dirty = EINA_FALSE;
96
97         ecore_file_unlink(ov->msg_bkp);
98         ecore_file_mv(ov->msg_path, ov->msg_bkp);
99         efile = eet_open(ov->msg_path, EET_FILE_MODE_WRITE);
100         EINA_SAFETY_ON_NULL_GOTO(efile, failed);
101
102         if (!(eet_data_write(efile,
103                                 ov->edd_msg_list, ALL_MESSAGES,
104                                 ov->messages, EET_COMPRESSION_DEFAULT)))
105                 ERR("Could in the messages log file");
106
107         eet_close(efile);
108         return ECORE_CALLBACK_DONE;
109
110 failed:
111         ecore_file_mv(ov->msg_bkp, ov->msg_path);
112         return ECORE_CALLBACK_DONE;
113 }
114
115 void overview_genlist_update(Evas_Object *obj, Message *msg,
116                                 const char *contact)
117 {
118         Overview *ov;
119         Message_Info *m_info;
120
121         EINA_SAFETY_ON_NULL_RETURN(obj);
122         ov = evas_object_data_get(obj, "overview.ctx");
123         EINA_SAFETY_ON_NULL_RETURN(ov);
124         EINA_SAFETY_ON_NULL_RETURN(msg);
125
126         m_info = _message_info_search(ov, contact);
127         EINA_SAFETY_ON_NULL_RETURN(m_info);
128
129         m_info->time = msg->time;
130         eina_stringshare_replace(&m_info->last_msg, msg->content);
131         _overview_messages_save(ov);
132         elm_genlist_item_update(m_info->it);
133 }
134
135 void message_object_item_set(Message *msg, Elm_Object_Item *it)
136 {
137         EINA_SAFETY_ON_NULL_RETURN(msg);
138         msg->it = it;
139 }
140
141 Elm_Object_Item *message_object_item_get(const Message *msg)
142 {
143         EINA_SAFETY_ON_NULL_RETURN_VAL(msg, NULL);
144         return msg->it;
145 }
146
147 void message_state_set(Message *msg, unsigned char state)
148 {
149         EINA_SAFETY_ON_NULL_RETURN(msg);
150         msg->state = state;
151 }
152
153 void *message_data_get(const Message *msg)
154 {
155         EINA_SAFETY_ON_NULL_RETURN_VAL(msg, NULL);
156         return msg->data;
157 }
158
159 void message_data_set(Message *msg, void *data)
160 {
161         EINA_SAFETY_ON_NULL_RETURN(msg);
162         msg->data = data;
163 }
164
165 static Eina_Bool _message_is_equal(Message *m1, Message *m2)
166 {
167         if (strcmp(m1->content, m2->content) != 0)
168                 return EINA_FALSE;
169
170         if (m1->outgoing != m2->outgoing)
171                 return EINA_FALSE;
172
173         if (m1->state != m2->state)
174                 return EINA_FALSE;
175
176         if (m1->time != m2->time)
177                 return EINA_FALSE;
178
179         return EINA_TRUE;
180 }
181
182 void overview_message_from_file_delete(Evas_Object *obj, Message *msg,
183                                         const char *contact)
184 {
185         Eet_File *efile;
186         Overview *ov;
187         char buf[PATH_MAX], bkp[PATH_MAX];
188         Messages_List *messages = NULL;
189         Eina_List *l;
190         Message *msg_aux;
191
192         EINA_SAFETY_ON_NULL_RETURN(obj);
193         ov = evas_object_data_get(obj, "overview.ctx");
194         EINA_SAFETY_ON_NULL_RETURN(ov);
195         EINA_SAFETY_ON_NULL_RETURN(msg);
196
197         snprintf(buf, sizeof(buf), "%s/%s.eet", ov->base_dir, contact);
198         snprintf(bkp, sizeof(bkp), "%s/%s.eet.bkp", ov->base_dir, contact);
199
200         efile = eet_open(buf, EET_FILE_MODE_READ_WRITE);
201
202         if (efile) {
203                 messages = eet_data_read(efile, ov->edd_c_msg, ALL_MESSAGES);
204         }
205
206         if (!messages) {
207                 efile = eet_open(bkp, EET_FILE_MODE_READ_WRITE);
208                 if (efile) {
209                         messages = eet_data_read(efile, ov->edd_c_msg,
210                                                         ALL_MESSAGES);
211                 }
212         }
213
214         if (!messages)
215                 return;
216
217         EINA_LIST_FOREACH(messages->list, l, msg_aux) {
218                 if (_message_is_equal(msg, msg_aux)) {
219                         _message_free(msg_aux);
220                         messages->list = eina_list_remove_list(messages->list,
221                                                                 l);
222                         break;
223                 }
224         }
225         Message_Info *m_info = _message_info_search(ov, contact);
226         EINA_SAFETY_ON_NULL_RETURN(m_info);
227         m_info->count--;
228         if (messages->list && eina_list_count(messages->list) != 0) {
229                 eet_data_write(efile, ov->edd_c_msg, ALL_MESSAGES, messages,
230                                 EET_COMPRESSION_DEFAULT);
231                 eet_close(efile);
232         } else {
233                 eet_close(efile);
234                 _message_info_del(m_info);
235         }
236
237         EINA_LIST_FREE(messages->list, msg_aux)
238                 _message_free(msg_aux);
239         free(messages);
240 }
241
242 unsigned char message_state_get(const Message *msg)
243 {
244         EINA_SAFETY_ON_NULL_RETURN_VAL(msg, '\0');
245         return msg->state;
246 }
247
248 const char * message_content_get(const Message *msg)
249 {
250         EINA_SAFETY_ON_NULL_RETURN_VAL(msg, NULL);
251         return msg->content;
252 }
253
254 const char * message_phone_get(const Message *msg)
255 {
256         EINA_SAFETY_ON_NULL_RETURN_VAL(msg, NULL);
257         return msg->phone;
258 }
259
260 unsigned char message_outgoing_get(const Message *msg)
261 {
262         EINA_SAFETY_ON_NULL_RETURN_VAL(msg, '\0');
263         return msg->outgoing;
264 }
265
266 long long message_time_get(const Message *msg)
267 {
268
269         EINA_SAFETY_ON_NULL_RETURN_VAL(msg, -1);
270         return msg->time;
271 }
272
273 static Message_Info *_message_info_search(Overview *ov, const char *sender)
274 {
275         Eina_List *l;
276         Message_Info *m_info;
277
278         EINA_SAFETY_ON_NULL_RETURN_VAL(ov->messages, NULL);
279
280         EINA_LIST_FOREACH(ov->messages->list, l, m_info) {
281                 if (strcmp(sender, m_info->sender) == 0)
282                         return m_info;
283         }
284
285         return NULL;
286 }
287
288 static void _message_free(Message *msg)
289 {
290         DBG("MSG=%p", msg);
291         eina_stringshare_del(msg->content);
292         eina_stringshare_del(msg->phone);
293         free(msg);
294 }
295
296 void message_del(Message *msg)
297 {
298
299         EINA_SAFETY_ON_NULL_RETURN(msg);
300         DBG("MSG=%p, refcount=%d", msg, msg->refcount);
301         EINA_SAFETY_ON_TRUE_RETURN(msg->refcount <= 0);
302         msg->refcount--;
303
304         if (msg->refcount == 0)
305                 _message_free(msg);
306 }
307
308 static void _message_info_free(Message_Info *m_info)
309 {
310         eina_stringshare_del(m_info->sender);
311         eina_stringshare_del(m_info->last_msg);
312         free(m_info);
313 }
314
315 static void _messages_file_delete(char *dir, const char *sender)
316 {
317         char path[PATH_MAX];
318         snprintf(path, sizeof(path), "%s/%s.eet", dir, sender);
319         ecore_file_unlink(path);
320         snprintf(path, sizeof(path), "%s/%s.eet.bkp", dir, sender);
321         ecore_file_unlink(path);
322 }
323
324 static void _message_info_del(Message_Info *m_info)
325 {
326         Overview *ctx = m_info->ov;
327         Eina_List *l, *next;
328         Message *msg;
329
330         EINA_SAFETY_ON_NULL_RETURN(ctx);
331
332         elm_object_item_del(m_info->it);
333
334         ctx->messages->list = eina_list_remove(ctx->messages->list, m_info);
335         ctx->messages->dirty = EINA_TRUE;
336
337         /* Remove unsaved SMSs */
338         EINA_LIST_FOREACH_SAFE(ctx->p_conversations->list, l, next, msg) {
339                 if (msg->phone == m_info->sender) {
340                         ctx->p_conversations->list =
341                                 eina_list_remove(ctx->p_conversations->list,
342                                                         msg);
343                         message_del(msg);
344                 }
345         }
346
347         _messages_file_delete(ctx->base_dir, m_info->sender);
348         _overview_messages_save(ctx);
349
350         if ((!ctx->messages->list) && (ctx->updater)) {
351                 ecore_poller_del(ctx->updater);
352                 ctx->updater = NULL;
353         }
354
355         _message_info_free(m_info);
356 }
357
358 static void _on_del(void *data, Evas *e __UNUSED__,
359                         Evas_Object *obj __UNUSED__, void *event __UNUSED__)
360 {
361         Overview *ov = data;
362         Message_Info *m_info;
363         Message *msg;
364
365         ofono_incoming_sms_cb_del(incoming_sms);
366         ofono_sent_sms_changed_cb_del(sent_sms);
367
368         if (ov->messages->save_poller)
369                 ecore_poller_del(ov->messages->save_poller);
370
371         if (ov->p_conversations->save_poller)
372                 ecore_poller_del(ov->p_conversations->save_poller);
373
374         if (ov->messages->dirty)
375                 _overview_messages_save_do(ov);
376
377         eina_hash_free(ov->pending_sms);
378
379         if (!ov->p_conversations->dirty) {
380                 EINA_LIST_FREE(ov->p_conversations->list, msg)
381                         message_del(msg);
382         } else {
383                 EINA_LIST_FREE(ov->p_conversations->list, msg) {
384                         _conversation_eet_update_save(ov, msg);
385                         message_del(msg);
386                 }
387         }
388
389         EINA_LIST_FREE(ov->messages->list, m_info)
390                 _message_info_free(m_info);
391
392         eet_data_descriptor_free(ov->edd_msg_info);
393         eet_data_descriptor_free(ov->edd_msg_list);
394         eet_data_descriptor_free(ov->edd_msg);
395         eet_data_descriptor_free(ov->edd_c_msg);
396
397         elm_genlist_item_class_free(ov->itc);
398         free(ov->messages);
399         free(ov->msg_path);
400         free(ov->base_dir);
401         free(ov->msg_bkp);
402         free(ov->p_conversations);
403         free(ov);
404
405         eet_shutdown();
406         ecore_file_shutdown();
407 }
408
409 static void _eet_descriptors_init(Eet_Data_Descriptor **messages,
410                                         Eet_Data_Descriptor **msg_info,
411                                         Eet_Data_Descriptor **edd_msg,
412                                         Eet_Data_Descriptor **edd_c_msg)
413 {
414         Eet_Data_Descriptor_Class eddc;
415         Eet_Data_Descriptor_Class eddcM;
416
417         EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddc, Message_Info);
418         *msg_info = eet_data_descriptor_stream_new(&eddc);
419
420         EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddc, Messages_List);
421         *messages = eet_data_descriptor_stream_new(&eddc);
422
423         EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddcM, Message);
424         *edd_msg = eet_data_descriptor_stream_new(&eddcM);
425
426         EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddcM, Messages_List);
427         *edd_c_msg = eet_data_descriptor_stream_new(&eddcM);
428
429         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd_msg, Message,
430                                         "content", content, EET_T_STRING);
431         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd_msg, Message,
432                                         "time", time, EET_T_LONG_LONG);
433         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd_msg, Message,
434                                         "outgoing", outgoing, EET_T_UCHAR);
435         EET_DATA_DESCRIPTOR_ADD_BASIC(*edd_msg, Message,
436                                         "state", state, EET_T_UCHAR);
437
438         EET_DATA_DESCRIPTOR_ADD_BASIC(*msg_info, Message_Info,
439                                         "sender", sender, EET_T_STRING);
440         EET_DATA_DESCRIPTOR_ADD_BASIC(*msg_info, Message_Info,
441                                         "last_msg", last_msg, EET_T_STRING);
442         EET_DATA_DESCRIPTOR_ADD_BASIC(*msg_info, Message_Info,
443                                         "time", time, EET_T_LONG_LONG);
444         EET_DATA_DESCRIPTOR_ADD_BASIC(*msg_info, Message_Info,
445                                         "count", count, EET_T_INT);
446
447         EET_DATA_DESCRIPTOR_ADD_LIST(*messages, Messages_List, "list", list,
448                                         *msg_info);
449
450         EET_DATA_DESCRIPTOR_ADD_LIST(*edd_c_msg, Messages_List, "list", list,
451                                         *edd_msg);
452
453 }
454
455 static void _overview_messages_save(Overview *ov)
456 {
457         if (ov->messages->save_poller)
458                 return;
459         ov->messages->save_poller = ecore_poller_add(ECORE_POLLER_CORE, 32,
460                                                      _overview_messages_save_do,
461                                                         ov);
462 }
463
464 static void _on_item_clicked(void *data, Evas_Object *obj __UNUSED__,
465                                 void *event_info)
466 {
467         Message_Info *m_info = data;
468         Elm_Object_Item *it = event_info;
469         char buf[PATH_MAX], bkp[PATH_MAX];
470         Overview *ov = m_info->ov;
471         Eet_File *efile;
472         Messages_List *messages = NULL;
473
474         elm_genlist_item_selected_set(it, EINA_FALSE);
475
476         snprintf(buf, sizeof(buf), "%s/%s.eet", ov->base_dir, m_info->sender);
477         snprintf(bkp, sizeof(bkp), "%s/%s.eet.bkp", ov->base_dir,
478                         m_info->sender);
479
480         efile = eet_open(buf, EET_FILE_MODE_READ);
481
482         if (efile) {
483                 messages = eet_data_read(efile, ov->edd_c_msg, ALL_MESSAGES);
484                 eet_close(efile);
485         }
486
487         if (!messages) {
488                 efile = eet_open(bkp, EET_FILE_MODE_READ);
489                 if (efile) {
490                         messages = eet_data_read(efile, ov->edd_c_msg,
491                                                         ALL_MESSAGES);
492                         eet_close(efile);
493                 }
494         }
495         if (messages) {
496                 gui_compose_messages_set(messages->list, m_info->sender);
497                 gui_compose_enter();
498                 /* Compose will free the messages->list for me */
499                 free(messages);
500         } else
501                 INF("Could not read the messages list!");
502 }
503
504 static void _overview_messages_read(Overview *ov)
505 {
506         Eet_File *efile;
507         Messages_List *msg_list = NULL;
508         Eina_List *l;
509         Message_Info *m_info;
510         Elm_Object_Item *it;
511
512         efile = eet_open(ov->msg_path, EET_FILE_MODE_READ);
513
514         if (efile) {
515                 msg_list = eet_data_read(efile, ov->edd_msg_list, ALL_MESSAGES);
516                 eet_close(efile);
517         }
518
519         if (!msg_list) {
520                 efile = eet_open(ov->msg_bkp, EET_FILE_MODE_READ);
521                 if (efile) {
522                         msg_list = eet_data_read(efile, ov->edd_msg_list,
523                                                 ALL_MESSAGES);
524                         eet_close(efile);
525                 }
526         }
527
528         if (!msg_list)
529                 msg_list = calloc(1, sizeof(Messages_List));
530
531         ov->messages = msg_list;
532         EINA_SAFETY_ON_NULL_RETURN(ov->messages);
533
534         EINA_LIST_FOREACH(ov->messages->list, l, m_info) {
535                 it = elm_genlist_item_append(ov->genlist,
536                                                 ov->itc, m_info, NULL,
537                                                 ELM_GENLIST_ITEM_NONE,
538                                                 _on_item_clicked, m_info);
539                 m_info->ov = ov;
540                 m_info->it = it;
541         }
542 }
543
544 static Eina_Bool _overview_time_updater(void *data)
545 {
546         Overview *ctx = data;
547         double now = ecore_loop_time_get();
548         const double interval_threshold = 30.0;
549         Elm_Object_Item *it;
550         const long long update_threshold = time(NULL) - WEEK - DAY;
551
552         if (!ctx->messages->list) {
553                 ctx->updater = NULL;
554                 return EINA_FALSE;
555         }
556
557         if (now - ctx->last_update < interval_threshold)
558                 return EINA_TRUE;
559         ctx->last_update = now;
560
561         it = elm_genlist_first_item_get(ctx->genlist);
562         for (; it != NULL; it = elm_genlist_item_next_get(it)) {
563                 const Message_Info *m_info = elm_object_item_data_get(it);
564                 long long t = m_info->time;
565                 if (EINA_UNLIKELY(t == 0)) {
566                         t = m_info->time;
567                 }
568                 if (EINA_UNLIKELY(t < update_threshold))
569                         break;
570                 elm_genlist_item_update(it);
571         }
572
573         return EINA_TRUE;
574 }
575
576 static void _overview_timer_updater_start(Overview *ov)
577 {
578         Evas *e = evas_object_evas_get(ov->layout);
579         Eina_Bool win_focused = evas_focus_state_get(e);
580         Eina_Bool obj_visible = evas_object_visible_get(ov->layout);
581
582         DBG("poller %p, win_focused=%hhu, obj_visible=%hhu",
583                 ov->updater, win_focused, obj_visible);
584         if (ov->updater)
585                 return;
586         if (!ov->messages->list)
587                 return;
588         if ((!win_focused) || (!obj_visible))
589                 return;
590
591         DBG("start poller messages");
592         /* ECORE_POLLER_CORE is 1/8th of second. */
593         ov->updater = ecore_poller_add(ECORE_POLLER_CORE, 8 * 60,
594                                                 _overview_time_updater,
595                                                 ov);
596         _overview_time_updater(ov);
597 }
598
599 static void _overview_time_updater_stop(Overview *ov)
600 {
601         Evas *e = evas_object_evas_get(ov->layout);
602         Eina_Bool win_focused = evas_focus_state_get(e);
603         Eina_Bool obj_visible = evas_object_visible_get(ov->layout);
604
605         DBG("poller %p, win_focused=%hhu, obj_visible=%hhu",
606                 ov->updater, win_focused, obj_visible);
607         if (!ov->updater)
608                 return;
609         if (win_focused && obj_visible)
610                 return;
611
612         DBG("delete poller %p", ov->updater);
613         ecore_poller_del(ov->updater);
614         ov->updater = NULL;
615 }
616
617 static Message *_message_list_search(Eina_List *list, Message *msg)
618 {
619         Eina_List *l;
620         Message *m;
621
622         EINA_LIST_FOREACH(list, l, m) {
623                 if ((m->content == msg->content &&
624                         m->time == msg->time) ||
625                         (msg->content == m->content && m->state != msg->state))
626                         return m;
627         }
628
629         return NULL;
630 }
631
632 static void _conversation_eet_update_save(Overview *ov, Message *msg)
633 {
634
635         Eet_File *efile;
636         char buf[PATH_MAX], bkp[PATH_MAX];
637         Messages_List *messages = NULL;
638         Message *msg_aux;
639
640         snprintf(buf, sizeof(buf), "%s/%s.eet", ov->base_dir, msg->phone);
641         snprintf(bkp, sizeof(bkp), "%s/%s.eet.bkp", ov->base_dir, msg->phone);
642
643         efile = eet_open(buf, EET_FILE_MODE_READ);
644
645         if (efile) {
646                 messages = eet_data_read(efile, ov->edd_c_msg, ALL_MESSAGES);
647                 eet_close(efile);
648         }
649
650         if (!messages) {
651                 efile = eet_open(bkp, EET_FILE_MODE_READ);
652                 if (efile) {
653                         messages = eet_data_read(efile, ov->edd_c_msg,
654                                                         ALL_MESSAGES);
655                         eet_close(efile);
656                 }
657         }
658
659         /* New file */
660         if (!messages) {
661                 messages = calloc(1, sizeof(Messages_List));
662                 EINA_SAFETY_ON_NULL_RETURN(messages);
663         }
664
665         msg_aux = _message_list_search(messages->list, msg);
666
667         if (!msg_aux) {
668                 messages->list = eina_list_append(messages->list, msg);
669                 msg->refcount++;
670         } else
671                 msg_aux->state = msg->state;
672
673         ecore_file_unlink(bkp);
674         ecore_file_mv(buf, bkp);
675
676         efile = eet_open(buf, EET_FILE_MODE_WRITE);
677         EINA_SAFETY_ON_NULL_RETURN(efile);
678
679         eet_data_write(efile, ov->edd_c_msg, ALL_MESSAGES, messages,
680                         EET_COMPRESSION_DEFAULT);
681
682         eet_close(efile);
683         EINA_LIST_FREE(messages->list, msg_aux) {
684                 if (msg_aux == msg)
685                         message_del(msg);
686                 else /* messages read from eet have no refcount */
687                         _message_free(msg_aux);
688         }
689         free(messages);
690 }
691
692 static Eina_Bool _conversation_eet_update_do(void *data)
693 {
694         Overview *ov = data;
695         Message *msg;
696
697         ov->p_conversations->dirty = EINA_FALSE;
698         ov->p_conversations->save_poller = NULL;
699
700         EINA_LIST_FREE(ov->p_conversations->list, msg) {
701                 _conversation_eet_update_save(ov, msg);
702                 message_del(msg);
703         }
704         ov->p_conversations->list = NULL;
705         return ECORE_CALLBACK_DONE;
706 }
707
708 static void _conversation_eet_update(Overview *ov)
709 {
710         if (ov->p_conversations->save_poller)
711                 return;
712
713         ov->p_conversations->save_poller = ecore_poller_add(ECORE_POLLER_CORE,
714                                                                 32,
715                                                                 _conversation_eet_update_do,
716                                                                 ov);
717 }
718
719 static void _message_info_genlist_update(Overview *ov, time_t timestamp,
720                                                 const char *sender,
721                                                 const char *message)
722 {
723         Message_Info *m_info;
724         Elm_Object_Item *it;
725
726         m_info = _message_info_search(ov, sender);
727
728         if (!m_info) {
729                 m_info = calloc(1, sizeof(Message_Info));
730                 EINA_SAFETY_ON_NULL_RETURN(m_info);
731                 m_info->sender = eina_stringshare_add(sender);
732                 ov->messages->list = eina_list_prepend(ov->messages->list,
733                                                         m_info);
734                 m_info->ov = ov;
735         } else {
736                 ov->messages->list = eina_list_remove(ov->messages->list,
737                                                         m_info);
738                 ov->messages->list = eina_list_prepend(ov->messages->list,
739                                                         m_info);
740                 if (m_info->it)
741                         elm_object_item_del(m_info->it);
742         }
743
744         m_info->count++;
745         m_info->time = timestamp;
746         eina_stringshare_replace(&m_info->last_msg, message);
747         ov->messages->dirty = EINA_TRUE;
748
749         it = elm_genlist_item_prepend(ov->genlist,
750                                         ov->itc, m_info, NULL,
751                                         ELM_GENLIST_ITEM_NONE,
752                                         _on_item_clicked, m_info);
753         m_info->it = it;
754
755         elm_genlist_item_show(m_info->it, ELM_GENLIST_ITEM_SCROLLTO_TOP);
756         _overview_timer_updater_start(ov);
757         _overview_messages_save(ov);
758 }
759
760 Message *message_new(time_t timestamp, const char *content,
761                         Eina_Bool outgoing, OFono_Sent_SMS_State state)
762 {
763         Message *msg;
764
765         msg = calloc(1, sizeof(Message));
766         EINA_SAFETY_ON_NULL_RETURN_VAL(msg, NULL);
767         msg->time = timestamp;
768         msg->outgoing = outgoing;
769         msg->content = eina_stringshare_add(content);
770         msg->state = state;
771         msg->refcount++;
772         return msg;
773 }
774
775 static void _incoming_sms_cb(void *data, unsigned int sms_class,
776                                 time_t timestamp, const char *sender,
777                                 const char *message)
778 {
779         Overview *ov = data;
780         Message *msg;
781
782         /* Users can only send class 1. This is OFono/GSM detail */
783         if (sms_class != 1)
784                 return;
785
786         msg = message_new(timestamp, message, EINA_FALSE,
787                                 OFONO_SENT_SMS_STATE_SENT);
788         EINA_SAFETY_ON_NULL_RETURN(msg);
789         msg->phone = eina_stringshare_add(sender);
790
791         _message_info_genlist_update(ov, timestamp, sender, message);
792
793         ov->p_conversations->list = eina_list_append(ov->p_conversations->list,
794                                                         msg);
795         ov->p_conversations->dirty = EINA_TRUE;
796         _conversation_eet_update(ov);
797 }
798
799 static void _on_show(void *data, Evas *e __UNUSED__,
800                         Evas_Object *obj __UNUSED__, void *event __UNUSED__)
801 {
802         Overview *ov = data;
803         DBG("Overview became visible");
804         _overview_timer_updater_start(ov);
805 }
806
807 static void _on_win_focus_in(void *data, Evas *e __UNUSED__,
808                                 void *event_info __UNUSED__)
809 {
810         Overview *ov = data;
811         DBG("window is focused");
812         _overview_timer_updater_start(ov);
813 }
814
815 static void _on_win_focus_out(void *data, Evas *e __UNUSED__,
816                                 void *event_info __UNUSED__)
817 {
818         Overview *ov = data;
819         DBG("window is unfocused");
820         _overview_time_updater_stop(ov);
821 }
822
823 static void _on_hide(void *data, Evas *e __UNUSED__,
824                         Evas_Object *obj __UNUSED__, void *event __UNUSED__)
825 {
826         Overview *ov = data;
827         DBG("Overview became hidden");
828         _overview_time_updater_stop(ov);
829 }
830
831 static char *_item_label_get(void *data, Evas_Object *obj __UNUSED__,
832                                 const char *part)
833 {
834         Message_Info *m_info = data;
835
836         if (strncmp(part, "elm.text.", strlen("elm.text.")))
837                 return NULL;
838         part += strlen("elm.text.");
839
840         if (strcmp(part, "name") == 0) {
841                 Contact_Info *c_info = gui_contact_search(m_info->sender, NULL);
842
843                 if (!c_info)
844                         return strdup(m_info->sender);
845                 else
846                         return strdup(contact_info_full_name_get(c_info));
847         }
848
849         if (strcmp(part, "content") == 0)
850                 return elm_entry_utf8_to_markup(m_info->last_msg);
851
852         if (strcmp(part, "time") == 0) {
853                 time_t time_msg = m_info->time;
854                 return date_short_format(time_msg);
855         }
856
857         ERR("Unexpected text part: %s", part);
858         return NULL;
859 }
860
861 static void _on_more_clicked(void *data __UNUSED__, Evas_Object *obj __UNUSED__,
862                                 const char *emission __UNUSED__,
863                                 const char *source __UNUSED__)
864 {
865         DBG("TODO - ON MORE CLICKED");
866 }
867
868 static void _on_del_clicked(void *data, Evas_Object *obj __UNUSED__,
869                                 void *event_info __UNUSED__)
870 {
871         Message_Info *m_info = data;
872         _message_info_del(m_info);
873 }
874
875 static Evas_Object *_item_content_get(void *data, Evas_Object *obj,
876                                 const char *part)
877 {
878         Evas_Object *btn = NULL;
879         Message_Info *msg = data;
880
881         if (strncmp(part, "elm.swallow.", strlen("elm.swallow.")))
882                 return NULL;
883         part += strlen("elm.swallow.");
884
885         if (strcmp(part, "more") == 0) {
886                 /* We can use the same button here */
887                 btn = layout_add(obj, "history/img");
888                 EINA_SAFETY_ON_NULL_RETURN_VAL(btn, NULL);
889                 elm_object_signal_callback_add(btn, "clicked,more", "gui",
890                                                 _on_more_clicked, msg);
891         } else if (strcmp(part, "delete") == 0) {
892                 btn = elm_button_add(obj);
893                 EINA_SAFETY_ON_NULL_RETURN_VAL(btn, NULL);
894                 elm_object_style_set(btn, "history-delete");
895                 elm_object_text_set(btn, "delete");
896                 evas_object_smart_callback_add(btn, "clicked", _on_del_clicked,
897                                                 msg);
898                 evas_object_propagate_events_set(btn, EINA_FALSE);
899         }
900         else
901                 ERR("unknown content part '%s'", part);
902
903         return btn;
904 }
905
906 static void _on_list_slide_enter(void *data __UNUSED__,
907                                         Evas_Object *obj,
908                                         void *event_info)
909 {
910         Elm_Object_Item *it = elm_genlist_decorated_item_get(obj);
911         DBG("cancel decorated item=%p", it);
912         if (it)
913                 elm_genlist_item_decorate_mode_set(it, "slide", EINA_FALSE);
914
915         it = event_info;
916         EINA_SAFETY_ON_NULL_RETURN(it);
917         DBG("it=%p", it);
918         elm_genlist_item_decorate_mode_set(it, "slide", EINA_TRUE);
919 }
920
921 static void _on_list_slide_cancel(void *data __UNUSED__,
922                                         Evas_Object *obj,
923                                         void *event_info __UNUSED__)
924 {
925         Elm_Object_Item *it = elm_genlist_decorated_item_get(obj);
926         DBG("it=%p", it);
927         if (it)
928                 elm_genlist_item_decorate_mode_set(it, "slide", EINA_FALSE);
929 }
930
931 void _overview_messages_clear(Overview *ov)
932 {
933         Message_Info *m_info;
934         Eina_List *l, *l_next;
935
936         EINA_LIST_FOREACH_SAFE(ov->messages->list, l, l_next, m_info)
937                 _message_info_del(m_info);
938 }
939
940 static void _on_layout_clicked(void *data, Evas_Object *o,
941                         const char *emission, const char *source __UNUSED__)
942 {
943         Overview *ov = data;
944         DBG("signal: %s", emission);
945
946         EINA_SAFETY_ON_FALSE_RETURN(eina_str_has_prefix(emission, "clicked,"));
947         emission += strlen("clicked,");
948
949         if (strcmp(emission, "edit") == 0) {
950                 elm_object_signal_emit(o, "toggle,on,edit", "gui");
951                 elm_genlist_decorate_mode_set(ov->genlist, EINA_TRUE);
952         } else if (strcmp(emission, "compose") == 0) {
953                 elm_object_signal_emit(o, "toggle,on,compose", "gui");
954                 elm_object_signal_emit(o, "toggle,off,view", "gui");
955                 gui_compose_enter();
956                 elm_object_signal_emit(o, "toggle,off,compose", "gui");
957                 elm_object_signal_emit(o, "toggle,on,view", "gui");
958         } else if (strcmp(emission, "view") == 0) {
959                 elm_object_signal_emit(o, "toggle,off,compose", "gui");
960                 elm_object_signal_emit(o, "toggle,on,view", "gui");
961         } else if (strcmp(emission, "edit,done") == 0) {
962                 elm_object_signal_emit(o, "toggle,off,edit", "gui");
963                 elm_genlist_decorate_mode_set(ov->genlist, EINA_FALSE);
964         } else if (strcmp(emission, "clear") == 0) {
965                 _overview_messages_clear(ov);
966                 elm_object_signal_emit(o, "toggle,off,edit", "gui");
967         } else
968                 ERR("Unkown emission: %s", emission);
969 }
970
971 static void _sent_sms_cb(void *data, OFono_Error error, OFono_Sent_SMS *sms)
972 {
973         Overview *ov = data;
974         Message *msg;
975         OFono_Sent_SMS_State state;
976         time_t timestamp;
977         const char *dest, *message;
978
979         if (error != OFONO_ERROR_NONE) {
980                 ERR("OFono error - Sending a SMS");
981                 return;
982         }
983
984         state = ofono_sent_sms_state_get(sms);
985         dest = ofono_sent_sms_destination_get(sms);
986         message = ofono_sent_sms_message_get(sms);
987         msg = eina_hash_find(ov->pending_sms, sms);
988         timestamp = ofono_sent_sms_timestamp_get(sms);
989
990         DBG("SMS Sent to: %s, message: %s, time: %ld", dest, message,
991                 timestamp);
992         /* New SMS */
993         if (!msg) {
994                 msg = message_new(timestamp, message, EINA_TRUE, state);
995                 EINA_SAFETY_ON_NULL_RETURN(msg);
996                 msg->phone = eina_stringshare_add(dest);
997
998                 _message_info_genlist_update(ov, timestamp, dest, message);
999         } else
1000                 msg->state = state;
1001
1002         if (state == OFONO_SENT_SMS_STATE_FAILED ||
1003                 state == OFONO_SENT_SMS_STATE_SENT)
1004                 eina_hash_del_by_key(ov->pending_sms, sms);
1005         else if (state == OFONO_SENT_SMS_STATE_PENDING) {
1006                 msg->refcount++;
1007                 eina_hash_add(ov->pending_sms, sms, msg);
1008                 ov->p_conversations->list = eina_list_append(ov->p_conversations->list,
1009                                                                 msg);
1010                 DBG("Added to pending conversations - msg:%p - to: %s - msg: %s",
1011                         msg, dest, message);
1012                 ov->p_conversations->dirty = EINA_TRUE;
1013         }
1014
1015         _conversation_eet_update(ov);
1016 }
1017
1018 Evas_Object *overview_add(Evas_Object *parent)
1019 {
1020         Evas_Object *obj, *genlist;
1021         Overview *ov;
1022         const char *config_path;
1023         int r;
1024         Evas *e;
1025         Elm_Genlist_Item_Class *itc;
1026
1027         eet_init();
1028         efreet_init();
1029         ecore_file_init();
1030
1031         ov = calloc(1, sizeof(Overview));
1032         EINA_SAFETY_ON_NULL_RETURN_VAL(ov, NULL);
1033
1034         ov->p_conversations = calloc(1, sizeof(Messages_List));
1035         EINA_SAFETY_ON_NULL_GOTO(ov->p_conversations, err_conversations);
1036
1037         ov->layout = obj = layout_add(parent, "messages-overview");
1038         EINA_SAFETY_ON_NULL_GOTO(obj, err_obj);
1039
1040         elm_object_signal_callback_add(obj, "clicked,*", "gui",
1041                                         _on_layout_clicked, ov);
1042
1043         genlist = elm_genlist_add(obj);
1044         EINA_SAFETY_ON_NULL_GOTO(genlist, err_genlist);
1045         elm_object_style_set(genlist, "messages-overview");
1046         elm_genlist_homogeneous_set(genlist, EINA_TRUE);
1047
1048         elm_scroller_policy_set(genlist, ELM_SCROLLER_POLICY_OFF,
1049                                 ELM_SCROLLER_POLICY_AUTO);
1050
1051         itc = elm_genlist_item_class_new();
1052         EINA_SAFETY_ON_NULL_GOTO(itc, err_itc);
1053         itc->item_style = "messages-overview";
1054         itc->func.text_get = _item_label_get;
1055         itc->func.content_get = _item_content_get;
1056         itc->func.state_get = NULL;
1057         itc->func.del = NULL;
1058         itc->decorate_all_item_style = "messages-overview-delete";
1059         itc->decorate_item_style = "messages-overview-delete";
1060         ov->genlist = genlist;
1061         ov->itc = itc;
1062
1063         evas_object_smart_callback_add(genlist, "drag,start,right",
1064                                         _on_list_slide_enter, ov);
1065         evas_object_smart_callback_add(genlist, "drag,start,left",
1066                                         _on_list_slide_cancel, ov);
1067         evas_object_smart_callback_add(genlist, "drag,start,down",
1068                                         _on_list_slide_cancel, ov);
1069         evas_object_smart_callback_add(genlist, "drag,start,up",
1070                                         _on_list_slide_cancel, ov);
1071
1072         elm_object_part_content_set(obj, "elm.swallow.genlist", genlist);
1073
1074         evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL, _on_del,
1075                                         ov);
1076         evas_object_event_callback_add(obj, EVAS_CALLBACK_HIDE, _on_hide,
1077                                         ov);
1078         evas_object_event_callback_add(obj, EVAS_CALLBACK_SHOW, _on_show,
1079                                         ov);
1080
1081         e = evas_object_evas_get(obj);
1082         evas_event_callback_add(e, EVAS_CALLBACK_CANVAS_FOCUS_OUT,
1083                                 _on_win_focus_out, ov);
1084         evas_event_callback_add(e, EVAS_CALLBACK_CANVAS_FOCUS_IN,
1085                                 _on_win_focus_in, ov);
1086
1087         evas_object_data_set(obj, "overview.ctx", ov);
1088
1089         config_path = efreet_config_home_get();
1090
1091         r = asprintf(&ov->base_dir, "%s/%s/messages", config_path,
1092                         PACKAGE_NAME);
1093
1094         if (r < 0)
1095                 goto err_base_dir;
1096
1097         ecore_file_mkpath(ov->base_dir);
1098
1099         r = asprintf(&ov->msg_path,  "%s/%s/messages/messages.eet", config_path,
1100                         PACKAGE_NAME);
1101
1102         if (r < 0)
1103                 goto err_path;
1104
1105         r = asprintf(&ov->msg_bkp,  "%s/%s/messages/messages.eet.bkp",
1106                         config_path, PACKAGE_NAME);
1107
1108         if (r < 0)
1109                 goto err_bkp;
1110
1111         _eet_descriptors_init(&ov->edd_msg_list, &ov->edd_msg_info,
1112                                 &ov->edd_msg, &ov->edd_c_msg);
1113         _overview_messages_read(ov);
1114
1115         ov->pending_sms = eina_hash_pointer_new(EINA_FREE_CB(message_del));
1116         EINA_SAFETY_ON_NULL_GOTO(ov->pending_sms, err_hash);
1117
1118         incoming_sms = ofono_incoming_sms_cb_add(_incoming_sms_cb, ov);
1119         sent_sms = ofono_sent_sms_changed_cb_add(_sent_sms_cb, ov);
1120         elm_object_signal_emit(ov->layout, "toggle,on,view", "gui");
1121
1122         return obj;
1123
1124 err_hash:
1125         free(ov->msg_bkp);
1126 err_bkp:
1127         free(ov->msg_path);
1128 err_path:
1129         free(ov->base_dir);
1130 err_base_dir:
1131         elm_genlist_item_class_free(itc);
1132 err_itc:
1133         evas_object_del(genlist);
1134 err_genlist:
1135         evas_object_del(obj);
1136 err_obj:
1137         free(ov->p_conversations);
1138 err_conversations:
1139         free(ov);
1140         ecore_file_shutdown();
1141         efreet_shutdown();
1142         eet_shutdown();
1143         return NULL;
1144 }
1145
1146 void overview_all_contact_messages_clear(Evas_Object *obj, const char *contact)
1147 {
1148         Overview *ov;
1149         Message_Info *m_info;
1150
1151         EINA_SAFETY_ON_NULL_RETURN(obj);
1152         ov = evas_object_data_get(obj, "overview.ctx");
1153         EINA_SAFETY_ON_NULL_RETURN(ov);
1154
1155         m_info = _message_info_search(ov, contact);
1156         EINA_SAFETY_ON_NULL_RETURN(m_info);
1157         _message_info_del(m_info);
1158 }