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());
136 const char *vCalendar::WeekDays
[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" };
138 unsigned short vCalendar::GetWeekDayIndex(const char *dayname
)
140 for( int i
= 0; i
< 7; i
++ ) {
141 if( strcasecmp(dayname
, WeekDays
[i
]) == 0 )
147 bool vCalendar::HasMultipleVEvents() const
150 GList
*attrs
= m_format
? vformat_get_attributes(m_format
) : 0;
151 for( ; attrs
; attrs
= attrs
->next
) {
152 VFormatAttribute
*attr
= (VFormatAttribute
*) attrs
->data
;
153 if( strcasecmp(vformat_attribute_get_name(attr
), "BEGIN") == 0 &&
154 strcasecmp(vformat_attribute_get_nth_value(attr
, 0), "VEVENT") == 0 )
162 void vCalendar::RecurToVCal()
164 using namespace Barry
;
166 Barry::Calendar
&cal
= m_BarryCal
;
171 vAttrPtr attr
= NewAttr("RRULE");
173 switch( cal
.RecurringType
)
175 case Calendar::Day
: // eg. every day
176 AddParam(attr
, "FREQ", "DAILY");
179 case Calendar::MonthByDate
: // eg. every month on the 12th
181 AddParam(attr
, "FREQ", "MONTHLY");
184 oss
<< cal
.DayOfMonth
;
185 AddParam(attr
, "BYMONTHDAY", oss
.str().c_str());
189 case Calendar::MonthByDay
: // eg. every month on 3rd Wed
190 // see: DayOfWeek and WeekOfMonth
191 AddParam(attr
, "FREQ", "MONTHLY");
192 if( cal
.DayOfWeek
<= 6 ) { // DayOfWeek is unsigned
194 oss
<< cal
.WeekOfMonth
<< WeekDays
[cal
.DayOfWeek
];
195 AddParam(attr
, "BYDAY", oss
.str().c_str());
199 case Calendar::YearByDate
: // eg. every year on March 5
200 // see: DayOfMonth and MonthOfYear
201 AddParam(attr
, "FREQ", "YEARLY");
204 oss
<< cal
.MonthOfYear
;
205 AddParam(attr
, "BYMONTH", oss
.str().c_str());
209 oss
<< cal
.DayOfMonth
;
210 AddParam(attr
, "BYMONTHDAY", oss
.str().c_str());
214 case Calendar::YearByDay
: // eg. every year on 3rd Wed of Jan
215 // see: DayOfWeek, WeekOfMonth, and
217 AddParam(attr
, "FREQ", "YEARLY");
218 if( cal
.DayOfWeek
<= 6 ) { // DayOfWeek is unsigned
220 oss
<< cal
.WeekOfMonth
<< WeekDays
[cal
.DayOfWeek
];
221 AddParam(attr
, "BYDAY", oss
.str().c_str());
224 oss
<< cal
.MonthOfYear
;
225 AddParam(attr
, "BYMONTH", oss
.str().c_str());
229 case Calendar::Week
: // eg. every week on Mon and Fri
231 AddParam(attr
, "FREQ", "WEEKLY");
234 for( int i
= 0, bm
= 1, cnt
= 0; i
< 7; i
++, bm
<<= 1 ) {
235 if( cal
.WeekDays
& bm
) {
242 AddParam(attr
, "BYDAY", oss
.str().c_str());
247 throw ConvertError("Unknown RecurringType in Barry Calendar object");
250 // add some common parameters
251 if( cal
.Interval
> 1 ) {
254 AddParam(attr
, "INTERVAL", oss
.str().c_str());
256 if( !cal
.Perpetual
) {
257 gStringPtr
rend(osync_time_unix2vtime(&cal
.RecurringEndTime
));
258 AddParam(attr
, "UNTIL", rend
.Get());
269 /// Note: interval can be used on all of these recurring types to
270 /// make it happen "every other time" or more, etc.
274 RecurringCodeType RecurringType;
275 unsigned short Interval; // must be >= 1
276 time_t RecurringEndTime; // only pertains if Recurring is true
277 // sets the date and time when
278 // recurrence of this appointment
279 // should no longer occur
280 // If a perpetual appointment, this
281 // is 0xFFFFFFFF in the low level data
282 // Instead, set the following flag.
283 bool Perpetual; // if true, this will always recur
284 unsigned short TimeZoneCode; // the time zone originally used
285 // for the recurrence data...
286 // seems to have little use, but
287 // set to your current time zone
290 unsigned short // recurring details, depending on type
295 unsigned char WeekDays; // bitmask, bit 0 = sunday
297 #define CAL_WD_SUN 0x01
298 #define CAL_WD_MON 0x02
299 #define CAL_WD_TUE 0x04
300 #define CAL_WD_WED 0x08
301 #define CAL_WD_THU 0x10
302 #define CAL_WD_FRI 0x20
303 #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
);
461 //////////////////////////////////////////////////////////////////////////////
464 VEventConverter::VEventConverter()
469 VEventConverter::VEventConverter(uint32_t newRecordId
)
471 m_RecordId(newRecordId
)
475 VEventConverter::~VEventConverter()
481 // Transfers ownership of m_Data to the caller
482 char* VEventConverter::ExtractData()
484 Trace
trace("VEventConverter::ExtractData");
490 bool VEventConverter::ParseData(const char *data
)
492 Trace
trace("VEventConverter::ParseData");
497 m_Cal
= vcal
.ToBarry(data
, m_RecordId
);
500 catch( vCalendar::ConvertError
&ce
) {
501 trace
.logf("ERROR: vCalendar::ConvertError exception: %s", ce
.what());
508 // Barry storage operator
509 void VEventConverter::operator()(const Barry::Calendar
&rec
)
511 Trace
trace("VEventConverter::operator()");
513 // Delete data if some already exists
523 m_Data
= vcal
.ExtractVCal();
526 catch( vCalendar::ConvertError
&ce
) {
527 trace
.logf("ERROR: vCalendar::ConvertError exception: %s", ce
.what());
531 // Barry builder operator
532 bool VEventConverter::operator()(Barry::Calendar
&rec
, unsigned int dbId
)
534 Trace
trace("VEventConverter::builder operator()");
540 // Handles calling of the Barry::Controller to fetch a specific
541 // record, indicated by index (into the RecordStateTable).
542 // Returns a g_malloc'd string of data containing the vevent20
543 // data. It is the responsibility of the caller to free it.
544 // This is intended to be passed into the GetChanges() function.
545 char* VEventConverter::GetRecordData(BarryEnvironment
*env
, unsigned int dbId
,
546 Barry::RecordStateTable::IndexType index
)
548 Trace
trace("VEventConverter::GetRecordData()");
550 using namespace Barry
;
552 VEventConverter cal2event
;
553 RecordParser
<Calendar
, VEventConverter
> parser(cal2event
);
554 env
->m_pCon
->GetRecord(dbId
, index
, parser
);
555 return cal2event
.ExtractData();
558 bool VEventConverter::CommitRecordData(BarryEnvironment
*env
, unsigned int dbId
,
559 Barry::RecordStateTable::IndexType StateIndex
, uint32_t recordId
,
560 const char *data
, bool add
, std::string
&errmsg
)
562 Trace
trace("VEventConverter::CommitRecordData()");
564 uint32_t newRecordId
;
566 // use given id if possible
567 if( recordId
&& !env
->m_CalendarSync
.m_Table
.GetIndex(recordId
) ) {
568 // recordId is unique and non-zero
569 newRecordId
= recordId
;
572 trace
.log("Can't use recommended recordId, generating new one.");
573 newRecordId
= env
->m_CalendarSync
.m_Table
.MakeNewRecordId();
577 newRecordId
= env
->m_CalendarSync
.m_Table
.StateMap
[StateIndex
].RecordId
;
579 trace
.logf("newRecordId: %lu", newRecordId
);
581 VEventConverter
convert(newRecordId
);
582 if( !convert
.ParseData(data
) ) {
583 std::ostringstream oss
;
584 oss
<< "unable to parse change data for new RecordId: "
585 << newRecordId
<< " data: " << data
;
587 trace
.logf(errmsg
.c_str());
591 Barry::RecordBuilder
<Barry::Calendar
, VEventConverter
> builder(convert
);
594 trace
.log("adding record");
595 env
->m_pCon
->AddRecord(dbId
, builder
);
598 trace
.log("setting record");
599 env
->m_pCon
->SetRecord(dbId
, StateIndex
, builder
);
600 trace
.log("clearing dirty flag");
601 env
->m_pCon
->ClearDirty(dbId
, StateIndex
);