3 // Conversion routines for vevents (VCALENDAR, etc)
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.
23 #include "environment.h"
25 //#include "vformat.h" // comes from opensync, but not a public header yet
29 #include <opensync/format/opensync_time.h>
33 //////////////////////////////////////////////////////////////////////////////
36 vCalendar::vCalendar()
42 vCalendar::~vCalendar()
49 vformat_free(m_format);
53 vAttrPtr vCalendar::NewAttr(const char *name)
55 Trace trace("vCalendar::NewAttr");
57 trace.logf("creating valueless attr: %s", name);
59 vAttrPtr attr(vformat_attribute_new(NULL, name));
61 throw ConvertError("resource error allocating vformat attribute");
65 vAttrPtr vCalendar::NewAttr(const char *name, const char *value)
67 Trace trace("vCalendar::NewAttr");
69 if( strlen(value) == 0 ) {
70 trace.logf("attribute '%s' contains no data, skipping", name);
74 trace.logf("creating attr: %s, %s", name, value);
76 vAttrPtr attr(vformat_attribute_new(NULL, name));
78 throw ConvertError("resource error allocating vformat attribute");
80 vformat_attribute_add_value(attr.Get(), value);
84 void vCalendar::AddAttr(vAttrPtr attr)
86 Trace trace("vCalendar::AddAttr");
89 trace.log("attribute contains no data, skipping");
93 vformat_add_attribute(m_format, attr.Extract());
96 void vCalendar::AddParam(vAttrPtr &attr, const char *name, const char *value)
98 Trace trace("vCalendar::AddParam");
101 trace.log("attribute pointer contains no data, skipping");
104 if( strlen(value) == 0 ) {
105 trace.log("parameter value is empty, skipping");
109 VFormatParam *pParam = vformat_attribute_param_new(name);
110 vformat_attribute_param_add_value(pParam, value);
111 vformat_attribute_add_param(attr.Get(), pParam);
114 std::string vCalendar::GetAttr(const char *attrname)
116 Trace trace("vCalendar::GetAttr");
117 trace.logf("getting attr: %s", attrname);
121 VFormatAttribute *attr = vformat_find_attribute(m_format, attrname);
123 if( vformat_attribute_is_single_valued(attr) ) {
124 ret = vformat_attribute_get_value(attr);
127 // FIXME - does this ever happen?
128 ret = vformat_attribute_get_nth_value(attr, 0);
132 trace.logf("attr value: %s", ret.c_str());
138 const char *vCalendar::WeekDays[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" };
140 unsigned short vCalendar::GetWeekDayIndex(const char *dayname)
142 for( int i = 0; i < 7; i++ ) {
143 if( strcasecmp(dayname, WeekDays[i]) == 0 )
149 bool vCalendar::HasMultipleVEvents() const
152 GList *attrs = vformat_get_attributes(m_format);
153 for( ; attrs; attrs = attrs->next ) {
154 VFormatAttribute *attr = (VFormatAttribute*) attrs->data;
155 if( strcasecmp(vformat_attribute_get_name(attr), "BEGIN") == 0 &&
156 strcasecmp(vformat_attribute_get_nth_value(attr, 0), "VEVENT") == 0 )
164 void vCalendar::RecurToVCal()
166 using namespace Barry;
168 Barry::Calendar &cal = m_BarryCal;
173 vAttrPtr attr = NewAttr("RRULE");
175 switch( cal.RecurringType )
177 case Calendar::Day: // eg. every day
178 AddParam(attr, "FREQ", "DAILY");
181 case Calendar::MonthByDate: // eg. every month on the 12th
183 AddParam(attr, "FREQ", "MONTHLY");
186 oss << cal.DayOfMonth;
187 AddParam(attr, "BYMONTHDAY", oss.str().c_str());
191 case Calendar::MonthByDay: // eg. every month on 3rd Wed
192 // see: DayOfWeek and WeekOfMonth
193 AddParam(attr, "FREQ", "MONTHLY");
194 if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned
196 oss << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
197 AddParam(attr, "BYDAY", oss.str().c_str());
201 case Calendar::YearByDate: // eg. every year on March 5
202 // see: DayOfMonth and MonthOfYear
203 AddParam(attr, "FREQ", "YEARLY");
206 oss << cal.MonthOfYear;
207 AddParam(attr, "BYMONTH", oss.str().c_str());
211 oss << cal.DayOfMonth;
212 AddParam(attr, "BYMONTHDAY", oss.str().c_str());
216 case Calendar::YearByDay: // eg. every year on 3rd Wed of Jan
217 // see: DayOfWeek, WeekOfMonth, and
219 AddParam(attr, "FREQ", "YEARLY");
220 if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned
222 oss << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
223 AddParam(attr, "BYDAY", oss.str().c_str());
226 oss << cal.MonthOfYear;
227 AddParam(attr, "BYMONTH", oss.str().c_str());
231 case Calendar::Week: // eg. every week on Mon and Fri
233 AddParam(attr, "FREQ", "WEEKLY");
236 for( int i = 0, bm = 1, cnt = 0; i < 7; i++, bm <<= 1 ) {
237 if( cal.WeekDays & bm ) {
244 AddParam(attr, "BYDAY", oss.str().c_str());
249 throw ConvertError("Unknown RecurringType in Barry Calendar object");
252 // add some common parameters
253 if( cal.Interval > 1 ) {
256 AddParam(attr, "INTERVAL", oss.str().c_str());
258 if( !cal.Perpetual ) {
259 gStringPtr rend(osync_time_unix2vtime(&cal.RecurringEndTime));
260 AddParam(attr, "UNTIL", rend.Get());
268 // /// Recurring data
270 // /// Note: interval can be used on all of these recurring types to
271 // /// make it happen "every other time" or more, etc.
275 // RecurringCodeType RecurringType;
276 // unsigned short Interval; // must be >= 1
277 // time_t RecurringEndTime; // only pertains if Recurring is true
278 // // sets the date and time when
279 // // recurrence of this appointment
280 // // should no longer occur
281 // // If a perpetual appointment, this
282 // // is 0xFFFFFFFF in the low level data
283 // // Instead, set the following flag.
284 // bool Perpetual; // if true, this will always recur
285 // unsigned short TimeZoneCode; // the time zone originally used
286 // // for the recurrence data...
287 // // seems to have little use, but
288 // // set to your current time zone
289 // // as a good default
291 // unsigned short // recurring details, depending on type
293 // WeekOfMonth, // 1-5
294 // DayOfMonth, // 1-31
295 // MonthOfYear; // 1-12
296 // unsigned char WeekDays; // bitmask, bit 0 = sunday
298 // #define CAL_WD_SUN 0x01
299 // #define CAL_WD_MON 0x02
300 // #define CAL_WD_TUE 0x04
301 // #define CAL_WD_WED 0x08
302 // #define CAL_WD_THU 0x10
303 // #define CAL_WD_FRI 0x20
304 // #define CAL_WD_SAT 0x40
309 void vCalendar::RecurToBarryCal()
311 // FIXME - needs to be implemented
316 // Main conversion routine for converting from Barry::Calendar to
317 // a vCalendar string of data.
318 const std::string& vCalendar::ToVCal(const Barry::Calendar &cal)
322 m_format = vformat_new();
324 throw ConvertError("resource error allocating vformat");
326 // store the Barry object we're working with
329 // begin building vCalendar data
330 AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Calendar Record//EN"));
331 AddAttr(NewAttr("BEGIN", "VEVENT"));
332 AddAttr(NewAttr("SEQUENCE", "0"));
333 AddAttr(NewAttr("SUMMARY", cal.Subject.c_str()));
334 AddAttr(NewAttr("DESCRIPTION", cal.Notes.c_str()));
335 AddAttr(NewAttr("LOCATION", cal.Location.c_str()));
337 gStringPtr start(osync_time_unix2vtime(&cal.StartTime));
338 gStringPtr end(osync_time_unix2vtime(&cal.EndTime));
339 gStringPtr notify(osync_time_unix2vtime(&cal.NotificationTime));
341 AddAttr(NewAttr("DTSTART", start.Get()));
342 AddAttr(NewAttr("DTEND", end.Get()));
343 // FIXME - add a truly globally unique "UID" string?
346 AddAttr(NewAttr("BEGIN", "VALARM"));
347 AddAttr(NewAttr("ACTION", "AUDIO"));
349 // notify must be UTC, when specified in DATE-TIME
350 vAttrPtr trigger = NewAttr("TRIGGER", notify.Get());
351 AddParam(trigger, "VALUE", "DATE-TIME");
354 AddAttr(NewAttr("END", "VALARM"));
357 if( cal.Recurring ) {
361 AddAttr(NewAttr("END", "VEVENT"));
363 // generate the raw VCALENDAR data
364 m_gCalData = vformat_to_string(m_format, VFORMAT_EVENT_20);
365 m_vCalData = m_gCalData;
370 // Main conversion routine for converting from vCalendar data string
371 // to a Barry::Calendar object.
372 const Barry::Calendar& vCalendar::ToBarry(const char *vcal, uint32_t RecordId)
376 Trace trace("vCalendar::ToBarry");
378 // we only handle vCalendar data with one vevent block
379 if( HasMultipleVEvents() )
380 throw ConvertError("vCalendar data contains more than one VEVENT block, unsupported");
385 // store the vCalendar raw data
388 // create format parser structures
389 m_format = vformat_new_from_string(vcal);
391 throw ConvertError("resource error allocating vformat");
393 string start = GetAttr("DTSTART");
394 trace.logf("DTSTART attr retrieved: %s", start.c_str());
395 string end = GetAttr("DTEND");
396 trace.logf("DTEND attr retrieved: %s", end.c_str());
397 string subject = GetAttr("SUMMARY");
398 trace.logf("SUMMARY attr retrieved: %s", subject.c_str());
399 if( subject.size() == 0 ) {
400 subject = "<blank subject>";
401 trace.logf("ERROR: bad data, blank SUMMARY: %s", vcal);
404 if( !start.size() || !end.size() ) {
405 // FIXME - DTEND is actually optional! According to the
406 // RFC, a DTSTART with no DTEND should be treated
407 // like a "special day" like an anniversary, which occupies
409 throw ConvertError("Blank DTSTART or DTEND");
412 // FIXME - we are assuming that any non-UTC timestamps
413 // in the vcalendar record will be in the current timezone...
414 // This is wrong! fix this later.
416 // Also, we current ignore any time zone
417 // parameters that might be in the vcalendar format... this
420 time_t now = time(NULL);
421 int zoneoffset = osync_time_timezone_diff(localtime(&now));
423 Barry::Calendar &rec = m_BarryCal;
424 rec.SetIds(Barry::Calendar::GetDefaultRecType(), RecordId);
425 rec.StartTime = osync_time_vtime2unix(start.c_str(), zoneoffset);
426 rec.EndTime = osync_time_vtime2unix(end.c_str(), zoneoffset);
427 // FIXME - until notification time is supported, we assume 15 min
429 rec.NotificationTime = rec.StartTime - 15 * 60;
430 rec.Subject = subject;
435 // Transfers ownership of m_gCalData to the caller.
436 char* vCalendar::ExtractVCal()
438 char *ret = m_gCalData;
443 void vCalendar::Clear()
454 vformat_free(m_format);
462 //////////////////////////////////////////////////////////////////////////////
465 VEventConverter::VEventConverter()
470 VEventConverter::VEventConverter(uint32_t newRecordId
)
472 m_RecordId(newRecordId
)
476 VEventConverter::~VEventConverter()
482 // Transfers ownership of m_Data to the caller
483 char* VEventConverter::ExtractData()
485 Trace
trace("VEventConverter::ExtractData");
491 bool VEventConverter::ParseData(const char *data
)
493 Trace
trace("VEventConverter::ParseData");
499 m_Cal = vcal.ToBarry(data, m_RecordId);
502 catch( vCalendar::ConvertError &ce ) {
503 trace.logf("ERROR: vCalendar::ConvertError exception: %s", ce.what());
512 // Barry storage operator
513 void VEventConverter::operator()(const Barry::Calendar
&rec
)
515 Trace
trace("VEventConverter::operator()");
518 // Delete data if some already exists
528 m_Data = vcal.ExtractVCal();
531 catch( vCalendar::ConvertError &ce ) {
532 trace.logf("ERROR: vCalendar::ConvertError exception: %s", ce.what());
537 // Barry builder operator
538 bool VEventConverter::operator()(Barry::Calendar
&rec
, unsigned int dbId
)
540 Trace
trace("VEventConverter::builder operator()");
546 // Handles calling of the Barry::Controller to fetch a specific
547 // record, indicated by index (into the RecordStateTable).
548 // Returns a g_malloc'd string of data containing the vevent20
549 // data. It is the responsibility of the caller to free it.
550 // This is intended to be passed into the GetChanges() function.
551 char* VEventConverter::GetRecordData(BarryEnvironment
*env
, unsigned int dbId
,
552 Barry::RecordStateTable::IndexType index
)
554 Trace
trace("VEventConverter::GetRecordData()");
556 using namespace Barry
;
558 VEventConverter cal2event
;
559 RecordParser
<Calendar
, VEventConverter
> parser(cal2event
);
560 env
->m_pCon
->GetRecord(dbId
, index
, parser
);
561 return cal2event
.ExtractData();
564 bool VEventConverter::CommitRecordData(BarryEnvironment
*env
, unsigned int dbId
,
565 Barry::RecordStateTable::IndexType StateIndex
, uint32_t recordId
,
566 const char *data
, bool add
, std::string
&errmsg
)
568 Trace
trace("VEventConverter::CommitRecordData()");
570 uint32_t newRecordId
;
572 // use given id if possible
573 if( recordId
&& !env
->m_CalendarSync
.m_Table
.GetIndex(recordId
) ) {
574 // recordId is unique and non-zero
575 newRecordId
= recordId
;
578 trace
.log("Can't use recommended recordId, generating new one.");
579 newRecordId
= env
->m_CalendarSync
.m_Table
.MakeNewRecordId();
583 newRecordId
= env
->m_CalendarSync
.m_Table
.StateMap
[StateIndex
].RecordId
;
585 trace
.logf("newRecordId: %lu", newRecordId
);
587 VEventConverter
convert(newRecordId
);
588 if( !convert
.ParseData(data
) ) {
589 std::ostringstream oss
;
590 oss
<< "unable to parse change data for new RecordId: "
591 << newRecordId
<< " data: " << data
;
593 trace
.logf(errmsg
.c_str());
597 Barry::RecordBuilder
<Barry::Calendar
, VEventConverter
> builder(convert
);
600 trace
.log("adding record");
601 env
->m_pCon
->AddRecord(dbId
, builder
);
604 trace
.log("setting record");
605 env
->m_pCon
->SetRecord(dbId
, StateIndex
, builder
);
606 trace
.log("clearing dirty flag");
607 env
->m_pCon
->ClearDirty(dbId
, StateIndex
);