HEAD: an extra unit test for wvbase64 that I forgot to check in a while ago.
[wvapps.git] / evolution / calendaradaptor.cc
blobbe1e1c3d9dbbf0c2e8d351232989ce673c1bb53c
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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
22 * USA
26 extern "C"
28 #include <bonobo/bonobo-main.h>
32 #include "wvtnefconv.h"
33 #include "wvfile.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,
43 gpointer data)
45 CalendarAdaptor *adaptor = static_cast<CalendarAdaptor *>(data);
47 WvLog log(adaptor->log);
49 if (NULL == adaptor)
50 return;
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)
68 bonobo_main_quit();
71 void CalendarAdaptor::merge_vcal2tnef(CalComponent *component, WvTnef &tnef,
72 WvString type, bool task)
74 WvString vcal("");
76 icalcomponent *icalevent = cal_component_get_icalcomponent(component);
77 int prop_count = 0;
78 icalproperty *prop = icalcomponent_get_first_property(icalevent,
79 ICAL_ANY_PROPERTY);
81 while (prop != NULL)
83 prop_count++;
84 WvString prop_name = icalproperty_get_name(prop);
85 WvString prop_value = icalproperty_get_value_as_string(prop);
86 log(WvLog::Debug5,
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,
94 ICAL_ANY_PROPERTY);
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;
103 bool chosen = false;
104 int relative = 0;
105 time_t dtend = 0;
106 time_t dtstart = 0;
107 time_t alarmtime = 0;
108 uint32_t rel_min = 0;
109 CalAlarmAction action;
110 CalAlarmTrigger trigger;
111 CalComponentText caltext;
112 CalComponentAlarm *alarm = NULL;
113 string *s;
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);
141 else
142 alarmtime = dtstart;
144 // Outlook only supports alarms before the start time
145 if (alarmtime < dtstart)
147 chosen = true;
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,
154 s, PT_LONG);
156 // Enable the alarm
157 rel_min = 1;
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,
161 s, PT_BOOLEAN);
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"),
168 s, PT_STRING8);
170 log("I set an alarm: %s", caltext.value);
174 cal_component_alarm_free(alarm);
175 alarm = NULL;
177 if (chosen)
178 break;
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
197 #if 0
198 static bool is_prop_in_cal(WvStringParm _prop_name, WvStringParm _prop_value,
199 icalcomponent *ical)
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,
204 ICAL_ANY_PROPERTY);
205 while (prop != NULL)
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)
221 return true;
223 if (strcmp(prop_name, "EXDATE") != 0)
224 return false;
227 prop = icalcomponent_get_next_property(ical, ICAL_ANY_PROPERTY);
230 return false;
233 static bool vcal_has_changed(CalComponent *component, const WvTnef &tnef,
234 bool task)
236 WvLog log("vcal_has_changed", WvLog::Debug1);
238 if (is_vcal(tnef))
240 icalcomponent *tnef_ical =
241 icalcomponent_new_from_string(get_vcal(tnef, task).edit());
242 icalcomponent *new_ical =
243 cal_component_get_icalcomponent(component);
245 icalproperty *prop =
246 icalcomponent_get_first_property(new_ical, ICAL_ANY_PROPERTY);
248 while (prop != NULL)
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));
272 return true;
276 prop = icalcomponent_get_next_property(new_ical,
277 ICAL_ANY_PROPERTY);
281 return false;
283 #endif
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)
289 assert(data);
290 assert(uid != NULL);
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)
302 assert(data);
303 assert(uid != NULL);
305 CalendarAdaptor *adaptor = static_cast<CalendarAdaptor *>(data);
306 adaptor->item_removed(uid);
309 CalendarAdaptor::CalendarAdaptor(WvStringParm _key,
310 WvStringParm _path,
311 const UniConf parent) :
312 ExchangeItAdaptor(_key, _path, true, parent),
313 log("CalendarAdaptor",
314 WvLog::Debug3),
315 is_task(false)
317 _initCalClient(_path, "/calendar.ics");
320 CalendarAdaptor::CalendarAdaptor(WvStringParm _key,
321 WvStringParm _path,
322 const UniConf parent,
323 WvStringParm _file,
324 bool task) :
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();
344 if (!client)
346 log("Calendar Client Initialization failed\n");
347 return;
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);
358 return;
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);
368 if (is_vcal(tnef))
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 *
383 g_free(vcal_utf8);
385 if (has_alarm(tnef))
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);
398 if (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)
407 do_alarm = false;
410 if (do_alarm)
412 alarm = cal_component_alarm_new();
413 p = tnef.get_property(str2guid(GUID_3),
414 ALARM_TRIGGER);
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();
429 else
430 cal_component_get_summary(calcomp, &text);
432 text.altrep = NULL;
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)
451 return true;
454 return false;
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)
473 success = true;
476 return success;
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();
494 return;
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);
511 if (file.isok())
512 istream = &file;
514 WvTnef tnef(istream);
515 file.close();
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))
523 log(WvLog::Debug1,
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)
540 // if so, do nothing
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();
545 return;
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;
559 uidcount++;
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);
571 return 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
576 # if 0
577 const bool CalendarAdaptor::_sync_local_items()
579 WvStringList uid_list;
581 log(WvLog::Debug1,
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
586 // this function
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);
589 uidcount = 0;
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());
600 struct stat statbuf;
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);
607 WvTnef tnef(&file);
608 file.close();
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))
615 log(WvLog::Debug1,
616 "YOU CARE ABOUT THIS: TNEF rewrite failure!\n");
618 needs_update = true;
621 else
623 WvTnef tnef;
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))
628 log(WvLog::Debug1,
629 "YOU CARE ABOUT THIS: TNEF rewrite failure!\n");
631 needs_update = true;
633 if (needs_update)
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);
639 else
641 log(WvLog::Debug3,
642 "Item %s in folder %s is in sync, no need to resend\n",
643 i(), this->key);
645 // FIXMEFIXME: Should we delete the calcomponent?
647 else
649 // we're screwed
652 return true;
655 #endif
657 const bool CalendarAdaptor::_sync_local_items()
659 return true;
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
667 * given CalClient
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;
676 char *uid;
677 while(current)
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");
689 else
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
700 if (!is_task)
702 state.root()[CALENDAR_LOCAL_IMPORTED_KEY].setmeint(1);
704 else
706 state.root()[TASKS_LOCAL_IMPORTED_KEY].setmeint(1);
708 state.root().commit();