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()
39 vCalendar::~vCalendar()
46 const char *vCalendar::WeekDays
[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" };
48 unsigned short vCalendar::GetWeekDayIndex(const char *dayname
)
50 for( int i
= 0; i
< 7; i
++ ) {
51 if( strcasecmp(dayname
, WeekDays
[i
]) == 0 )
57 bool vCalendar::HasMultipleVEvents() const
60 VFormat
*format
= const_cast<VFormat
*>(Format());
61 GList
*attrs
= format
? vformat_get_attributes(format
) : 0;
62 for( ; attrs
; attrs
= attrs
->next
) {
63 VFormatAttribute
*attr
= (VFormatAttribute
*) attrs
->data
;
64 if( strcasecmp(vformat_attribute_get_name(attr
), "BEGIN") == 0 &&
65 strcasecmp(vformat_attribute_get_nth_value(attr
, 0), "VEVENT") == 0 )
73 void vCalendar::RecurToVCal()
75 using namespace Barry
;
77 Barry::Calendar
&cal
= m_BarryCal
;
82 vAttrPtr attr
= NewAttr("RRULE");
84 switch( cal
.RecurringType
)
86 case Calendar::Day
: // eg. every day
87 AddParam(attr
, "FREQ", "DAILY");
90 case Calendar::MonthByDate
: // eg. every month on the 12th
92 AddParam(attr
, "FREQ", "MONTHLY");
95 oss
<< cal
.DayOfMonth
;
96 AddParam(attr
, "BYMONTHDAY", oss
.str().c_str());
100 case Calendar::MonthByDay
: // eg. every month on 3rd Wed
101 // see: DayOfWeek and WeekOfMonth
102 AddParam(attr
, "FREQ", "MONTHLY");
103 if( cal
.DayOfWeek
<= 6 ) { // DayOfWeek is unsigned
105 oss
<< cal
.WeekOfMonth
<< WeekDays
[cal
.DayOfWeek
];
106 AddParam(attr
, "BYDAY", oss
.str().c_str());
110 case Calendar::YearByDate
: // eg. every year on March 5
111 // see: DayOfMonth and MonthOfYear
112 AddParam(attr
, "FREQ", "YEARLY");
115 oss
<< cal
.MonthOfYear
;
116 AddParam(attr
, "BYMONTH", oss
.str().c_str());
120 oss
<< cal
.DayOfMonth
;
121 AddParam(attr
, "BYMONTHDAY", oss
.str().c_str());
125 case Calendar::YearByDay
: // eg. every year on 3rd Wed of Jan
126 // see: DayOfWeek, WeekOfMonth, and
128 AddParam(attr
, "FREQ", "YEARLY");
129 if( cal
.DayOfWeek
<= 6 ) { // DayOfWeek is unsigned
131 oss
<< cal
.WeekOfMonth
<< WeekDays
[cal
.DayOfWeek
];
132 AddParam(attr
, "BYDAY", oss
.str().c_str());
135 oss
<< cal
.MonthOfYear
;
136 AddParam(attr
, "BYMONTH", oss
.str().c_str());
140 case Calendar::Week
: // eg. every week on Mon and Fri
142 AddParam(attr
, "FREQ", "WEEKLY");
145 for( int i
= 0, bm
= 1, cnt
= 0; i
< 7; i
++, bm
<<= 1 ) {
146 if( cal
.WeekDays
& bm
) {
153 AddParam(attr
, "BYDAY", oss
.str().c_str());
158 throw ConvertError("Unknown RecurringType in Barry Calendar object");
161 // add some common parameters
162 if( cal
.Interval
> 1 ) {
165 AddParam(attr
, "INTERVAL", oss
.str().c_str());
167 if( !cal
.Perpetual
) {
168 gStringPtr
rend(osync_time_unix2vtime(&cal
.RecurringEndTime
));
169 AddParam(attr
, "UNTIL", rend
.Get());
180 /// Note: interval can be used on all of these recurring types to
181 /// make it happen "every other time" or more, etc.
185 RecurringCodeType RecurringType;
186 unsigned short Interval; // must be >= 1
187 time_t RecurringEndTime; // only pertains if Recurring is true
188 // sets the date and time when
189 // recurrence of this appointment
190 // should no longer occur
191 // If a perpetual appointment, this
192 // is 0xFFFFFFFF in the low level data
193 // Instead, set the following flag.
194 bool Perpetual; // if true, this will always recur
195 unsigned short TimeZoneCode; // the time zone originally used
196 // for the recurrence data...
197 // seems to have little use, but
198 // set to your current time zone
201 unsigned short // recurring details, depending on type
206 unsigned char WeekDays; // bitmask, bit 0 = sunday
208 #define CAL_WD_SUN 0x01
209 #define CAL_WD_MON 0x02
210 #define CAL_WD_TUE 0x04
211 #define CAL_WD_WED 0x08
212 #define CAL_WD_THU 0x10
213 #define CAL_WD_FRI 0x20
214 #define CAL_WD_SAT 0x40
220 void vCalendar::RecurToBarryCal()
222 // FIXME - needs to be implemented
227 // Main conversion routine for converting from Barry::Calendar to
228 // a vCalendar string of data.
229 const std::string
& vCalendar::ToVCal(const Barry::Calendar
&cal
)
231 Trace
trace("vCalendar::ToVCal");
232 std::ostringstream oss
;
234 trace
.logf("ToVCal, initial Barry record: %s", oss
.str().c_str());
238 SetFormat( vformat_new() );
240 throw ConvertError("resource error allocating vformat");
242 // store the Barry object we're working with
245 // begin building vCalendar data
246 AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Calendar Record//EN"));
247 AddAttr(NewAttr("BEGIN", "VEVENT"));
248 AddAttr(NewAttr("SEQUENCE", "0"));
249 AddAttr(NewAttr("SUMMARY", cal
.Subject
.c_str()));
250 AddAttr(NewAttr("DESCRIPTION", cal
.Notes
.c_str()));
251 AddAttr(NewAttr("LOCATION", cal
.Location
.c_str()));
253 gStringPtr
start(osync_time_unix2vtime(&cal
.StartTime
));
254 gStringPtr
end(osync_time_unix2vtime(&cal
.EndTime
));
255 gStringPtr
notify(osync_time_unix2vtime(&cal
.NotificationTime
));
257 AddAttr(NewAttr("DTSTART", start
.Get()));
258 AddAttr(NewAttr("DTEND", end
.Get()));
259 // FIXME - add a truly globally unique "UID" string?
262 AddAttr(NewAttr("BEGIN", "VALARM"));
263 AddAttr(NewAttr("ACTION", "AUDIO"));
265 // notify must be UTC, when specified in DATE-TIME
266 vAttrPtr trigger
= NewAttr("TRIGGER", notify
.Get());
267 AddParam(trigger
, "VALUE", "DATE-TIME");
270 AddAttr(NewAttr("END", "VALARM"));
273 if( cal
.Recurring
) {
277 AddAttr(NewAttr("END", "VEVENT"));
279 // generate the raw VCALENDAR data
280 m_gCalData
= vformat_to_string(Format(), VFORMAT_EVENT_20
);
281 m_vCalData
= m_gCalData
;
283 trace
.logf("ToVCal, resulting vcal data: %s", m_vCalData
.c_str());
287 // Main conversion routine for converting from vCalendar data string
288 // to a Barry::Calendar object.
289 const Barry::Calendar
& vCalendar::ToBarry(const char *vcal
, uint32_t RecordId
)
293 Trace
trace("vCalendar::ToBarry");
294 trace
.logf("ToBarry, working on vcal data: %s", vcal
);
296 // we only handle vCalendar data with one vevent block
297 if( HasMultipleVEvents() )
298 throw ConvertError("vCalendar data contains more than one VEVENT block, unsupported");
303 // store the vCalendar raw data
306 // create format parser structures
307 SetFormat( vformat_new_from_string(vcal
) );
309 throw ConvertError("resource error allocating vformat");
311 string start
= GetAttr("DTSTART", "/vevent");
312 trace
.logf("DTSTART attr retrieved: %s", start
.c_str());
313 string end
= GetAttr("DTEND", "/vevent");
314 trace
.logf("DTEND attr retrieved: %s", end
.c_str());
315 string subject
= GetAttr("SUMMARY", "/vevent");
316 trace
.logf("SUMMARY attr retrieved: %s", subject
.c_str());
317 if( subject
.size() == 0 ) {
318 subject
= "<blank subject>";
319 trace
.logf("ERROR: bad data, blank SUMMARY: %s", vcal
);
321 vAttr trigger_obj
= GetAttrObj("TRIGGER", 0, "/valarm");
326 // Now, run checks and convert into Barry object
330 // FIXME - we are assuming that any non-UTC timestamps
331 // in the vcalendar record will be in the current timezone...
332 // This is wrong! fix this later.
334 // Also, we current ignore any time zone
335 // parameters that might be in the vcalendar format... this
338 time_t now
= time(NULL
);
339 int zoneoffset
= osync_time_timezone_diff(localtime(&now
));
341 Barry::Calendar
&rec
= m_BarryCal
;
342 rec
.SetIds(Barry::Calendar::GetDefaultRecType(), RecordId
);
345 throw ConvertError("Blank DTSTART");
346 rec
.StartTime
= osync_time_vtime2unix(start
.c_str(), zoneoffset
);
349 // DTEND is actually optional! According to the
350 // RFC, a DTSTART with no DTEND should be treated
351 // like a "special day" like an anniversary, which occupies
354 // Since the Blackberry doesn't really map well to this
355 // case, we'll set the end time to 1 day past start.
357 rec
.EndTime
= rec
.StartTime
+ 24 * 60 * 60;
360 rec
.EndTime
= osync_time_vtime2unix(end
.c_str(), zoneoffset
);
363 rec
.Subject
= subject
;
365 // convert trigger time into notification time
366 // assume no notification, by default
367 rec
.NotificationTime
= 0;
368 if( trigger_obj
.Get() ) {
369 string trigger_type
= trigger_obj
.GetParam("VALUE");
370 string trigger
= trigger_obj
.GetValue();
372 if( trigger
.size() == 0 ) {
373 trace
.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
375 else if( trigger_type
== "DATE-TIME" ) {
376 rec
.NotificationTime
= osync_time_vtime2unix(trigger
.c_str(), zoneoffset
);
378 else if( trigger_type
== "DURATION" || trigger_type
.size() == 0 ) {
379 // default is DURATION (RFC 4.8.6.3)
380 string related
= trigger_obj
.GetParam("RELATED");
382 // default to relative to start time
383 time_t *relative
= &rec
.StartTime
;
384 if( related
== "END" )
385 relative
= &rec
.EndTime
;
387 rec
.NotificationTime
= *relative
+ osync_time_alarmdu2sec(trigger
.c_str());
390 throw ConvertError("Unknown TRIGGER VALUE");
394 trace
.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
397 std::ostringstream oss
;
398 m_BarryCal
.Dump(oss
);
399 trace
.logf("ToBarry, resulting Barry record: %s", oss
.str().c_str());
403 // Transfers ownership of m_gCalData to the caller.
404 char* vCalendar::ExtractVCal()
406 char *ret
= m_gCalData
;
411 void vCalendar::Clear()
425 //////////////////////////////////////////////////////////////////////////////
428 VEventConverter::VEventConverter()
433 VEventConverter::VEventConverter(uint32_t newRecordId
)
435 m_RecordId(newRecordId
)
439 VEventConverter::~VEventConverter()
445 // Transfers ownership of m_Data to the caller
446 char* VEventConverter::ExtractData()
448 Trace
trace("VEventConverter::ExtractData");
454 bool VEventConverter::ParseData(const char *data
)
456 Trace
trace("VEventConverter::ParseData");
461 m_Cal
= vcal
.ToBarry(data
, m_RecordId
);
464 catch( vCalendar::ConvertError
&ce
) {
465 trace
.logf("ERROR: vCalendar::ConvertError exception: %s", ce
.what());
472 // Barry storage operator
473 void VEventConverter::operator()(const Barry::Calendar
&rec
)
475 Trace
trace("VEventConverter::operator()");
477 // Delete data if some already exists
487 m_Data
= vcal
.ExtractVCal();
490 catch( vCalendar::ConvertError
&ce
) {
491 trace
.logf("ERROR: vCalendar::ConvertError exception: %s", ce
.what());
495 // Barry builder operator
496 bool VEventConverter::operator()(Barry::Calendar
&rec
, unsigned int dbId
)
498 Trace
trace("VEventConverter::builder operator()");
504 // Handles calling of the Barry::Controller to fetch a specific
505 // record, indicated by index (into the RecordStateTable).
506 // Returns a g_malloc'd string of data containing the vevent20
507 // data. It is the responsibility of the caller to free it.
508 // This is intended to be passed into the GetChanges() function.
509 char* VEventConverter::GetRecordData(BarryEnvironment
*env
, unsigned int dbId
,
510 Barry::RecordStateTable::IndexType index
)
512 Trace
trace("VEventConverter::GetRecordData()");
514 using namespace Barry
;
516 VEventConverter cal2event
;
517 RecordParser
<Calendar
, VEventConverter
> parser(cal2event
);
518 env
->m_pCon
->GetRecord(dbId
, index
, parser
);
519 return cal2event
.ExtractData();
522 bool VEventConverter::CommitRecordData(BarryEnvironment
*env
, unsigned int dbId
,
523 Barry::RecordStateTable::IndexType StateIndex
, uint32_t recordId
,
524 const char *data
, bool add
, std::string
&errmsg
)
526 Trace
trace("VEventConverter::CommitRecordData()");
528 uint32_t newRecordId
;
530 // use given id if possible
531 if( recordId
&& !env
->m_CalendarSync
.m_Table
.GetIndex(recordId
) ) {
532 // recordId is unique and non-zero
533 newRecordId
= recordId
;
536 trace
.log("Can't use recommended recordId, generating new one.");
537 newRecordId
= env
->m_CalendarSync
.m_Table
.MakeNewRecordId();
541 newRecordId
= env
->m_CalendarSync
.m_Table
.StateMap
[StateIndex
].RecordId
;
543 trace
.logf("newRecordId: %lu", newRecordId
);
545 VEventConverter
convert(newRecordId
);
546 if( !convert
.ParseData(data
) ) {
547 std::ostringstream oss
;
548 oss
<< "unable to parse change data for new RecordId: "
549 << newRecordId
<< " data: " << data
;
551 trace
.logf(errmsg
.c_str());
555 Barry::RecordBuilder
<Barry::Calendar
, VEventConverter
> builder(convert
);
558 trace
.log("adding record");
559 env
->m_pCon
->AddRecord(dbId
, builder
);
562 trace
.log("setting record");
563 env
->m_pCon
->SetRecord(dbId
, StateIndex
, builder
);
564 trace
.log("clearing dirty flag");
565 env
->m_pCon
->ClearDirty(dbId
, StateIndex
);