1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * Authors : Patrick Patterson <ppatters@nit.ca>
4 * Peter Colijn <pcolijn@nit.ca>
5 * Scott MacLean <scott@nit.ca>
6 * William Lachance <wlach@nit.ca>
8 * Copyright 2003-2004, Net Integration Technologies, Inc.
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of version 2 of the GNU General Public
12 * License as published by the Free Software Foundation.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
28 #include <bonobo/bonobo-main.h>
32 #include "wvtnefconv.h"
35 #include "calendaradaptor.h"
36 #include "localstorage.h"
37 #include "eituniconfkeys.h"
40 // Called when the client is connected to the calendar
41 static void cal_open_cb(CalClient
*client
,
42 CalClientOpenStatus status
,
45 CalendarAdaptor
*adaptor
= static_cast<CalendarAdaptor
*>(data
);
47 WvLog
log(adaptor
->log
);
52 // Notify the element that it's ready for updates
53 if (CAL_CLIENT_OPEN_SUCCESS
== status
)
55 log("cal_open_cb. status: ready!\n");
56 adaptor
->set_ready(true);
60 // Called when the client is disconnected from the calendar
61 static void cal_destroy_cb(GObject
*object
, gpointer data
)
63 g_message("Disconnected from a calendar");
65 // Do we really want to quit?? If one calendar dies
66 // maybe we should keep going with the others?
67 // Yeah, but they usually all die at the same time (pcolijn)
71 void CalendarAdaptor::merge_vcal2tnef(CalComponent
*component
, WvTnef
&tnef
,
72 WvString type
, bool task
)
76 icalcomponent
*icalevent
= cal_component_get_icalcomponent(component
);
78 icalproperty
*prop
= icalcomponent_get_first_property(icalevent
,
84 WvString prop_name
= icalproperty_get_name(prop
);
85 WvString prop_value
= icalproperty_get_value_as_string(prop
);
87 "Property Number: %s - Property kind: %s Property value: %s\n",
88 prop_count
, prop_name
, prop_value
);
90 if (!!prop_name
&& !!prop_value
)
91 vcal
.append("%s:%s\n", prop_name
, prop_value
);
93 prop
= icalcomponent_get_next_property(icalevent
,
97 // This code is not in WvMAPI because it relies heavily
98 // on Evolution CalComponent stuff...
99 if (cal_component_has_alarms(component
))
101 GList
*list
= cal_component_get_alarm_uids(component
);
102 const char *auid
= NULL
;
107 time_t alarmtime
= 0;
108 uint32_t rel_min
= 0;
109 CalAlarmAction action
;
110 CalAlarmTrigger trigger
;
111 CalComponentText caltext
;
112 CalComponentAlarm
*alarm
= NULL
;
115 for (GList
*l
= list
; l
; l
= l
->next
)
117 auid
= (const char *)l
->data
;
118 alarm
= cal_component_get_alarm(component
, auid
);
119 cal_component_alarm_get_action(alarm
, &action
);
120 dtstart
= icaltime_as_timet(icalcomponent_get_dtstart(icalevent
));
121 dtend
= icaltime_as_timet(icalcomponent_get_dtend(icalevent
));
123 // Use the first suitable DISPLAY alarm for the alarm
124 // in Outlook; Outlook only supports one
125 if (action
== CAL_ALARM_DISPLAY
)
127 cal_component_alarm_get_trigger(alarm
, &trigger
);
129 if (CAL_ALARM_TRIGGER_RELATIVE_START
== trigger
.type
)
131 relative
= icaldurationtype_as_int(trigger
.u
.rel_duration
);
132 alarmtime
= dtstart
+ relative
;
134 else if (CAL_ALARM_TRIGGER_RELATIVE_END
== trigger
.type
)
136 relative
= icaldurationtype_as_int(trigger
.u
.rel_duration
);
137 alarmtime
= dtend
+ relative
;
139 else if (CAL_ALARM_TRIGGER_ABSOLUTE
== trigger
.type
)
140 alarmtime
= icaltime_as_timet(trigger
.u
.abs_time
);
144 // Outlook only supports alarms before the start time
145 if (alarmtime
< dtstart
)
148 rel_min
= (dtstart
- alarmtime
) / 60;
149 rel_min
= swap4(rel_min
);
151 // Set the alarm time
152 s
= new string((const char *)&rel_min
, 4);
153 tnef
.set_property(str2guid(GUID_3
), ALARM_TRIGGER
,
158 rel_min
= swap4(rel_min
);
159 s
= new string((const char *)&rel_min
, 4);
160 tnef
.set_property(str2guid(GUID_3
), IS_ALARM_ENABLED
,
163 // Set the alarm description
164 cal_component_alarm_get_description(alarm
, &caltext
);
165 s
= new string(caltext
.value
);
166 tnef
.set_property(str2guid(GUID_2
),
167 str2ustr("EvoAlarmTxt"),
170 log("I set an alarm: %s", caltext
.value
);
174 cal_component_alarm_free(alarm
);
181 cal_obj_uid_list_free(list
);
184 tnef_set_type(tnef
, type
);
186 char *vcal_fooenc
= utf8_to_barenc(vcal
, get_codepage(tnef
));
187 vcal2tnef(vcal_fooenc
, tnef
, task
);
189 log("After merging, the TNEF VCal now looks like this:\n%s\n",
190 get_vcal(tnef
, task
));
194 // FIXME: Do we need this? This looks suspiciously like the crappy
195 // "duplicate detection" code that was in the server, and was intended
196 // for the "fix stuff after a crash" code
198 static bool is_prop_in_cal(WvStringParm _prop_name
, WvStringParm _prop_value
,
201 WvLog
log("is_prop_in_cal", WvLog::Debug5
);
202 // this should work unless ical is horribly broken. assuming it isn't
203 icalproperty
*prop
= icalcomponent_get_first_property(ical
,
207 WvString prop_name
= icalproperty_get_name(prop
);
208 WvString prop_value
= icalproperty_get_value_as_string(prop
);
210 log("(%s, %s): prop name: %s, prop value: %s\n",
211 _prop_name
.cstr(), _prop_value
.cstr(),
212 prop_name
.cstr(), prop_value
.cstr());
214 if (strcmp(prop_name
, _prop_name
) == 0)
216 // evolution appends a "Z" to the last modified
217 // time stamp to indicate timezone-- for better or worse,
218 // we don't add this in the icals we generate in wvtnef,
219 // so we have to do the comparison like this.
220 if (strncmp(prop_value
, _prop_value
, strlen(prop_value
)) == 0)
223 if (strcmp(prop_name
, "EXDATE") != 0)
227 prop
= icalcomponent_get_next_property(ical
, ICAL_ANY_PROPERTY
);
233 static bool vcal_has_changed(CalComponent
*component
, const WvTnef
&tnef
,
236 WvLog
log("vcal_has_changed", WvLog::Debug1
);
240 icalcomponent
*tnef_ical
=
241 icalcomponent_new_from_string(get_vcal(tnef
, task
).edit());
242 icalcomponent
*new_ical
=
243 cal_component_get_icalcomponent(component
);
246 icalcomponent_get_first_property(new_ical
, ICAL_ANY_PROPERTY
);
250 WvString prop_name
= icalproperty_get_name(prop
);
251 WvString prop_value
= icalproperty_get_value_as_string(prop
);
253 // NOTE: UID is not a property that appears in a CalComponent..
254 // Use strncmp here because property names sometimes get
255 // extra values appended to them with ';'s and (RFC for details)
256 if (!strncmp(prop_name
, "SUMMARY", 7) ||
257 !strncmp(prop_name
, "DTSTART", 7) ||
258 !strncmp(prop_name
, "DTEND", 5) ||
259 !strncmp(prop_name
, "LOCATION", 8) ||
260 !strncmp(prop_name
, "DESCRIPTION", 11) ||
261 !strncmp(prop_name
, "LAST-MODIFIED", 13) ||
262 !strncmp(prop_name
, "RRULE", 5) ||
263 !strncmp(prop_name
, "EXDATE", 6) ||
264 !strncmp(prop_name
, "TRANSP", 6) ||
265 !strncmp(prop_name
, "CLASS", 5) ||
266 !strncmp(prop_name
, "CATEGORIES", 10))
268 if (!is_prop_in_cal(prop_name
, prop_value
, tnef_ical
))
270 log("Property: %s not found in calendar for item:\n%s\n",
271 prop_name
, get_vcal(tnef
, task
));
276 prop
= icalcomponent_get_next_property(new_ical
,
285 // This gets called back whenever evolution changes/updates something in
286 // the calendar. It just marshals the uid to a callback in CalendarAdaptor
287 static void update_cb(CalClient
*client
, const char *uid
, gpointer data
)
292 CalendarAdaptor
*adaptor
= static_cast<CalendarAdaptor
*>(data
);
293 adaptor
->item_updated(uid
);
296 // This gets called back whenever the user deletes something in their
297 // evolution calendar (or we delete something manually). In the former
298 // case, it adds a key to the todelete list in the gconf keyspace. In the
299 // latter case, this function does nothing.
300 static void remove_cb(CalClient
*client
, const char *uid
, gpointer data
)
305 CalendarAdaptor
*adaptor
= static_cast<CalendarAdaptor
*>(data
);
306 adaptor
->item_removed(uid
);
309 CalendarAdaptor::CalendarAdaptor(WvStringParm _key
,
311 const UniConf parent
) :
312 ExchangeItAdaptor(_key
, _path
, true, parent
),
313 log("CalendarAdaptor",
317 _initCalClient(_path
, "/calendar.ics");
320 CalendarAdaptor::CalendarAdaptor(WvStringParm _key
,
322 const UniConf parent
,
325 ExchangeItAdaptor(_key
, _path
, true, parent
),
326 log("CalendarAdaptor", WvLog::Debug3
), is_task(task
)
328 _initCalClient(_path
, _file
);
331 CalendarAdaptor::~CalendarAdaptor()
333 g_object_unref(client
);
336 void CalendarAdaptor::_initCalClient(WvStringParm path
, WvStringParm file
)
338 log("CalendarAdaptor::_initCalClient\n");
339 WvString calendar_filename
= path
;
340 calendar_filename
.append(file
);
341 log("Calendar filename: %s\n", calendar_filename
);
343 client
= cal_client_new();
346 log("Calendar Client Initialization failed\n");
350 g_signal_connect(client
, "backend_died", G_CALLBACK(cal_destroy_cb
), this);
351 g_signal_connect(client
, "cal_opened", G_CALLBACK(cal_open_cb
), this);
352 g_signal_connect(client
, "obj_updated", G_CALLBACK(update_cb
), this);
353 g_signal_connect(client
, "obj_removed", G_CALLBACK(remove_cb
), this);
355 if (!cal_client_open_calendar(client
, calendar_filename
.cstr(), FALSE
))
357 log("Cannot open calendar %s\n", calendar_filename
);
362 /** Call this to update or add an item to the server
364 const bool CalendarAdaptor::update_item(WvTnef
&tnef
, WvStringParm uid
)
366 log(WvLog::Debug1
, "Told to update item: %s\n", uid
);
370 // Set the UID so we don't duplicate stuff
371 tnef_set_uid(tnef
, uid
);
373 WvString vcal
= get_vcal(tnef
, is_task
);
374 char *vcal_utf8
= fooenc_to_utf8(vcal
, get_codepage(tnef
));
376 log(" %s\n\n\n", vcal
);
377 CalComponent
*calcomp
= cal_component_new();
378 icalcomponent
*icalcomp
= icalparser_parse_string(vcal_utf8
);
379 cal_component_set_icalcomponent(calcomp
, icalcomp
);
380 cal_component_rescan(calcomp
);
382 // icalparser_parse_string takes const char *
387 log(WvLog::Debug1
, "Item has an alarm. Creating Evo alarm.\n");
388 bool do_alarm
= true;
389 uint32_t rel_min
= 0;
390 const WvMapiPart
*p
= NULL
;
391 CalComponent
*itemcomp
= NULL
;
392 CalComponentText text
;
393 CalAlarmTrigger trigger
;
394 CalAlarmAction action
= CAL_ALARM_DISPLAY
;
395 CalComponentAlarm
*alarm
= NULL
;
397 cal_client_get_object(client
, uid
, &itemcomp
);
400 cal_component_remove_all_alarms(itemcomp
);
401 cal_component_rescan(itemcomp
);
403 // If we were unable to remove the alarms,
404 // it's unsafe to add new ones
405 if (cal_client_update_object(client
, itemcomp
) !=
406 CAL_CLIENT_RESULT_SUCCESS
)
412 alarm
= cal_component_alarm_new();
413 p
= tnef
.get_property(str2guid(GUID_3
),
415 rel_min
= *(const uint32_t *)p
->get_string()->c_str();
416 rel_min
= swap4(rel_min
);
418 trigger
.type
= CAL_ALARM_TRIGGER_RELATIVE_START
;
419 trigger
.u
.rel_duration
=
420 icaldurationtype_from_int(-1*rel_min
*60);
422 cal_component_alarm_set_action(alarm
, action
);
423 cal_component_alarm_set_trigger(alarm
, trigger
);
425 p
= tnef
.get_property(str2guid(GUID_2
),
426 str2ustr("EvoAlarmTxt"));
427 if (p
&& strlen(p
->get_string()->c_str()))
428 text
.value
= p
->get_string()->c_str();
430 cal_component_get_summary(calcomp
, &text
);
433 cal_component_alarm_set_description(alarm
, &text
);
434 cal_component_add_alarm(calcomp
, alarm
);
435 cal_component_rescan(calcomp
);
436 cal_component_alarm_free(alarm
);
440 // FIXME: check if calendar is "available" first somehow
442 // asking the cal client to update the object will send an
443 // update signal, but we don't want to add the item we just added
444 // to the tosend list..
445 state
[NOSEND_KEY
][uid
].setmeint(1);
446 log("adding nosend key: %s\n", state
[NOSEND_KEY
][uid
].getme());
448 // So we know if we're successful
449 if (cal_client_update_object(client
, calcomp
) ==
450 CAL_CLIENT_RESULT_SUCCESS
)
457 const bool CalendarAdaptor::delete_item(WvStringParm uid
)
459 log(WvLog::Debug1
, "Told to delete item %s\n", uid
);
460 bool success
= false;
462 // don't want to send a delete command when we're
463 // responding to a server event
464 state
[NOSEND_KEY
][uid
].setmeint(1);
465 log("adding nosend key: %s\n", state
[NOSEND_KEY
][uid
].getme());
467 if (cal_client_remove_object (client
, uid
.cstr()) ==
468 CAL_CLIENT_RESULT_SUCCESS
)
470 // FIXME: should remove the TNEF as well..
471 // if/when we do, we need to update DELETE to use the item sent
472 // back from the server on denial (pcolijn)
479 // This gets called by update_cb, which in turn gets called when the user
480 // changes something in Evolution
481 // it should take the item pointed to by uid, modify its associated TNEF,
482 // and enqueue it to be sent
483 void CalendarAdaptor::item_updated(WvStringParm uid
)
485 log(WvLog::Debug1
, "Got an update for %s\n", uid
);
487 // Check whether we're expecting this update
488 // (i.e. if we just added/updated the item b/c we just synced w/the server)
489 // if so, remove from the nosend list but don't send it out
490 if (state
[NOSEND_KEY
][uid
].getmeint())
492 log("Action: It's in our nosend list, do nothing\n");
493 state
[NOSEND_KEY
][uid
].remove();
497 // Otherwise we want to update our local TNEF, and enqueue it to send to
498 // the server at a later time..
499 log("Action: converting ical to tnef, and enqueing it to be sent\n");
501 CalComponent
*component
= NULL
;
503 if (cal_client_get_object(client
, uid
, &component
) ==
504 CAL_CLIENT_GET_SUCCESS
)
506 // Open up our TNEF for modification
507 WvStream
*istream
= NULL
;
508 WvString
tnef_path("%s/%s", get_storage_path(), uid
);
509 WvFile
file(tnef_path
, O_RDONLY
);
514 WvTnef
tnef(istream
);
516 tnef_set_uid(tnef
, uid
);
518 WvString item_type
= get_item_type();
519 merge_vcal2tnef(component
, tnef
, item_type
, is_task
);
521 // FIXME: should check for failure of this operation
522 if (!tnef
.rewrite(tnef_path
))
524 "YOU CARE ABOUT THIS: TNEF rewrite failure!\n");
526 log("Converted vcal looks like this:\n%s\n",
527 get_vcal(tnef
, is_task
));
529 state
[TOSEND_KEY
][uid
].setmeint(1);
530 // FIXMEFIXME: Should we also delete the calcomponent?
534 void CalendarAdaptor::item_removed(WvStringParm uid
)
536 log(WvLog::Debug1
, "Got a remove for %s\n", uid
);
538 // Check whether we're expecting this update
539 // (i.e.: if we just removed the item b/c we just synced w/ the server)
541 if (state
[NOSEND_KEY
][uid
].getmeint())
543 log("Action: It's in our nosend list, do nothing\n");
544 state
[NOSEND_KEY
][uid
].remove();
548 state
[TODELETE_KEY
][uid
].setmeint(1);
550 log("Action: enqueing it to be deleted", uid
);
553 static int uidcount
= 0;
555 static void add_uid_to_strlist(gpointer data
, gpointer userdata
)
557 const char *uid
= (const char *)data
;
558 WvStringList
*string_list
= (WvStringList
*)userdata
;
561 string_list
->append(uid
);
564 WvStringList
*CalendarAdaptor::get_all_uids()
566 WvStringList
*uid_list
= new WvStringList();
567 GList
*evo_uid_list
= cal_client_get_uids(client
, CALOBJ_TYPE_ANY
);
568 g_list_foreach(evo_uid_list
, &add_uid_to_strlist
, uid_list
);
569 cal_obj_uid_list_free(evo_uid_list
);
574 // FIXME: Do we need this? This is the "try to recover after a crash code"
575 // Ideally, we want the plugin to be solid enough that this isn't necessary
577 const bool CalendarAdaptor::_sync_local_items()
579 WvStringList uid_list
;
582 "Checking to see if we have any out-of-sync local items\n");
584 // FIXMEFIXME: have to check if the cal_client is loaded.. I'm not sure
585 // what to do if it isn't.. maybe it should be a precondition for calling
587 GList
*evo_uid_list
= cal_client_get_uids(client
, CALOBJ_TYPE_ANY
);
588 g_list_foreach(evo_uid_list
, &add_uid_to_strlist
, &uid_list
);
590 cal_obj_uid_list_free(evo_uid_list
);
592 WvStringList::Iter
i(uid_list
);
593 for(i
.rewind(); i
.next();)
595 CalComponent
*component
= NULL
;
596 if (cal_client_get_object(client
, i().cstr(), &component
) ==
597 CAL_CLIENT_GET_SUCCESS
)
599 WvString
tnef_path("%s/%s", get_storage_path(), i());
601 bool needs_update
= false;
603 if(stat(tnef_path
.cstr(), &statbuf
) == 0)
605 // FIXME: should check for failure of this operation
606 WvFile
file(tnef_path
, O_RDONLY
);
609 if (vcal_has_changed(component
, tnef
, is_task
))
611 WvString item_type
= get_item_type();
612 merge_vcal2tnef(component
, tnef
, item_type
, is_task
);
613 // FIXME: should check for failure of this operation
614 if (!tnef
.rewrite(tnef_path
))
616 "YOU CARE ABOUT THIS: TNEF rewrite failure!\n");
624 WvString item_type
= get_item_type();
625 merge_vcal2tnef(component
, tnef
, item_type
, is_task
);
626 // FIXME: should check for failure of this operation
627 if (!tnef
.rewrite(tnef_path
))
629 "YOU CARE ABOUT THIS: TNEF rewrite failure!\n");
635 state
[TOSEND_KEY
][i()].setmeint(1);
636 log("Syncing local item %s in folder %s to be sent, "
637 "as it has changed\n", i(), this->key
);
642 "Item %s in folder %s is in sync, no need to resend\n",
645 // FIXMEFIXME: Should we delete the calcomponent?
657 const bool CalendarAdaptor::_sync_local_items()
663 /** Merge CalClient into the calendar associated to the adpator
665 * @param local_client the CalClient to pull the data from
666 * @note should be called by the open callback set for the
669 void CalendarAdaptor::merge_into_calendar(CalClient
*local_client
)
671 WvLog
log("migrate_local_calendar", WvLog::Debug1
);
673 GList
*list
= cal_client_get_uids(local_client
, CALOBJ_TYPE_ANY
);
674 GList
*current
= list
;
675 CalComponent
*component
= NULL
;
679 uid
= (char*)current
->data
;
680 if (cal_client_get_object(local_client
, uid
, &component
) ==
681 CAL_CLIENT_GET_SUCCESS
)
683 log("Got 1 %s item to migrate !\n",(is_task
?"task":"calendar"));
684 if (cal_client_update_object(client
, component
) != CAL_CLIENT_RESULT_SUCCESS
)
686 log("Error updating client!\n");
691 log("cal_client_get_object() failed.\n");
693 g_object_unref(component
);
694 current
= g_list_next(current
);
696 cal_obj_uid_list_free(list
);
697 // not sure about this one, looks like it makes wombat crash
698 // how else can I get rid of this. -- hub
699 // g_object_unref(local_client); // we are done with the client
702 state
.root()[CALENDAR_LOCAL_IMPORTED_KEY
].setmeint(1);
706 state
.root()[TASKS_LOCAL_IMPORTED_KEY
].setmeint(1);
708 state
.root().commit();