Fixed virtual function mis-name bug in btool's null parser
[barry/pauldeden.git] / opensync-plugin / src / vevent.cc
blobfa0b6bd3e15dd953de5b1f6c6ca1c9e5e9a67dfd
1 //
2 // \file vevent.cc
3 // Conversion routines for vevents (VCALENDAR, etc)
4 //
6 /*
7 Copyright (C) 2006-2008, 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 <strings.h>
29 #include <sstream>
32 //////////////////////////////////////////////////////////////////////////////
33 // vCalendar
35 vCalendar::vCalendar()
36 : m_gCalData(0)
40 vCalendar::~vCalendar()
42 if( m_gCalData ) {
43 g_free(m_gCalData);
47 const char *vCalendar::WeekDays[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" };
49 unsigned short vCalendar::GetWeekDayIndex(const char *dayname)
51 for( int i = 0; i < 7; i++ ) {
52 if( strcasecmp(dayname, WeekDays[i]) == 0 )
53 return i;
55 return 0;
58 bool vCalendar::HasMultipleVEvents() const
60 int count = 0;
61 b_VFormat *format = const_cast<b_VFormat*>(Format());
62 GList *attrs = format ? b_vformat_get_attributes(format) : 0;
63 for( ; attrs; attrs = attrs->next ) {
64 b_VFormatAttribute *attr = (b_VFormatAttribute*) attrs->data;
65 if( strcasecmp(b_vformat_attribute_get_name(attr), "BEGIN") == 0 &&
66 strcasecmp(b_vformat_attribute_get_nth_value(attr, 0), "VEVENT") == 0 )
68 count++;
71 return count > 1;
74 void vCalendar::RecurToVCal()
76 using namespace Barry;
77 using namespace std;
78 Barry::Calendar &cal = m_BarryCal;
80 if( !cal.Recurring )
81 return;
83 vAttrPtr attr = NewAttr("RRULE");
85 switch( cal.RecurringType )
87 case Calendar::Day: // eg. every day
88 AddParam(attr, "FREQ", "DAILY");
89 break;
91 case Calendar::MonthByDate: // eg. every month on the 12th
92 // see: DayOfMonth
93 AddParam(attr, "FREQ", "MONTHLY");
95 ostringstream oss;
96 oss << cal.DayOfMonth;
97 AddParam(attr, "BYMONTHDAY", oss.str().c_str());
99 break;
101 case Calendar::MonthByDay: // eg. every month on 3rd Wed
102 // see: DayOfWeek and WeekOfMonth
103 AddParam(attr, "FREQ", "MONTHLY");
104 if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned
105 ostringstream oss;
106 oss << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
107 AddParam(attr, "BYDAY", oss.str().c_str());
109 break;
111 case Calendar::YearByDate: // eg. every year on March 5
112 // see: DayOfMonth and MonthOfYear
113 AddParam(attr, "FREQ", "YEARLY");
115 ostringstream oss;
116 oss << cal.MonthOfYear;
117 AddParam(attr, "BYMONTH", oss.str().c_str());
120 ostringstream oss;
121 oss << cal.DayOfMonth;
122 AddParam(attr, "BYMONTHDAY", oss.str().c_str());
124 break;
126 case Calendar::YearByDay: // eg. every year on 3rd Wed of Jan
127 // see: DayOfWeek, WeekOfMonth, and
128 // MonthOfYear
129 AddParam(attr, "FREQ", "YEARLY");
130 if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned
131 ostringstream oss;
132 oss << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
133 AddParam(attr, "BYDAY", oss.str().c_str());
135 oss.str("");
136 oss << cal.MonthOfYear;
137 AddParam(attr, "BYMONTH", oss.str().c_str());
139 break;
141 case Calendar::Week: // eg. every week on Mon and Fri
142 // see: WeekDays
143 AddParam(attr, "FREQ", "WEEKLY");
145 ostringstream oss;
146 for( int i = 0, bm = 1, cnt = 0; i < 7; i++, bm <<= 1 ) {
147 if( cal.WeekDays & bm ) {
148 if( cnt )
149 oss << ",";
150 oss << WeekDays[i];
151 cnt++;
154 AddParam(attr, "BYDAY", oss.str().c_str());
156 break;
158 default:
159 throw ConvertError("Unknown RecurringType in Barry Calendar object");
162 // add some common parameters
163 if( cal.Interval > 1 ) {
164 ostringstream oss;
165 oss << cal.Interval;
166 AddParam(attr, "INTERVAL", oss.str().c_str());
168 if( !cal.Perpetual ) {
169 gStringPtr rend(osync_time_unix2vtime(&cal.RecurringEndTime));
170 AddParam(attr, "UNTIL", rend.Get());
173 AddAttr(attr);
176 bool AllDayEvent;
179 /// Recurring data
181 /// Note: interval can be used on all of these recurring types to
182 /// make it happen "every other time" or more, etc.
185 bool Recurring;
186 RecurringCodeType RecurringType;
187 unsigned short Interval; // must be >= 1
188 time_t RecurringEndTime; // only pertains if Recurring is true
189 // sets the date and time when
190 // recurrence of this appointment
191 // should no longer occur
192 // If a perpetual appointment, this
193 // is 0xFFFFFFFF in the low level data
194 // Instead, set the following flag.
195 bool Perpetual; // if true, this will always recur
196 unsigned short TimeZoneCode; // the time zone originally used
197 // for the recurrence data...
198 // seems to have little use, but
199 // set to your current time zone
200 // as a good default
202 unsigned short // recurring details, depending on type
203 DayOfWeek, // 0-6
204 WeekOfMonth, // 1-5
205 DayOfMonth, // 1-31
206 MonthOfYear; // 1-12
207 unsigned char WeekDays; // bitmask, bit 0 = sunday
209 #define CAL_WD_SUN 0x01
210 #define CAL_WD_MON 0x02
211 #define CAL_WD_TUE 0x04
212 #define CAL_WD_WED 0x08
213 #define CAL_WD_THU 0x10
214 #define CAL_WD_FRI 0x20
215 #define CAL_WD_SAT 0x40
221 void vCalendar::RecurToBarryCal()
223 // FIXME - needs to be implemented
225 // GetWeekDayIndex()
228 // Main conversion routine for converting from Barry::Calendar to
229 // a vCalendar string of data.
230 const std::string& vCalendar::ToVCal(const Barry::Calendar &cal)
232 Trace trace("vCalendar::ToVCal");
233 std::ostringstream oss;
234 cal.Dump(oss);
235 trace.logf("ToVCal, initial Barry record: %s", oss.str().c_str());
237 // start fresh
238 Clear();
239 SetFormat( b_vformat_new() );
240 if( !Format() )
241 throw ConvertError("resource error allocating vformat");
243 // store the Barry object we're working with
244 m_BarryCal = cal;
246 // begin building vCalendar data
247 AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Calendar Record//EN"));
248 AddAttr(NewAttr("BEGIN", "VEVENT"));
249 AddAttr(NewAttr("SEQUENCE", "0"));
250 AddAttr(NewAttr("SUMMARY", cal.Subject.c_str()));
251 AddAttr(NewAttr("DESCRIPTION", cal.Notes.c_str()));
252 AddAttr(NewAttr("LOCATION", cal.Location.c_str()));
254 gStringPtr start(osync_time_unix2vtime(&cal.StartTime));
255 gStringPtr end(osync_time_unix2vtime(&cal.EndTime));
256 gStringPtr notify(osync_time_unix2vtime(&cal.NotificationTime));
258 AddAttr(NewAttr("DTSTART", start.Get()));
259 AddAttr(NewAttr("DTEND", end.Get()));
260 // FIXME - add a truly globally unique "UID" string?
263 AddAttr(NewAttr("BEGIN", "VALARM"));
264 AddAttr(NewAttr("ACTION", "AUDIO"));
266 // notify must be UTC, when specified in DATE-TIME
267 vAttrPtr trigger = NewAttr("TRIGGER", notify.Get());
268 AddParam(trigger, "VALUE", "DATE-TIME");
269 AddAttr(trigger);
271 AddAttr(NewAttr("END", "VALARM"));
274 if( cal.Recurring ) {
275 RecurToVCal();
278 AddAttr(NewAttr("END", "VEVENT"));
280 // generate the raw VCALENDAR data
281 m_gCalData = b_vformat_to_string(Format(), VFORMAT_EVENT_20);
282 m_vCalData = m_gCalData;
284 trace.logf("ToVCal, resulting vcal data: %s", m_vCalData.c_str());
285 return m_vCalData;
288 // Main conversion routine for converting from vCalendar data string
289 // to a Barry::Calendar object.
290 const Barry::Calendar& vCalendar::ToBarry(const char *vcal, uint32_t RecordId)
292 using namespace std;
294 Trace trace("vCalendar::ToBarry");
295 trace.logf("ToBarry, working on vcal data: %s", vcal);
297 // we only handle vCalendar data with one vevent block
298 if( HasMultipleVEvents() )
299 throw ConvertError("vCalendar data contains more than one VEVENT block, unsupported");
301 // start fresh
302 Clear();
304 // store the vCalendar raw data
305 m_vCalData = vcal;
307 // create format parser structures
308 SetFormat( b_vformat_new_from_string(vcal) );
309 if( !Format() )
310 throw ConvertError("resource error allocating vformat");
312 string start = GetAttr("DTSTART", "/vevent");
313 trace.logf("DTSTART attr retrieved: %s", start.c_str());
314 string end = GetAttr("DTEND", "/vevent");
315 trace.logf("DTEND attr retrieved: %s", end.c_str());
316 string subject = GetAttr("SUMMARY", "/vevent");
317 trace.logf("SUMMARY attr retrieved: %s", subject.c_str());
318 if( subject.size() == 0 ) {
319 subject = "<blank subject>";
320 trace.logf("ERROR: bad data, blank SUMMARY: %s", vcal);
322 vAttr trigger_obj = GetAttrObj("TRIGGER", 0, "/valarm");
327 // Now, run checks and convert into Barry object
331 // FIXME - we are assuming that any non-UTC timestamps
332 // in the vcalendar record will be in the current timezone...
333 // This is wrong! fix this later.
335 // Also, we current ignore any time zone
336 // parameters that might be in the vcalendar format... this
337 // must be fixed.
339 time_t now = time(NULL);
340 int zoneoffset = osync_time_timezone_diff(localtime(&now));
342 Barry::Calendar &rec = m_BarryCal;
343 rec.SetIds(Barry::Calendar::GetDefaultRecType(), RecordId);
345 if( !start.size() )
346 throw ConvertError("Blank DTSTART");
347 rec.StartTime = osync_time_vtime2unix(start.c_str(), zoneoffset);
349 if( !end.size() ) {
350 // DTEND is actually optional! According to the
351 // RFC, a DTSTART with no DTEND should be treated
352 // like a "special day" like an anniversary, which occupies
353 // no time.
355 // Since the Blackberry doesn't really map well to this
356 // case, we'll set the end time to 1 day past start.
358 rec.EndTime = rec.StartTime + 24 * 60 * 60;
360 else {
361 rec.EndTime = osync_time_vtime2unix(end.c_str(), zoneoffset);
364 rec.Subject = subject;
366 // convert trigger time into notification time
367 // assume no notification, by default
368 rec.NotificationTime = 0;
369 if( trigger_obj.Get() ) {
370 string trigger_type = trigger_obj.GetParam("VALUE");
371 string trigger = trigger_obj.GetValue();
373 if( trigger.size() == 0 ) {
374 trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
376 else if( trigger_type == "DATE-TIME" ) {
377 rec.NotificationTime = osync_time_vtime2unix(trigger.c_str(), zoneoffset);
379 else if( trigger_type == "DURATION" || trigger_type.size() == 0 ) {
380 // default is DURATION (RFC 4.8.6.3)
381 string related = trigger_obj.GetParam("RELATED");
383 // default to relative to start time
384 time_t *relative = &rec.StartTime;
385 if( related == "END" )
386 relative = &rec.EndTime;
388 rec.NotificationTime = *relative + osync_time_alarmdu2sec(trigger.c_str());
390 else {
391 throw ConvertError("Unknown TRIGGER VALUE");
394 else {
395 trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
398 std::ostringstream oss;
399 m_BarryCal.Dump(oss);
400 trace.logf("ToBarry, resulting Barry record: %s", oss.str().c_str());
401 return m_BarryCal;
404 // Transfers ownership of m_gCalData to the caller.
405 char* vCalendar::ExtractVCal()
407 char *ret = m_gCalData;
408 m_gCalData = 0;
409 return ret;
412 void vCalendar::Clear()
414 vBase::Clear();
415 m_vCalData.clear();
416 m_BarryCal.Clear();
418 if( m_gCalData ) {
419 g_free(m_gCalData);
420 m_gCalData = 0;
426 //////////////////////////////////////////////////////////////////////////////
429 VEventConverter::VEventConverter()
430 : m_Data(0)
434 VEventConverter::VEventConverter(uint32_t newRecordId)
435 : m_Data(0),
436 m_RecordId(newRecordId)
440 VEventConverter::~VEventConverter()
442 if( m_Data )
443 g_free(m_Data);
446 // Transfers ownership of m_Data to the caller
447 char* VEventConverter::ExtractData()
449 Trace trace("VEventConverter::ExtractData");
450 char *ret = m_Data;
451 m_Data = 0;
452 return ret;
455 bool VEventConverter::ParseData(const char *data)
457 Trace trace("VEventConverter::ParseData");
459 try {
461 vCalendar vcal;
462 m_Cal = vcal.ToBarry(data, m_RecordId);
465 catch( vCalendar::ConvertError &ce ) {
466 trace.logf("ERROR: vCalendar::ConvertError exception: %s", ce.what());
467 return false;
470 return true;
473 // Barry storage operator
474 void VEventConverter::operator()(const Barry::Calendar &rec)
476 Trace trace("VEventConverter::operator()");
478 // Delete data if some already exists
479 if( m_Data ) {
480 g_free(m_Data);
481 m_Data = 0;
484 try {
486 vCalendar vcal;
487 vcal.ToVCal(rec);
488 m_Data = vcal.ExtractVCal();
491 catch( vCalendar::ConvertError &ce ) {
492 trace.logf("ERROR: vCalendar::ConvertError exception: %s", ce.what());
496 // Barry builder operator
497 bool VEventConverter::operator()(Barry::Calendar &rec, unsigned int dbId)
499 Trace trace("VEventConverter::builder operator()");
501 rec = m_Cal;
502 return true;
505 // Handles calling of the Barry::Controller to fetch a specific
506 // record, indicated by index (into the RecordStateTable).
507 // Returns a g_malloc'd string of data containing the vevent20
508 // data. It is the responsibility of the caller to free it.
509 // This is intended to be passed into the GetChanges() function.
510 char* VEventConverter::GetRecordData(BarryEnvironment *env, unsigned int dbId,
511 Barry::RecordStateTable::IndexType index)
513 Trace trace("VEventConverter::GetRecordData()");
515 using namespace Barry;
517 VEventConverter cal2event;
518 RecordParser<Calendar, VEventConverter> parser(cal2event);
519 env->m_pDesktop->GetRecord(dbId, index, parser);
520 return cal2event.ExtractData();
523 bool VEventConverter::CommitRecordData(BarryEnvironment *env, unsigned int dbId,
524 Barry::RecordStateTable::IndexType StateIndex, uint32_t recordId,
525 const char *data, bool add, std::string &errmsg)
527 Trace trace("VEventConverter::CommitRecordData()");
529 uint32_t newRecordId;
530 if( add ) {
531 // use given id if possible
532 if( recordId && !env->m_CalendarSync.m_Table.GetIndex(recordId) ) {
533 // recordId is unique and non-zero
534 newRecordId = recordId;
536 else {
537 trace.log("Can't use recommended recordId, generating new one.");
538 newRecordId = env->m_CalendarSync.m_Table.MakeNewRecordId();
541 else {
542 newRecordId = env->m_CalendarSync.m_Table.StateMap[StateIndex].RecordId;
544 trace.logf("newRecordId: %lu", newRecordId);
546 VEventConverter convert(newRecordId);
547 if( !convert.ParseData(data) ) {
548 std::ostringstream oss;
549 oss << "unable to parse change data for new RecordId: "
550 << newRecordId << " data: " << data;
551 errmsg = oss.str();
552 trace.logf(errmsg.c_str());
553 return false;
556 Barry::RecordBuilder<Barry::Calendar, VEventConverter> builder(convert);
558 if( add ) {
559 trace.log("adding record");
560 env->m_pDesktop->AddRecord(dbId, builder);
562 else {
563 trace.log("setting record");
564 env->m_pDesktop->SetRecord(dbId, StateIndex, builder);
565 trace.log("clearing dirty flag");
566 env->m_pDesktop->ClearDirty(dbId, StateIndex);
569 return true;