3 // Conversion routines for vevents (VCALENDAR, etc)
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.
23 #include "environment.h"
25 #include "vformat.h" // comes from opensync, but not a public header yet
32 //////////////////////////////////////////////////////////////////////////////
35 vCalendar::vCalendar()
40 vCalendar::~vCalendar()
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 )
58 bool vCalendar::HasMultipleVEvents() const
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 )
74 void vCalendar::RecurToVCal()
76 using namespace Barry
;
78 Barry::Calendar
&cal
= m_BarryCal
;
83 vAttrPtr attr
= NewAttr("RRULE");
85 switch( cal
.RecurringType
)
87 case Calendar::Day
: // eg. every day
88 AddParam(attr
, "FREQ", "DAILY");
91 case Calendar::MonthByDate
: // eg. every month on the 12th
93 AddParam(attr
, "FREQ", "MONTHLY");
96 oss
<< cal
.DayOfMonth
;
97 AddParam(attr
, "BYMONTHDAY", oss
.str().c_str());
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
106 oss
<< cal
.WeekOfMonth
<< WeekDays
[cal
.DayOfWeek
];
107 AddParam(attr
, "BYDAY", oss
.str().c_str());
111 case Calendar::YearByDate
: // eg. every year on March 5
112 // see: DayOfMonth and MonthOfYear
113 AddParam(attr
, "FREQ", "YEARLY");
116 oss
<< cal
.MonthOfYear
;
117 AddParam(attr
, "BYMONTH", oss
.str().c_str());
121 oss
<< cal
.DayOfMonth
;
122 AddParam(attr
, "BYMONTHDAY", oss
.str().c_str());
126 case Calendar::YearByDay
: // eg. every year on 3rd Wed of Jan
127 // see: DayOfWeek, WeekOfMonth, and
129 AddParam(attr
, "FREQ", "YEARLY");
130 if( cal
.DayOfWeek
<= 6 ) { // DayOfWeek is unsigned
132 oss
<< cal
.WeekOfMonth
<< WeekDays
[cal
.DayOfWeek
];
133 AddParam(attr
, "BYDAY", oss
.str().c_str());
136 oss
<< cal
.MonthOfYear
;
137 AddParam(attr
, "BYMONTH", oss
.str().c_str());
141 case Calendar::Week
: // eg. every week on Mon and Fri
143 AddParam(attr
, "FREQ", "WEEKLY");
146 for( int i
= 0, bm
= 1, cnt
= 0; i
< 7; i
++, bm
<<= 1 ) {
147 if( cal
.WeekDays
& bm
) {
154 AddParam(attr
, "BYDAY", oss
.str().c_str());
159 throw ConvertError("Unknown RecurringType in Barry Calendar object");
162 // add some common parameters
163 if( cal
.Interval
> 1 ) {
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());
181 /// Note: interval can be used on all of these recurring types to
182 /// make it happen "every other time" or more, etc.
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
202 unsigned short // recurring details, depending on type
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
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
;
235 trace
.logf("ToVCal, initial Barry record: %s", oss
.str().c_str());
239 SetFormat( b_vformat_new() );
241 throw ConvertError("resource error allocating vformat");
243 // store the Barry object we're working with
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");
271 AddAttr(NewAttr("END", "VALARM"));
274 if( cal
.Recurring
) {
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());
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
)
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");
304 // store the vCalendar raw data
307 // create format parser structures
308 SetFormat( b_vformat_new_from_string(vcal
) );
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
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
);
346 throw ConvertError("Blank DTSTART");
347 rec
.StartTime
= osync_time_vtime2unix(start
.c_str(), zoneoffset
);
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
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;
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());
391 throw ConvertError("Unknown TRIGGER VALUE");
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());
404 // Transfers ownership of m_gCalData to the caller.
405 char* vCalendar::ExtractVCal()
407 char *ret
= m_gCalData
;
412 void vCalendar::Clear()
426 //////////////////////////////////////////////////////////////////////////////
429 VEventConverter::VEventConverter()
434 VEventConverter::VEventConverter(uint32_t newRecordId
)
436 m_RecordId(newRecordId
)
440 VEventConverter::~VEventConverter()
446 // Transfers ownership of m_Data to the caller
447 char* VEventConverter::ExtractData()
449 Trace
trace("VEventConverter::ExtractData");
455 bool VEventConverter::ParseData(const char *data
)
457 Trace
trace("VEventConverter::ParseData");
462 m_Cal
= vcal
.ToBarry(data
, m_RecordId
);
465 catch( vCalendar::ConvertError
&ce
) {
466 trace
.logf("ERROR: vCalendar::ConvertError exception: %s", ce
.what());
473 // Barry storage operator
474 void VEventConverter::operator()(const Barry::Calendar
&rec
)
476 Trace
trace("VEventConverter::operator()");
478 // Delete data if some already exists
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()");
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
;
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
;
537 trace
.log("Can't use recommended recordId, generating new one.");
538 newRecordId
= env
->m_CalendarSync
.m_Table
.MakeNewRecordId();
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
;
552 trace
.logf(errmsg
.c_str());
556 Barry::RecordBuilder
<Barry::Calendar
, VEventConverter
> builder(convert
);
559 trace
.log("adding record");
560 env
->m_pDesktop
->AddRecord(dbId
, builder
);
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
);