1 /*
2 * biji-memo-provider.c
3 * Copyright (C) Pierre-Yves LUYTEN 2013 <py@luyten.fr>
4 *
5 * bijiben is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * bijiben is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 /*
21 * http://tools.ietf.org/html/rfc2445
22 *
23 * Evolution UI offers to sync Memo to local computer
24 * TODO: check this
25 */
26
27
28 #include <libecal/libecal.h> /* ECalClient */
29 #include "biji-tracker.h"
30 #include "biji-memo-provider.h"
31 #include "biji-memo-note.h"
32
33
34 #define MINER_ID "gn:memo:miner:fd48e15b-2460-4761-a7be-942616102fa6"
35
36 struct _BijiMemoProvider
37 {
38 BijiProvider parent_instance;
39
40 BijiProviderInfo info;
41 ESource *source;
42 ECalClient *client;
43 GtkWidget *icon;
44
45 /* Startup */
46 GSList *memos;
47 GHashTable *tracker;
48 GHashTable *items;
49 GHashTable *archives;
50 GQueue *queue;
51 };
52
53
54
55
56 G_DEFINE_TYPE (BijiMemoProvider, biji_memo_provider, BIJI_TYPE_PROVIDER);
57
58
59 /* Properties */
60 enum {
61 PROP_0,
62 PROP_SOURCE,
63 BIJI_MEMO_PROP
64 };
65
66
67
68 static GParamSpec *properties[BIJI_MEMO_PROP] = { NULL, };
69
70 /* Memos */
71
72
73 gboolean
time_val_from_icaltime(ICalTime * itt,glong * result)74 time_val_from_icaltime (ICalTime *itt, glong *result)
75 {
76 g_autoptr(GDateTime) t = NULL;
77 g_autofree char *iso = NULL;
78
79 if (!itt)
80 return FALSE;
81
82 iso = isodate_from_time_t (i_cal_time_as_timet (itt));
83
84 t = g_date_time_new_from_iso8601 (iso, NULL);
85 if (t == NULL)
86 {
87 return FALSE;
88 }
89
90 *result = g_date_time_to_unix (t);
91 return TRUE;
92 }
93
94
95
96 ICalTime *
icaltime_from_time_val(glong t)97 icaltime_from_time_val (glong t)
98 {
99 g_autoptr(GDateTime) tv = NULL;
100 ICalTime *out;
101
102 tv = g_date_time_new_from_unix_utc (t);
103 out = i_cal_time_new_from_day_of_year (g_date_time_get_day_of_year (tv),
104 g_date_time_get_year (tv));
105
106 return out;
107 }
108
109
110
111 typedef struct
112 {
113 ECalComponent *ecal;
114 BijiMemoProvider *self;
115 BijiInfoSet set;
116 } BijiMemoItem;
117
118
119
120 static BijiMemoItem *
memo_item_new(BijiMemoProvider * self)121 memo_item_new (BijiMemoProvider *self)
122 {
123 BijiMemoItem *item;
124
125 item = g_slice_new (BijiMemoItem);
126 item->ecal = NULL;
127 item->self = self;
128
129 item->set.content = NULL;
130 item->set.mtime = 0;
131 item->set.created = 0;
132 item->set.title = NULL;
133 item->set.url = NULL;
134
135 return item;
136 }
137
138
139 /* ECalComponent do not transfer much values */
140 static void
memo_item_free(BijiMemoItem * item)141 memo_item_free (BijiMemoItem *item)
142 {
143 g_free (item->set.content);
144 g_slice_free (BijiMemoItem, item);
145 }
146
147
148
149 /* No color, hence we use default color */
150
151 static void
create_note_from_item(BijiMemoItem * item)152 create_note_from_item (BijiMemoItem *item)
153 {
154 BijiNoteObj *note;
155 GdkRGBA color;
156 BijiManager *manager;
157
158 manager = biji_provider_get_manager (BIJI_PROVIDER (item->self));
159 note = biji_memo_note_new_from_info (item->self,
160 manager,
161 &item->set,
162 item->ecal,
163 item->set.content,
164 item->self->client);
165
166 biji_manager_get_default_color (manager, &color);
167 biji_note_obj_set_rgba (note, &color);
168 g_hash_table_replace (item->self->items,
169 g_strdup (item->set.url),
170 note);
171 }
172
173
174
175 static void
trash(gpointer urn_uuid,gpointer self)176 trash (gpointer urn_uuid, gpointer self)
177 {
178 biji_tracker_trash_resource (
179 biji_provider_get_manager (BIJI_PROVIDER (self)), (gchar*) urn_uuid);
180 }
181
182
183 static void
handle_next_item(BijiMemoProvider * self)184 handle_next_item (BijiMemoProvider *self)
185 {
186 BijiMemoItem *item;
187 GList *list;
188
189 item = g_queue_pop_head (self->queue);
190
191 if (item != NULL)
192 {
193 g_hash_table_remove (self->tracker, item->set.url);
194
195 create_note_from_item (item);
196 /* debug pour tracker. Il faut en plus datasource->urn */
197 g_debug ("created=%"G_GINT64_FORMAT, item->set.created);
198 g_debug ("title=%s", item->set.title);
199 g_debug ("url=%s", item->set.url);
200 g_debug ("content=%s\n================\n\n\n", item->set.content);
201
202 biji_tracker_ensure_resource_from_info (
203 biji_provider_get_manager (BIJI_PROVIDER (self)), &item->set);
204
205 //memo_item_free (item);
206 handle_next_item (self);
207 }
208
209
210 /* Loading and notes creation over. Provide notes to manager */
211 else
212 {
213 /* Post load tracker db clean-up */
214 list = g_hash_table_get_values (self->tracker);
215 g_list_foreach (list, trash, self);
216 g_list_free (list);
217
218 /* Now simply provide data to controller */
219 list = g_hash_table_get_values (self->items);
220 BIJI_PROVIDER_GET_CLASS (self)->notify_loaded (BIJI_PROVIDER (self), list, BIJI_LIVING_ITEMS);
221 g_list_free (list);
222 }
223 }
224
225
226
227 static void
on_object_list_got(GObject * obj,GAsyncResult * res,gpointer user_data)228 on_object_list_got (GObject *obj,
229 GAsyncResult *res,
230 gpointer user_data)
231 {
232 GSList *l, *desc, *ll;
233 GError *error;
234 BijiMemoProvider *self = BIJI_MEMO_PROVIDER (user_data);
235
236 error = NULL;
237 e_cal_client_get_object_list_finish (self->client,
238 res,
239 &self->memos,
240 &error);
241
242 if (error)
243 {
244 g_warning ("e cal get obj list fin %s", error->message);
245 g_error_free (error);
246 return;
247 }
248
249 for (l = self->memos; l != NULL; l = l->next)
250 {
251 ECalComponent *co; /* Memo */
252 ECalComponentText *text;
253 BijiMemoItem *item;
254 ICalTime *t;
255 glong time, dtstart;
256 ECalComponentDateTime *tz;
257
258
259 item = memo_item_new (self);
260 item->set.datasource_urn = g_strdup (self->info.datasource);
261 co = item->ecal = e_cal_component_new_from_icalcomponent (l->data);
262
263 /* Summary, url */
264 text = e_cal_component_get_summary (co);
265 item->set.title = text ? g_strdup (e_cal_component_text_get_value (text)) : NULL;
266 e_cal_component_text_free (text);
267 item->set.url = g_strdup (e_cal_component_get_uid (co));
268
269
270 /* Last modified, created */
271 tz = e_cal_component_get_dtstart (co);
272 if (time_val_from_icaltime (tz ? e_cal_component_datetime_get_value (tz) : NULL, &time))
273 dtstart = time;
274 else
275 dtstart = 0;
276 e_cal_component_datetime_free (tz);
277
278 t = e_cal_component_get_last_modified (co); /* or dtstart */
279 if (time_val_from_icaltime (t, &time))
280 item->set.mtime = time;
281 else
282 item->set.mtime = dtstart;
283 g_clear_object (&t);
284
285 t = e_cal_component_get_created (co); /* or dtstart */
286 if (time_val_from_icaltime (t, &time))
287 item->set.created = time;
288 else
289 item->set.created = dtstart;
290 g_clear_object (&t);
291
292 /* Description */
293 desc = e_cal_component_get_descriptions (co);
294 for (ll=desc; ll!=NULL; ll=ll->next)
295 {
296 ECalComponentText *txt = ll->data;
297
298 if (txt && e_cal_component_text_get_value (txt) != NULL)
299 {
300 item->set.content = g_strdup (e_cal_component_text_get_value (txt));
301 break;
302 }
303 }
304
305 if (item->set.content == NULL)
306 g_warning ("note %s has no content", item->set.title);
307
308
309 g_slist_free_full (desc, e_cal_component_text_free);
310 g_queue_push_head (self->queue, item);
311 }
312
313 handle_next_item (self);
314 }
315
316
317
318
319 static const gchar *
i_want_all_memos(void)320 i_want_all_memos (void)
321 {
322 return "occur-in-time-range? (make-time \"19820421T204153Z\") (make-time \"20820423T232416Z\")";
323 }
324
325
326
327 /* Stock all existing urn-uuid. Then parse files */
328 static void
on_notes_mined(GObject * source_object,GAsyncResult * res,gpointer user_data)329 on_notes_mined (GObject *source_object,
330 GAsyncResult *res,
331 gpointer user_data)
332 {
333 BijiMemoProvider *self;
334 TrackerSparqlConnection *connect;
335 TrackerSparqlCursor *cursor;
336 GError *error;
337
338 self = user_data;
339 connect = TRACKER_SPARQL_CONNECTION (source_object);
340 error = NULL;
341 cursor = tracker_sparql_connection_query_finish (connect, res, &error);
342
343 if (error)
344 {
345 g_warning ("%s", error->message);
346 g_error_free (error);
347 }
348
349 if (cursor)
350 {
351 while (tracker_sparql_cursor_next (cursor, NULL, NULL))
352 {
353 g_hash_table_insert (self->tracker,
354 g_strdup (tracker_sparql_cursor_get_string (cursor, 0, NULL)),
355 g_strdup (tracker_sparql_cursor_get_string (cursor, 1, NULL)));
356
357 }
358
359 g_object_unref (cursor);
360 }
361
362 e_cal_client_get_object_list (self->client,
363 i_want_all_memos(), /* sexp, not null */
364 NULL, /* Cancellable */
365 on_object_list_got,
366 self);
367 }
368
369
370
371 /* From gnome-calendar -> ported to GdkRGBA */
372 static GdkPixbuf*
get_pixbuf_from_color(GdkRGBA * color,gint size)373 get_pixbuf_from_color (GdkRGBA *color, gint size)
374 {
375 cairo_surface_t *surface;
376 cairo_t *cr;
377 gint width, height;
378 GdkPixbuf *pix;
379
380
381 width = height = size;
382 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
383 cr = cairo_create (surface);
384
385 cairo_set_source_rgb (cr,
386 color->red,
387 color->green,
388 color->blue);
389 cairo_rectangle (cr, 0, 0, width, height);
390 cairo_fill (cr);
391 cairo_destroy (cr);
392 pix = gdk_pixbuf_get_from_surface (surface,
393 0, 0,
394 width, height);
395 cairo_surface_destroy (surface);
396 return pix;
397 }
398
399
400 static gboolean
_get_icon(BijiMemoProvider * self,GtkWidget ** result)401 _get_icon (BijiMemoProvider *self,
402 GtkWidget **result)
403 {
404 ESourceExtension *ext;
405 const gchar *color;
406 GdkRGBA rgba;
407 GdkPixbuf *pix;
408
409 ext = e_source_get_extension (self->source, E_SOURCE_EXTENSION_MEMO_LIST);
410 color = e_source_selectable_get_color (E_SOURCE_SELECTABLE (ext));
411
412 if (color == NULL || !gdk_rgba_parse (&rgba, color))
413 return FALSE;
414
415 pix = get_pixbuf_from_color (&rgba, 40);
416 *result = gtk_image_new_from_pixbuf (pix);
417 g_object_set (G_OBJECT (*result), "margin", 4, NULL);
418 g_object_unref (pix);
419
420 return TRUE;
421 }
422
423
424
425 /*
426 * Once the client is connected,
427 * mine notes for this.
428 */
429 static void
on_client_connected(GObject * obj,GAsyncResult * res,gpointer user_data)430 on_client_connected (GObject *obj,
431 GAsyncResult *res,
432 gpointer user_data)
433 {
434 GError *error = NULL;
435 gchar *query;
436 BijiMemoProvider *self = BIJI_MEMO_PROVIDER (user_data);
437
438 e_cal_client_connect_finish (res, &error);
439
440 if (error)
441 {
442 g_warning ("On Client Connected : %s", error->message);
443 return;
444 }
445
446 self->client = E_CAL_CLIENT (obj);
447 query = g_strdup_printf ("SELECT ?url ?urn WHERE {?urn a nfo:Note; "
448 " nie:dataSource '%s' ; nie:url ?url}",
449 self->info.datasource);
450
451 tracker_sparql_connection_query_async (
452 biji_manager_get_tracker_connection (
453 biji_provider_get_manager (BIJI_PROVIDER (self))),
454 query,
455 NULL,
456 on_notes_mined,
457 self);
458
459 g_free (query);
460 }
461
462
463 /* GObject */
464
465
466
467 static void
biji_memo_provider_constructed(GObject * obj)468 biji_memo_provider_constructed (GObject *obj)
469 {
470 BijiMemoProvider *self;
471
472 G_OBJECT_CLASS (biji_memo_provider_parent_class)->constructed (obj);
473
474 self = BIJI_MEMO_PROVIDER (obj);
475
476 /* Info */
477 self->info.unique_id = e_source_get_uid (self->source);
478 self->info.datasource = g_strdup_printf ("memo:%s",
479 self->info.unique_id);
480 self->info.name = g_strdup (e_source_get_display_name (self->source));
481 if (!_get_icon (self, &self->info.icon))
482 self->info.icon = gtk_image_new_from_icon_name ("user-home", GTK_ICON_SIZE_INVALID);
483
484 gtk_image_set_pixel_size (GTK_IMAGE (self->info.icon), 48);
485 g_object_ref (self->info.icon);
486
487 e_cal_client_connect (self->source,
488 E_CAL_CLIENT_SOURCE_TYPE_MEMOS,
489 10, /* wait up to 10 seconds until the memo list is connected */
490 NULL, /* cancel */
491 on_client_connected,
492 self);
493 }
494
495
496
497 static void
on_object_created(GObject * client,GAsyncResult * res,gpointer user_data)498 on_object_created (GObject *client,
499 GAsyncResult *res,
500 gpointer user_data)
501 {
502 GError *error = NULL;
503 gchar *out_uid;
504
505 e_cal_client_create_object_finish (E_CAL_CLIENT (client),
506 res,
507 &out_uid,
508 &error);
509
510 if (error)
511 {
512 g_warning ("%s", error->message);
513 g_error_free (error);
514 return;
515 }
516
517 g_free (out_uid);
518 }
519
520
521
522 static BijiNoteObj *
memo_create_note(BijiProvider * provider,const gchar * str)523 memo_create_note (BijiProvider *provider,
524 const gchar *str)
525 {
526 BijiMemoProvider *self = BIJI_MEMO_PROVIDER (provider);
527 BijiInfoSet info;
528 BijiNoteObj *note;
529 ICalComponent *icalcomp;
530 ECalComponent *comp;
531 gchar *title, *html;
532 time_t dttime;
533 ECalComponentDateTime *dt;
534 ICalTimezone *zone;
535 ICalTime *itt;
536
537
538 title = NULL;
539
540 if (str)
541 {
542 title = biji_manager_get_unique_title (
543 biji_provider_get_manager (provider), str);
544 }
545
546 info.url = NULL;
547 info.title = title;
548 info.mtime = g_get_real_time () / G_USEC_PER_SEC;
549 info.content = title;
550 info.created = info.mtime;
551
552 e_cal_client_get_default_object_sync (
553 self->client, &icalcomp, NULL, NULL);
554
555 if (icalcomp == NULL)
556 icalcomp = i_cal_component_new (I_CAL_VJOURNAL_COMPONENT);
557
558 comp = e_cal_component_new ();
559 if (!e_cal_component_set_icalcomponent (comp, icalcomp))
560 {
561 g_object_unref (icalcomp);
562 e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_JOURNAL);
563 }
564
565
566 /* Dates and commit sequence */
567 dttime = time (NULL);
568 zone = e_cal_client_get_default_timezone (self->client);
569
570 if (dttime)
571 {
572 itt = i_cal_time_new_from_timet_with_zone (
573 dttime, FALSE, zone);
574 dt = e_cal_component_datetime_new_take (itt, g_strdup (zone ? i_cal_timezone_get_tzid (zone) : NULL));
575
576 e_cal_component_set_dtstart (comp, dt);
577 e_cal_component_set_dtend (comp, dt);
578 e_cal_component_set_last_modified (comp, itt);
579 e_cal_component_set_created (comp, itt);
580
581 e_cal_component_datetime_free (dt);
582 }
583
584 if (dttime)
585 e_cal_component_commit_sequence (comp);
586
587
588 /* make sure the component has an UID and info get it */
589 if (! (info.url = (gchar *) i_cal_component_get_uid (icalcomp)))
590 {
591 gchar *uid;
592
593 uid = e_util_generate_uid ();
594 i_cal_component_set_uid (icalcomp, uid);
595 info.url = uid;
596 }
597
598
599 /* Create the note, push the new vjournal */
600 note = biji_memo_note_new_from_info (
601 self,
602 biji_provider_get_manager (provider),
603 &info,
604 comp,
605 str,
606 self->client);
607
608
609 biji_note_obj_set_title (note, title);
610 biji_note_obj_set_raw_text (note, title);
611 html = html_from_plain_text (title);
612 biji_note_obj_set_html (note, html);
613 g_free (html);
614
615
616 e_cal_client_create_object (self->client,
617 icalcomp,
618 E_CAL_OPERATION_FLAG_NONE,
619 NULL, /* GCancellable */
620 on_object_created,
621 self);
622
623 g_free (title);
624
625 return note;
626 }
627
628
629
630
631 static void
biji_memo_provider_init(BijiMemoProvider * self)632 biji_memo_provider_init (BijiMemoProvider *self)
633 {
634 self->queue = g_queue_new ();
635 self->items = g_hash_table_new_full (g_str_hash, g_str_equal,
636 g_free, g_object_unref);
637 self->tracker = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
638 }
639
640 static void
biji_memo_provider_finalize(GObject * object)641 biji_memo_provider_finalize (GObject *object)
642 {
643 BijiMemoProvider *self = BIJI_MEMO_PROVIDER (object);
644
645 g_slist_free_full (self->memos, g_object_unref);
646
647 g_hash_table_unref (self->tracker);
648 g_hash_table_unref (self->items);
649 g_queue_free_full (self->queue, (GDestroyNotify) memo_item_free);
650
651 G_OBJECT_CLASS (biji_memo_provider_parent_class)->finalize (object);
652 }
653
654
655
656 static void
biji_memo_provider_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)657 biji_memo_provider_set_property (GObject *object,
658 guint property_id,
659 const GValue *value,
660 GParamSpec *pspec)
661 {
662 BijiMemoProvider *self = BIJI_MEMO_PROVIDER (object);
663
664
665 switch (property_id)
666 {
667 case PROP_SOURCE:
668 self->source = g_value_get_object (value);
669 break;
670 default:
671 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
672 break;
673 }
674 }
675
676
677 static void
biji_memo_provider_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)678 biji_memo_provider_get_property (GObject *object,
679 guint property_id,
680 GValue *value,
681 GParamSpec *pspec)
682 {
683 BijiMemoProvider *self = BIJI_MEMO_PROVIDER (object);
684
685 switch (property_id)
686 {
687 case PROP_SOURCE:
688 g_value_set_object (value, self->source);
689 break;
690 default:
691 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
692 break;
693 }
694 }
695
696
697
698 static const BijiProviderInfo *
memo_provider_get_info(BijiProvider * provider)699 memo_provider_get_info (BijiProvider *provider)
700 {
701 return &(BIJI_MEMO_PROVIDER (provider)->info);
702 }
703
704
705 static void
biji_memo_provider_class_init(BijiMemoProviderClass * klass)706 biji_memo_provider_class_init (BijiMemoProviderClass *klass)
707 {
708 GObjectClass* object_class = G_OBJECT_CLASS (klass);
709 BijiProviderClass *provider_class = BIJI_PROVIDER_CLASS (klass);
710
711 object_class->finalize = biji_memo_provider_finalize;
712 object_class->constructed = biji_memo_provider_constructed;
713 object_class->get_property = biji_memo_provider_get_property;
714 object_class->set_property = biji_memo_provider_set_property;
715
716 provider_class->get_info = memo_provider_get_info;
717 provider_class->create_new_note = memo_create_note;
718 provider_class->create_note_full = NULL;
719
720 properties[PROP_SOURCE] =
721 g_param_spec_object ("source",
722 "Provider ESource",
723 "ESource Memo associated to Provider",
724 E_TYPE_SOURCE,
725 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
726
727 g_object_class_install_properties (object_class, BIJI_MEMO_PROP, properties);
728 }
729
730
731
732 BijiProvider *
biji_memo_provider_new(BijiManager * manager,ESource * source)733 biji_memo_provider_new (BijiManager *manager,
734 ESource *source)
735 {
736 return g_object_new (BIJI_TYPE_MEMO_PROVIDER,
737 "manager", manager,
738 "source", source,
739 NULL);
740 }
741