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
31 //////////////////////////////////////////////////////////////////////////////
34 vCalendar::vCalendar()
40 vCalendar::~vCalendar()
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
));
59 throw ConvertError("resource error allocating vformat attribute");
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
);
72 trace
.logf("creating attr: %s, %s", name
, value
);
74 vAttrPtr
attr(vformat_attribute_new(NULL
, name
));
76 throw ConvertError("resource error allocating vformat attribute");
78 vformat_attribute_add_value(attr
.Get(), value
);
82 void vCalendar::AddAttr(vAttrPtr attr
)
84 Trace
trace("vCalendar::AddAttr");
87 trace
.log("attribute contains no data, skipping");
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");
99 trace
.log("attribute pointer contains no data, skipping");
102 if( strlen(value
) == 0 ) {
103 trace
.log("parameter value is empty, skipping");
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
);
119 VFormatAttribute
*attr
= vformat_find_attribute(m_format
, attrname
);
121 if( vformat_attribute_is_single_valued(attr
) ) {
122 ret
= vformat_attribute_get_value(attr
);
125 // FIXME - does this ever happen?
126 ret
= vformat_attribute_get_nth_value(attr
, 0);
130 trace
.logf("attr value: %s", ret
.c_str());
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()
150 const char *name
= vformat_attribute_get_name(m_attr
);
156 std::string
vAttr::GetValue()
161 if( vformat_attribute_is_single_valued(m_attr
) ) {
162 ret
= vformat_attribute_get_value(m_attr
);
165 // FIXME - does this ever happen?
166 ret
= vformat_attribute_get_nth_value(m_attr
, 0);
172 std::string
vAttr::GetParam(const char *name
, int nth
)
179 VFormatParam
*param
= vformat_attribute_find_param(m_attr
, name
);
183 const char *value
= vformat_attribute_param_get_nth_value(param
, nth
);
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 )
202 bool vCalendar::HasMultipleVEvents() const
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 )
216 void vCalendar::RecurToVCal()
218 using namespace Barry
;
220 Barry::Calendar
&cal
= m_BarryCal
;
225 vAttrPtr attr
= NewAttr("RRULE");
227 switch( cal
.RecurringType
)
229 case Calendar::Day
: // eg. every day
230 AddParam(attr
, "FREQ", "DAILY");
233 case Calendar::MonthByDate
: // eg. every month on the 12th
235 AddParam(attr
, "FREQ", "MONTHLY");
238 oss
<< cal
.DayOfMonth
;
239 AddParam(attr
, "BYMONTHDAY", oss
.str().c_str());
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
248 oss
<< cal
.WeekOfMonth
<< WeekDays
[cal
.DayOfWeek
];
249 AddParam(attr
, "BYDAY", oss
.str().c_str());
253 case Calendar::YearByDate
: // eg. every year on March 5
254 // see: DayOfMonth and MonthOfYear
255 AddParam(attr
, "FREQ", "YEARLY");
258 oss
<< cal
.MonthOfYear
;
259 AddParam(attr
, "BYMONTH", oss
.str().c_str());
263 oss
<< cal
.DayOfMonth
;
264 AddParam(attr
, "BYMONTHDAY", oss
.str().c_str());
268 case Calendar::YearByDay
: // eg. every year on 3rd Wed of Jan
269 // see: DayOfWeek, WeekOfMonth, and
271 AddParam(attr
, "FREQ", "YEARLY");
272 if( cal
.DayOfWeek
<= 6 ) { // DayOfWeek is unsigned
274 oss
<< cal
.WeekOfMonth
<< WeekDays
[cal
.DayOfWeek
];
275 AddParam(attr
, "BYDAY", oss
.str().c_str());
278 oss
<< cal
.MonthOfYear
;
279 AddParam(attr
, "BYMONTH", oss
.str().c_str());
283 case Calendar::Week
: // eg. every week on Mon and Fri
285 AddParam(attr
, "FREQ", "WEEKLY");
288 for( int i
= 0, bm
= 1, cnt
= 0; i
< 7; i
++, bm
<<= 1 ) {
289 if( cal
.WeekDays
& bm
) {
296 AddParam(attr
, "BYDAY", oss
.str().c_str());
301 throw ConvertError("Unknown RecurringType in Barry Calendar object");
304 // add some common parameters
305 if( cal
.Interval
> 1 ) {
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());
323 /// Note: interval can be used on all of these recurring types to
324 /// make it happen "every other time" or more, etc.
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
344 unsigned short // recurring details, depending on type
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
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
;
377 trace
.logf("ToVCal, initial Barry record: %s", oss
.str().c_str());
381 m_format
= vformat_new();
383 throw ConvertError("resource error allocating vformat");
385 // store the Barry object we're working with
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");
413 AddAttr(NewAttr("END", "VALARM"));
416 if( cal
.Recurring
) {
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());
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
)
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");
446 // store the vCalendar raw data
449 // create format parser structures
450 m_format
= vformat_new_from_string(vcal
);
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
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
);
488 throw ConvertError("Blank DTSTART");
489 rec
.StartTime
= osync_time_vtime2unix(start
.c_str(), zoneoffset
);
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
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;
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());
533 throw ConvertError("Unknown TRIGGER VALUE");
537 trace
.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
544 // Transfers ownership of m_gCalData to the caller.
545 char* vCalendar::ExtractVCal()
547 char *ret
= m_gCalData
;
552 void vCalendar::Clear()
563 vformat_free(m_format
);
570 //////////////////////////////////////////////////////////////////////////////
573 VEventConverter::VEventConverter()
578 VEventConverter::VEventConverter(uint32_t newRecordId
)
580 m_RecordId(newRecordId
)
584 VEventConverter::~VEventConverter()
590 // Transfers ownership of m_Data to the caller
591 char* VEventConverter::ExtractData()
593 Trace
trace("VEventConverter::ExtractData");
599 bool VEventConverter::ParseData(const char *data
)
601 Trace
trace("VEventConverter::ParseData");
606 m_Cal
= vcal
.ToBarry(data
, m_RecordId
);
609 catch( vCalendar::ConvertError
&ce
) {
610 trace
.logf("ERROR: vCalendar::ConvertError exception: %s", ce
.what());
617 // Barry storage operator
618 void VEventConverter::operator()(const Barry::Calendar
&rec
)
620 Trace
trace("VEventConverter::operator()");
622 // Delete data if some already exists
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()");
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
;
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
;
681 trace
.log("Can't use recommended recordId, generating new one.");
682 newRecordId
= env
->m_CalendarSync
.m_Table
.MakeNewRecordId();
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
;
696 trace
.logf(errmsg
.c_str());
700 Barry::RecordBuilder
<Barry::Calendar
, VEventConverter
> builder(convert
);
703 trace
.log("adding record");
704 env
->m_pCon
->AddRecord(dbId
, builder
);
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
);