- opensync plugin fixes:
[barry.git] / opensync-plugin / src / vevent.cc
bloba52741eb5ed3c76dc087a8f3db15cc067ca0e3d5
1 //
2 // \file vevent.cc
3 // Conversion routines for vevents (VCALENDAR, etc)
4 //
6 /*
7 Copyright (C) 2006-2007, Net Direct Inc. (http://www.netdirect.ca/)
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
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.
18 See the GNU General Public License in the COPYING file at the
19 root directory of this project for more details.
22 #include "vevent.h"
23 #include "environment.h"
24 #include "trace.h"
25 #include "vformat.h" // comes from opensync, but not a public header yet
26 #include <stdint.h>
27 #include <glib.h>
28 #include <sstream>
31 //////////////////////////////////////////////////////////////////////////////
32 // vCalendar
34 vCalendar::vCalendar()
35 : m_gCalData(0)
39 vCalendar::~vCalendar()
41 if( m_gCalData ) {
42 g_free(m_gCalData);
46 const char *vCalendar::WeekDays[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" };
48 unsigned short vCalendar::GetWeekDayIndex(const char *dayname)
50 for( int i = 0; i < 7; i++ ) {
51 if( strcasecmp(dayname, WeekDays[i]) == 0 )
52 return i;
54 return 0;
57 bool vCalendar::HasMultipleVEvents() const
59 int count = 0;
60 VFormat *format = const_cast<VFormat*>(Format());
61 GList *attrs = format ? vformat_get_attributes(format) : 0;
62 for( ; attrs; attrs = attrs->next ) {
63 VFormatAttribute *attr = (VFormatAttribute*) attrs->data;
64 if( strcasecmp(vformat_attribute_get_name(attr), "BEGIN") == 0 &&
65 strcasecmp(vformat_attribute_get_nth_value(attr, 0), "VEVENT") == 0 )
67 count++;
70 return count > 1;
73 void vCalendar::RecurToVCal()
75 using namespace Barry;
76 using namespace std;
77 Barry::Calendar &cal = m_BarryCal;
79 if( !cal.Recurring )
80 return;
82 vAttrPtr attr = NewAttr("RRULE");
84 switch( cal.RecurringType )
86 case Calendar::Day: // eg. every day
87 AddParam(attr, "FREQ", "DAILY");
88 break;
90 case Calendar::MonthByDate: // eg. every month on the 12th
91 // see: DayOfMonth
92 AddParam(attr, "FREQ", "MONTHLY");
94 ostringstream oss;
95 oss << cal.DayOfMonth;
96 AddParam(attr, "BYMONTHDAY", oss.str().c_str());
98 break;
100 case Calendar::MonthByDay: // eg. every month on 3rd Wed
101 // see: DayOfWeek and WeekOfMonth
102 AddParam(attr, "FREQ", "MONTHLY");
103 if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned
104 ostringstream oss;
105 oss << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
106 AddParam(attr, "BYDAY", oss.str().c_str());
108 break;
110 case Calendar::YearByDate: // eg. every year on March 5
111 // see: DayOfMonth and MonthOfYear
112 AddParam(attr, "FREQ", "YEARLY");
114 ostringstream oss;
115 oss << cal.MonthOfYear;
116 AddParam(attr, "BYMONTH", oss.str().c_str());
119 ostringstream oss;
120 oss << cal.DayOfMonth;
121 AddParam(attr, "BYMONTHDAY", oss.str().c_str());
123 break;
125 case Calendar::YearByDay: // eg. every year on 3rd Wed of Jan
126 // see: DayOfWeek, WeekOfMonth, and
127 // MonthOfYear
128 AddParam(attr, "FREQ", "YEARLY");
129 if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned
130 ostringstream oss;
131 oss << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
132 AddParam(attr, "BYDAY", oss.str().c_str());
134 oss.str("");
135 oss << cal.MonthOfYear;
136 AddParam(attr, "BYMONTH", oss.str().c_str());
138 break;
140 case Calendar::Week: // eg. every week on Mon and Fri
141 // see: WeekDays
142 AddParam(attr, "FREQ", "WEEKLY");
144 ostringstream oss;
145 for( int i = 0, bm = 1, cnt = 0; i < 7; i++, bm <<= 1 ) {
146 if( cal.WeekDays & bm ) {
147 if( cnt )
148 oss << ",";
149 oss << WeekDays[i];
150 cnt++;
153 AddParam(attr, "BYDAY", oss.str().c_str());
155 break;
157 default:
158 throw ConvertError("Unknown RecurringType in Barry Calendar object");
161 // add some common parameters
162 if( cal.Interval > 1 ) {
163 ostringstream oss;
164 oss << cal.Interval;
165 AddParam(attr, "INTERVAL", oss.str().c_str());
167 if( !cal.Perpetual ) {
168 gStringPtr rend(osync_time_unix2vtime(&cal.RecurringEndTime));
169 AddParam(attr, "UNTIL", rend.Get());
172 AddAttr(attr);
175 bool AllDayEvent;
178 /// Recurring data
180 /// Note: interval can be used on all of these recurring types to
181 /// make it happen "every other time" or more, etc.
184 bool Recurring;
185 RecurringCodeType RecurringType;
186 unsigned short Interval; // must be >= 1
187 time_t RecurringEndTime; // only pertains if Recurring is true
188 // sets the date and time when
189 // recurrence of this appointment
190 // should no longer occur
191 // If a perpetual appointment, this
192 // is 0xFFFFFFFF in the low level data
193 // Instead, set the following flag.
194 bool Perpetual; // if true, this will always recur
195 unsigned short TimeZoneCode; // the time zone originally used
196 // for the recurrence data...
197 // seems to have little use, but
198 // set to your current time zone
199 // as a good default
201 unsigned short // recurring details, depending on type
202 DayOfWeek, // 0-6
203 WeekOfMonth, // 1-5
204 DayOfMonth, // 1-31
205 MonthOfYear; // 1-12
206 unsigned char WeekDays; // bitmask, bit 0 = sunday
208 #define CAL_WD_SUN 0x01
209 #define CAL_WD_MON 0x02
210 #define CAL_WD_TUE 0x04
211 #define CAL_WD_WED 0x08
212 #define CAL_WD_THU 0x10
213 #define CAL_WD_FRI 0x20
214 #define CAL_WD_SAT 0x40
220 void vCalendar::RecurToBarryCal()
222 // FIXME - needs to be implemented
224 // GetWeekDayIndex()
227 // Main conversion routine for converting from Barry::Calendar to
228 // a vCalendar string of data.
229 const std::string& vCalendar::ToVCal(const Barry::Calendar &cal)
231 Trace trace("vCalendar::ToVCal");
232 std::ostringstream oss;
233 cal.Dump(oss);
234 trace.logf("ToVCal, initial Barry record: %s", oss.str().c_str());
236 // start fresh
237 Clear();
238 SetFormat( vformat_new() );
239 if( !Format() )
240 throw ConvertError("resource error allocating vformat");
242 // store the Barry object we're working with
243 m_BarryCal = cal;
245 // begin building vCalendar data
246 AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Calendar Record//EN"));
247 AddAttr(NewAttr("BEGIN", "VEVENT"));
248 AddAttr(NewAttr("SEQUENCE", "0"));
249 AddAttr(NewAttr("SUMMARY", cal.Subject.c_str()));
250 AddAttr(NewAttr("DESCRIPTION", cal.Notes.c_str()));
251 AddAttr(NewAttr("LOCATION", cal.Location.c_str()));
253 gStringPtr start(osync_time_unix2vtime(&cal.StartTime));
254 gStringPtr end(osync_time_unix2vtime(&cal.EndTime));
255 gStringPtr notify(osync_time_unix2vtime(&cal.NotificationTime));
257 AddAttr(NewAttr("DTSTART", start.Get()));
258 AddAttr(NewAttr("DTEND", end.Get()));
259 // FIXME - add a truly globally unique "UID" string?
262 AddAttr(NewAttr("BEGIN", "VALARM"));
263 AddAttr(NewAttr("ACTION", "AUDIO"));
265 // notify must be UTC, when specified in DATE-TIME
266 vAttrPtr trigger = NewAttr("TRIGGER", notify.Get());
267 AddParam(trigger, "VALUE", "DATE-TIME");
268 AddAttr(trigger);
270 AddAttr(NewAttr("END", "VALARM"));
273 if( cal.Recurring ) {
274 RecurToVCal();
277 AddAttr(NewAttr("END", "VEVENT"));
279 // generate the raw VCALENDAR data
280 m_gCalData = vformat_to_string(Format(), VFORMAT_EVENT_20);
281 m_vCalData = m_gCalData;
283 trace.logf("ToVCal, resulting vcal data: %s", m_vCalData.c_str());
284 return m_vCalData;
287 // Main conversion routine for converting from vCalendar data string
288 // to a Barry::Calendar object.
289 const Barry::Calendar& vCalendar::ToBarry(const char *vcal, uint32_t RecordId)
291 using namespace std;
293 Trace trace("vCalendar::ToBarry");
294 trace.logf("ToBarry, working on vcal data: %s", vcal);
296 // we only handle vCalendar data with one vevent block
297 if( HasMultipleVEvents() )
298 throw ConvertError("vCalendar data contains more than one VEVENT block, unsupported");
300 // start fresh
301 Clear();
303 // store the vCalendar raw data
304 m_vCalData = vcal;
306 // create format parser structures
307 SetFormat( vformat_new_from_string(vcal) );
308 if( !Format() )
309 throw ConvertError("resource error allocating vformat");
311 string start = GetAttr("DTSTART", "/vevent");
312 trace.logf("DTSTART attr retrieved: %s", start.c_str());
313 string end = GetAttr("DTEND", "/vevent");
314 trace.logf("DTEND attr retrieved: %s", end.c_str());
315 string subject = GetAttr("SUMMARY", "/vevent");
316 trace.logf("SUMMARY attr retrieved: %s", subject.c_str());
317 if( subject.size() == 0 ) {
318 subject = "<blank subject>";
319 trace.logf("ERROR: bad data, blank SUMMARY: %s", vcal);
321 vAttr trigger_obj = GetAttrObj("TRIGGER", 0, "/valarm");
326 // Now, run checks and convert into Barry object
330 // FIXME - we are assuming that any non-UTC timestamps
331 // in the vcalendar record will be in the current timezone...
332 // This is wrong! fix this later.
334 // Also, we current ignore any time zone
335 // parameters that might be in the vcalendar format... this
336 // must be fixed.
338 time_t now = time(NULL);
339 int zoneoffset = osync_time_timezone_diff(localtime(&now));
341 Barry::Calendar &rec = m_BarryCal;
342 rec.SetIds(Barry::Calendar::GetDefaultRecType(), RecordId);
344 if( !start.size() )
345 throw ConvertError("Blank DTSTART");
346 rec.StartTime = osync_time_vtime2unix(start.c_str(), zoneoffset);
348 if( !end.size() ) {
349 // DTEND is actually optional! According to the
350 // RFC, a DTSTART with no DTEND should be treated
351 // like a "special day" like an anniversary, which occupies
352 // no time.
354 // Since the Blackberry doesn't really map well to this
355 // case, we'll set the end time to 1 day past start.
357 rec.EndTime = rec.StartTime + 24 * 60 * 60;
359 else {
360 rec.EndTime = osync_time_vtime2unix(end.c_str(), zoneoffset);
363 rec.Subject = subject;
365 // convert trigger time into notification time
366 // assume no notification, by default
367 rec.NotificationTime = 0;
368 if( trigger_obj.Get() ) {
369 string trigger_type = trigger_obj.GetParam("VALUE");
370 string trigger = trigger_obj.GetValue();
372 if( trigger.size() == 0 ) {
373 trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
375 else if( trigger_type == "DATE-TIME" ) {
376 rec.NotificationTime = osync_time_vtime2unix(trigger.c_str(), zoneoffset);
378 else if( trigger_type == "DURATION" || trigger_type.size() == 0 ) {
379 // default is DURATION (RFC 4.8.6.3)
380 string related = trigger_obj.GetParam("RELATED");
382 // default to relative to start time
383 time_t *relative = &rec.StartTime;
384 if( related == "END" )
385 relative = &rec.EndTime;
387 rec.NotificationTime = *relative + osync_time_alarmdu2sec(trigger.c_str());
389 else {
390 throw ConvertError("Unknown TRIGGER VALUE");
393 else {
394 trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
397 std::ostringstream oss;
398 m_BarryCal.Dump(oss);
399 trace.logf("ToBarry, resulting Barry record: %s", oss.str().c_str());
400 return m_BarryCal;
403 // Transfers ownership of m_gCalData to the caller.
404 char* vCalendar::ExtractVCal()
406 char *ret = m_gCalData;
407 m_gCalData = 0;
408 return ret;
411 void vCalendar::Clear()
413 vBase::Clear();
414 m_vCalData.clear();
415 m_BarryCal.Clear();
417 if( m_gCalData ) {
418 g_free(m_gCalData);
419 m_gCalData = 0;
425 //////////////////////////////////////////////////////////////////////////////
428 VEventConverter::VEventConverter()
429 : m_Data(0)
433 VEventConverter::VEventConverter(uint32_t newRecordId)
434 : m_Data(0),
435 m_RecordId(newRecordId)
439 VEventConverter::~VEventConverter()
441 if( m_Data )
442 g_free(m_Data);
445 // Transfers ownership of m_Data to the caller
446 char* VEventConverter::ExtractData()
448 Trace trace("VEventConverter::ExtractData");
449 char *ret = m_Data;
450 m_Data = 0;
451 return ret;
454 bool VEventConverter::ParseData(const char *data)
456 Trace trace("VEventConverter::ParseData");
458 try {
460 vCalendar vcal;
461 m_Cal = vcal.ToBarry(data, m_RecordId);
464 catch( vCalendar::ConvertError &ce ) {
465 trace.logf("ERROR: vCalendar::ConvertError exception: %s", ce.what());
466 return false;
469 return true;
472 // Barry storage operator
473 void VEventConverter::operator()(const Barry::Calendar &rec)
475 Trace trace("VEventConverter::operator()");
477 // Delete data if some already exists
478 if( m_Data ) {
479 g_free(m_Data);
480 m_Data = 0;
483 try {
485 vCalendar vcal;
486 vcal.ToVCal(rec);
487 m_Data = vcal.ExtractVCal();
490 catch( vCalendar::ConvertError &ce ) {
491 trace.logf("ERROR: vCalendar::ConvertError exception: %s", ce.what());
495 // Barry builder operator
496 bool VEventConverter::operator()(Barry::Calendar &rec, unsigned int dbId)
498 Trace trace("VEventConverter::builder operator()");
500 rec = m_Cal;
501 return true;
504 // Handles calling of the Barry::Controller to fetch a specific
505 // record, indicated by index (into the RecordStateTable).
506 // Returns a g_malloc'd string of data containing the vevent20
507 // data. It is the responsibility of the caller to free it.
508 // This is intended to be passed into the GetChanges() function.
509 char* VEventConverter::GetRecordData(BarryEnvironment *env, unsigned int dbId,
510 Barry::RecordStateTable::IndexType index)
512 Trace trace("VEventConverter::GetRecordData()");
514 using namespace Barry;
516 VEventConverter cal2event;
517 RecordParser<Calendar, VEventConverter> parser(cal2event);
518 env->m_pCon->GetRecord(dbId, index, parser);
519 return cal2event.ExtractData();
522 bool VEventConverter::CommitRecordData(BarryEnvironment *env, unsigned int dbId,
523 Barry::RecordStateTable::IndexType StateIndex, uint32_t recordId,
524 const char *data, bool add, std::string &errmsg)
526 Trace trace("VEventConverter::CommitRecordData()");
528 uint32_t newRecordId;
529 if( add ) {
530 // use given id if possible
531 if( recordId && !env->m_CalendarSync.m_Table.GetIndex(recordId) ) {
532 // recordId is unique and non-zero
533 newRecordId = recordId;
535 else {
536 trace.log("Can't use recommended recordId, generating new one.");
537 newRecordId = env->m_CalendarSync.m_Table.MakeNewRecordId();
540 else {
541 newRecordId = env->m_CalendarSync.m_Table.StateMap[StateIndex].RecordId;
543 trace.logf("newRecordId: %lu", newRecordId);
545 VEventConverter convert(newRecordId);
546 if( !convert.ParseData(data) ) {
547 std::ostringstream oss;
548 oss << "unable to parse change data for new RecordId: "
549 << newRecordId << " data: " << data;
550 errmsg = oss.str();
551 trace.logf(errmsg.c_str());
552 return false;
555 Barry::RecordBuilder<Barry::Calendar, VEventConverter> builder(convert);
557 if( add ) {
558 trace.log("adding record");
559 env->m_pCon->AddRecord(dbId, builder);
561 else {
562 trace.log("setting record");
563 env->m_pCon->SetRecord(dbId, StateIndex, builder);
564 trace.log("clearing dirty flag");
565 env->m_pCon->ClearDirty(dbId, StateIndex);
568 return true;