- opensync plugin:
[barry.git] / opensync-plugin / src / vevent.cc
blob533d18c8e25522519429227d4140089e2013006c
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),
36 m_format(0)
40 vCalendar::~vCalendar()
42 if( m_gCalData ) {
43 g_free(m_gCalData);
46 if( m_format ) {
47 vformat_free(m_format);
51 vAttrPtr vCalendar::NewAttr(const char *name)
53 Trace trace("vCalendar::NewAttr");
55 trace.logf("creating valueless attr: %s", name);
57 vAttrPtr attr(vformat_attribute_new(NULL, name));
58 if( !attr.Get() )
59 throw ConvertError("resource error allocating vformat attribute");
60 return attr;
63 vAttrPtr vCalendar::NewAttr(const char *name, const char *value)
65 Trace trace("vCalendar::NewAttr");
67 if( strlen(value) == 0 ) {
68 trace.logf("attribute '%s' contains no data, skipping", name);
69 return vAttrPtr();
72 trace.logf("creating attr: %s, %s", name, value);
74 vAttrPtr attr(vformat_attribute_new(NULL, name));
75 if( !attr.Get() )
76 throw ConvertError("resource error allocating vformat attribute");
78 vformat_attribute_add_value(attr.Get(), value);
79 return attr;
82 void vCalendar::AddAttr(vAttrPtr attr)
84 Trace trace("vCalendar::AddAttr");
86 if( !attr.Get() ) {
87 trace.log("attribute contains no data, skipping");
88 return;
91 vformat_add_attribute(m_format, attr.Extract());
94 void vCalendar::AddParam(vAttrPtr &attr, const char *name, const char *value)
96 Trace trace("vCalendar::AddParam");
98 if( !attr.Get() ) {
99 trace.log("attribute pointer contains no data, skipping");
100 return;
102 if( strlen(value) == 0 ) {
103 trace.log("parameter value is empty, skipping");
104 return;
107 VFormatParam *pParam = vformat_attribute_param_new(name);
108 vformat_attribute_param_add_value(pParam, value);
109 vformat_attribute_add_param(attr.Get(), pParam);
112 std::string vCalendar::GetAttr(const char *attrname)
114 Trace trace("vCalendar::GetAttr");
115 trace.logf("getting attr: %s", attrname);
117 std::string ret;
119 VFormatAttribute *attr = vformat_find_attribute(m_format, attrname);
120 if( attr ) {
121 if( vformat_attribute_is_single_valued(attr) ) {
122 ret = vformat_attribute_get_value(attr);
124 else {
125 // FIXME - does this ever happen?
126 ret = vformat_attribute_get_nth_value(attr, 0);
130 trace.logf("attr value: %s", ret.c_str());
132 return ret;
135 vAttr vCalendar::GetAttrObj(const char *attrname)
137 Trace trace("vCalendar::GetAttrObj");
138 trace.logf("getting attr: %s", attrname);
140 return vAttr(vformat_find_attribute(m_format, attrname));
143 std::string vAttr::GetName()
145 std::string ret;
147 if( !m_attr )
148 return ret;
150 const char *name = vformat_attribute_get_name(m_attr);
151 if( name )
152 ret = name;
153 return ret;
156 std::string vAttr::GetValue()
158 std::string ret;
160 if( m_attr ) {
161 if( vformat_attribute_is_single_valued(m_attr) ) {
162 ret = vformat_attribute_get_value(m_attr);
164 else {
165 // FIXME - does this ever happen?
166 ret = vformat_attribute_get_nth_value(m_attr, 0);
169 return ret;
172 std::string vAttr::GetParam(const char *name, int nth)
174 std::string ret;
176 if( !m_attr )
177 return ret;
179 VFormatParam *param = vformat_attribute_find_param(m_attr, name);
180 if( !param )
181 return ret;
183 const char *value = vformat_attribute_param_get_nth_value(param, nth);
184 if( value )
185 ret = value;
187 return ret;
191 const char *vCalendar::WeekDays[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" };
193 unsigned short vCalendar::GetWeekDayIndex(const char *dayname)
195 for( int i = 0; i < 7; i++ ) {
196 if( strcasecmp(dayname, WeekDays[i]) == 0 )
197 return i;
199 return 0;
202 bool vCalendar::HasMultipleVEvents() const
203 { int count = 0;
204 GList *attrs = m_format ? vformat_get_attributes(m_format) : 0;
205 for( ; attrs; attrs = attrs->next ) {
206 VFormatAttribute *attr = (VFormatAttribute*) attrs->data;
207 if( strcasecmp(vformat_attribute_get_name(attr), "BEGIN") == 0 &&
208 strcasecmp(vformat_attribute_get_nth_value(attr, 0), "VEVENT") == 0 )
210 count++;
213 return count > 1;
216 void vCalendar::RecurToVCal()
218 using namespace Barry;
219 using namespace std;
220 Barry::Calendar &cal = m_BarryCal;
222 if( !cal.Recurring )
223 return;
225 vAttrPtr attr = NewAttr("RRULE");
227 switch( cal.RecurringType )
229 case Calendar::Day: // eg. every day
230 AddParam(attr, "FREQ", "DAILY");
231 break;
233 case Calendar::MonthByDate: // eg. every month on the 12th
234 // see: DayOfMonth
235 AddParam(attr, "FREQ", "MONTHLY");
237 ostringstream oss;
238 oss << cal.DayOfMonth;
239 AddParam(attr, "BYMONTHDAY", oss.str().c_str());
241 break;
243 case Calendar::MonthByDay: // eg. every month on 3rd Wed
244 // see: DayOfWeek and WeekOfMonth
245 AddParam(attr, "FREQ", "MONTHLY");
246 if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned
247 ostringstream oss;
248 oss << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
249 AddParam(attr, "BYDAY", oss.str().c_str());
251 break;
253 case Calendar::YearByDate: // eg. every year on March 5
254 // see: DayOfMonth and MonthOfYear
255 AddParam(attr, "FREQ", "YEARLY");
257 ostringstream oss;
258 oss << cal.MonthOfYear;
259 AddParam(attr, "BYMONTH", oss.str().c_str());
262 ostringstream oss;
263 oss << cal.DayOfMonth;
264 AddParam(attr, "BYMONTHDAY", oss.str().c_str());
266 break;
268 case Calendar::YearByDay: // eg. every year on 3rd Wed of Jan
269 // see: DayOfWeek, WeekOfMonth, and
270 // MonthOfYear
271 AddParam(attr, "FREQ", "YEARLY");
272 if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned
273 ostringstream oss;
274 oss << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
275 AddParam(attr, "BYDAY", oss.str().c_str());
277 oss.str("");
278 oss << cal.MonthOfYear;
279 AddParam(attr, "BYMONTH", oss.str().c_str());
281 break;
283 case Calendar::Week: // eg. every week on Mon and Fri
284 // see: WeekDays
285 AddParam(attr, "FREQ", "WEEKLY");
287 ostringstream oss;
288 for( int i = 0, bm = 1, cnt = 0; i < 7; i++, bm <<= 1 ) {
289 if( cal.WeekDays & bm ) {
290 if( cnt )
291 oss << ",";
292 oss << WeekDays[i];
293 cnt++;
296 AddParam(attr, "BYDAY", oss.str().c_str());
298 break;
300 default:
301 throw ConvertError("Unknown RecurringType in Barry Calendar object");
304 // add some common parameters
305 if( cal.Interval > 1 ) {
306 ostringstream oss;
307 oss << cal.Interval;
308 AddParam(attr, "INTERVAL", oss.str().c_str());
310 if( !cal.Perpetual ) {
311 gStringPtr rend(osync_time_unix2vtime(&cal.RecurringEndTime));
312 AddParam(attr, "UNTIL", rend.Get());
315 AddAttr(attr);
318 bool AllDayEvent;
321 /// Recurring data
323 /// Note: interval can be used on all of these recurring types to
324 /// make it happen "every other time" or more, etc.
327 bool Recurring;
328 RecurringCodeType RecurringType;
329 unsigned short Interval; // must be >= 1
330 time_t RecurringEndTime; // only pertains if Recurring is true
331 // sets the date and time when
332 // recurrence of this appointment
333 // should no longer occur
334 // If a perpetual appointment, this
335 // is 0xFFFFFFFF in the low level data
336 // Instead, set the following flag.
337 bool Perpetual; // if true, this will always recur
338 unsigned short TimeZoneCode; // the time zone originally used
339 // for the recurrence data...
340 // seems to have little use, but
341 // set to your current time zone
342 // as a good default
344 unsigned short // recurring details, depending on type
345 DayOfWeek, // 0-6
346 WeekOfMonth, // 1-5
347 DayOfMonth, // 1-31
348 MonthOfYear; // 1-12
349 unsigned char WeekDays; // bitmask, bit 0 = sunday
351 #define CAL_WD_SUN 0x01
352 #define CAL_WD_MON 0x02
353 #define CAL_WD_TUE 0x04
354 #define CAL_WD_WED 0x08
355 #define CAL_WD_THU 0x10
356 #define CAL_WD_FRI 0x20
357 #define CAL_WD_SAT 0x40
363 void vCalendar::RecurToBarryCal()
365 // FIXME - needs to be implemented
367 // GetWeekDayIndex()
370 // Main conversion routine for converting from Barry::Calendar to
371 // a vCalendar string of data.
372 const std::string& vCalendar::ToVCal(const Barry::Calendar &cal)
374 Trace trace("vCalendar::ToVCal");
375 std::ostringstream oss;
376 cal.Dump(oss);
377 trace.logf("ToVCal, initial Barry record: %s", oss.str().c_str());
379 // start fresh
380 Clear();
381 m_format = vformat_new();
382 if( !m_format )
383 throw ConvertError("resource error allocating vformat");
385 // store the Barry object we're working with
386 m_BarryCal = cal;
388 // begin building vCalendar data
389 AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Calendar Record//EN"));
390 AddAttr(NewAttr("BEGIN", "VEVENT"));
391 AddAttr(NewAttr("SEQUENCE", "0"));
392 AddAttr(NewAttr("SUMMARY", cal.Subject.c_str()));
393 AddAttr(NewAttr("DESCRIPTION", cal.Notes.c_str()));
394 AddAttr(NewAttr("LOCATION", cal.Location.c_str()));
396 gStringPtr start(osync_time_unix2vtime(&cal.StartTime));
397 gStringPtr end(osync_time_unix2vtime(&cal.EndTime));
398 gStringPtr notify(osync_time_unix2vtime(&cal.NotificationTime));
400 AddAttr(NewAttr("DTSTART", start.Get()));
401 AddAttr(NewAttr("DTEND", end.Get()));
402 // FIXME - add a truly globally unique "UID" string?
405 AddAttr(NewAttr("BEGIN", "VALARM"));
406 AddAttr(NewAttr("ACTION", "AUDIO"));
408 // notify must be UTC, when specified in DATE-TIME
409 vAttrPtr trigger = NewAttr("TRIGGER", notify.Get());
410 AddParam(trigger, "VALUE", "DATE-TIME");
411 AddAttr(trigger);
413 AddAttr(NewAttr("END", "VALARM"));
416 if( cal.Recurring ) {
417 RecurToVCal();
420 AddAttr(NewAttr("END", "VEVENT"));
422 // generate the raw VCALENDAR data
423 m_gCalData = vformat_to_string(m_format, VFORMAT_EVENT_20);
424 m_vCalData = m_gCalData;
426 trace.logf("ToVCal, resulting vcal data: %s", m_vCalData.c_str());
427 return m_vCalData;
430 // Main conversion routine for converting from vCalendar data string
431 // to a Barry::Calendar object.
432 const Barry::Calendar& vCalendar::ToBarry(const char *vcal, uint32_t RecordId)
434 using namespace std;
436 Trace trace("vCalendar::ToBarry");
437 trace.logf("ToBarry, working on vcal data: %s", vcal);
439 // we only handle vCalendar data with one vevent block
440 if( HasMultipleVEvents() )
441 throw ConvertError("vCalendar data contains more than one VEVENT block, unsupported");
443 // start fresh
444 Clear();
446 // store the vCalendar raw data
447 m_vCalData = vcal;
449 // create format parser structures
450 m_format = vformat_new_from_string(vcal);
451 if( !m_format )
452 throw ConvertError("resource error allocating vformat");
454 string start = GetAttr("DTSTART");
455 trace.logf("DTSTART attr retrieved: %s", start.c_str());
456 string end = GetAttr("DTEND");
457 trace.logf("DTEND attr retrieved: %s", end.c_str());
458 string subject = GetAttr("SUMMARY");
459 trace.logf("SUMMARY attr retrieved: %s", subject.c_str());
460 if( subject.size() == 0 ) {
461 subject = "<blank subject>";
462 trace.logf("ERROR: bad data, blank SUMMARY: %s", vcal);
464 vAttr trigger_obj = GetAttrObj("TRIGGER");
469 // Now, run checks and convert into Barry object
473 // FIXME - we are assuming that any non-UTC timestamps
474 // in the vcalendar record will be in the current timezone...
475 // This is wrong! fix this later.
477 // Also, we current ignore any time zone
478 // parameters that might be in the vcalendar format... this
479 // must be fixed.
481 time_t now = time(NULL);
482 int zoneoffset = osync_time_timezone_diff(localtime(&now));
484 Barry::Calendar &rec = m_BarryCal;
485 rec.SetIds(Barry::Calendar::GetDefaultRecType(), RecordId);
487 if( !start.size() )
488 throw ConvertError("Blank DTSTART");
489 rec.StartTime = osync_time_vtime2unix(start.c_str(), zoneoffset);
491 if( !end.size() ) {
492 // DTEND is actually optional! According to the
493 // RFC, a DTSTART with no DTEND should be treated
494 // like a "special day" like an anniversary, which occupies
495 // no time.
497 // Since the Blackberry doesn't really map well to this
498 // case, we'll set the end time to 1 day past start.
500 rec.EndTime = rec.StartTime + 24 * 60 * 60;
502 else {
503 rec.EndTime = osync_time_vtime2unix(end.c_str(), zoneoffset);
506 rec.Subject = subject;
508 // convert trigger time into notification time
509 // assume no notification, by default
510 rec.NotificationTime = 0;
511 if( trigger_obj.Get() ) {
512 string trigger_type = trigger_obj.GetParam("VALUE");
513 string trigger = trigger_obj.GetValue();
515 if( trigger.size() == 0 ) {
516 trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
518 else if( trigger_type == "DATE-TIME" ) {
519 rec.NotificationTime = osync_time_vtime2unix(trigger.c_str(), zoneoffset);
521 else if( trigger_type == "DURATION" || trigger_type.size() == 0 ) {
522 // default is DURATION (RFC 4.8.6.3)
523 string related = trigger_obj.GetParam("RELATED");
525 // default to relative to start time
526 time_t *relative = &rec.StartTime;
527 if( related == "END" )
528 relative = &rec.EndTime;
530 rec.NotificationTime = *relative + osync_time_alarmdu2sec(trigger.c_str());
532 else {
533 throw ConvertError("Unknown TRIGGER VALUE");
536 else {
537 trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
541 return m_BarryCal;
544 // Transfers ownership of m_gCalData to the caller.
545 char* vCalendar::ExtractVCal()
547 char *ret = m_gCalData;
548 m_gCalData = 0;
549 return ret;
552 void vCalendar::Clear()
554 m_vCalData.clear();
555 m_BarryCal.Clear();
557 if( m_gCalData ) {
558 g_free(m_gCalData);
559 m_gCalData = 0;
562 if( m_format ) {
563 vformat_free(m_format);
564 m_format = 0;
570 //////////////////////////////////////////////////////////////////////////////
573 VEventConverter::VEventConverter()
574 : m_Data(0)
578 VEventConverter::VEventConverter(uint32_t newRecordId)
579 : m_Data(0),
580 m_RecordId(newRecordId)
584 VEventConverter::~VEventConverter()
586 if( m_Data )
587 g_free(m_Data);
590 // Transfers ownership of m_Data to the caller
591 char* VEventConverter::ExtractData()
593 Trace trace("VEventConverter::ExtractData");
594 char *ret = m_Data;
595 m_Data = 0;
596 return ret;
599 bool VEventConverter::ParseData(const char *data)
601 Trace trace("VEventConverter::ParseData");
603 try {
605 vCalendar vcal;
606 m_Cal = vcal.ToBarry(data, m_RecordId);
609 catch( vCalendar::ConvertError &ce ) {
610 trace.logf("ERROR: vCalendar::ConvertError exception: %s", ce.what());
611 return false;
614 return true;
617 // Barry storage operator
618 void VEventConverter::operator()(const Barry::Calendar &rec)
620 Trace trace("VEventConverter::operator()");
622 // Delete data if some already exists
623 if( m_Data ) {
624 g_free(m_Data);
625 m_Data = 0;
628 try {
630 vCalendar vcal;
631 vcal.ToVCal(rec);
632 m_Data = vcal.ExtractVCal();
635 catch( vCalendar::ConvertError &ce ) {
636 trace.logf("ERROR: vCalendar::ConvertError exception: %s", ce.what());
640 // Barry builder operator
641 bool VEventConverter::operator()(Barry::Calendar &rec, unsigned int dbId)
643 Trace trace("VEventConverter::builder operator()");
645 rec = m_Cal;
646 return true;
649 // Handles calling of the Barry::Controller to fetch a specific
650 // record, indicated by index (into the RecordStateTable).
651 // Returns a g_malloc'd string of data containing the vevent20
652 // data. It is the responsibility of the caller to free it.
653 // This is intended to be passed into the GetChanges() function.
654 char* VEventConverter::GetRecordData(BarryEnvironment *env, unsigned int dbId,
655 Barry::RecordStateTable::IndexType index)
657 Trace trace("VEventConverter::GetRecordData()");
659 using namespace Barry;
661 VEventConverter cal2event;
662 RecordParser<Calendar, VEventConverter> parser(cal2event);
663 env->m_pCon->GetRecord(dbId, index, parser);
664 return cal2event.ExtractData();
667 bool VEventConverter::CommitRecordData(BarryEnvironment *env, unsigned int dbId,
668 Barry::RecordStateTable::IndexType StateIndex, uint32_t recordId,
669 const char *data, bool add, std::string &errmsg)
671 Trace trace("VEventConverter::CommitRecordData()");
673 uint32_t newRecordId;
674 if( add ) {
675 // use given id if possible
676 if( recordId && !env->m_CalendarSync.m_Table.GetIndex(recordId) ) {
677 // recordId is unique and non-zero
678 newRecordId = recordId;
680 else {
681 trace.log("Can't use recommended recordId, generating new one.");
682 newRecordId = env->m_CalendarSync.m_Table.MakeNewRecordId();
685 else {
686 newRecordId = env->m_CalendarSync.m_Table.StateMap[StateIndex].RecordId;
688 trace.logf("newRecordId: %lu", newRecordId);
690 VEventConverter convert(newRecordId);
691 if( !convert.ParseData(data) ) {
692 std::ostringstream oss;
693 oss << "unable to parse change data for new RecordId: "
694 << newRecordId << " data: " << data;
695 errmsg = oss.str();
696 trace.logf(errmsg.c_str());
697 return false;
700 Barry::RecordBuilder<Barry::Calendar, VEventConverter> builder(convert);
702 if( add ) {
703 trace.log("adding record");
704 env->m_pCon->AddRecord(dbId, builder);
706 else {
707 trace.log("setting record");
708 env->m_pCon->SetRecord(dbId, StateIndex, builder);
709 trace.log("clearing dirty flag");
710 env->m_pCon->ClearDirty(dbId, StateIndex);
713 return true;