3 // Conversion routines for vevents (VCALENDAR, etc)
7 Copyright (C) 2006-2010, 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.
32 namespace Barry
{ namespace Sync
{
34 //////////////////////////////////////////////////////////////////////////////
37 vCalendar::vCalendar(vTimeZone
&vtz
)
43 vCalendar::~vCalendar()
50 const char *vCalendar::WeekDays
[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" };
52 unsigned short vCalendar::GetWeekDayIndex(const char *dayname
)
54 for( int i
= 0; i
< 7; i
++ ) {
55 if( strcasecmp(dayname
, WeekDays
[i
]) == 0 )
61 bool vCalendar::HasMultipleVEvents() const
64 b_VFormat
*format
= const_cast<b_VFormat
*>(Format());
65 GList
*attrs
= format
? b_vformat_get_attributes(format
) : 0;
66 for( ; attrs
; attrs
= attrs
->next
) {
67 b_VFormatAttribute
*attr
= (b_VFormatAttribute
*) attrs
->data
;
68 if( strcasecmp(b_vformat_attribute_get_name(attr
), "BEGIN") == 0 &&
69 strcasecmp(b_vformat_attribute_get_nth_value(attr
, 0), "VEVENT") == 0 )
77 void vCalendar::RecurToVCal()
79 using namespace Barry
;
81 Barry::Calendar
&cal
= m_BarryCal
;
86 vAttrPtr attr
= NewAttr("RRULE");
88 switch( cal
.RecurringType
)
90 case Calendar::Day
: // eg. every day
91 AddParam(attr
, "FREQ", "DAILY");
94 case Calendar::MonthByDate
: // eg. every month on the 12th
96 AddParam(attr
, "FREQ", "MONTHLY");
99 oss
<< cal
.DayOfMonth
;
100 AddParam(attr
, "BYMONTHDAY", oss
.str().c_str());
104 case Calendar::MonthByDay
: // eg. every month on 3rd Wed
105 // see: DayOfWeek and WeekOfMonth
106 AddParam(attr
, "FREQ", "MONTHLY");
107 if( cal
.DayOfWeek
<= 6 ) { // DayOfWeek is unsigned
109 oss
<< cal
.WeekOfMonth
<< WeekDays
[cal
.DayOfWeek
];
110 AddParam(attr
, "BYDAY", oss
.str().c_str());
114 case Calendar::YearByDate
: // eg. every year on March 5
115 // see: DayOfMonth and MonthOfYear
116 AddParam(attr
, "FREQ", "YEARLY");
119 oss
<< cal
.MonthOfYear
;
120 AddParam(attr
, "BYMONTH", oss
.str().c_str());
124 oss
<< cal
.DayOfMonth
;
125 AddParam(attr
, "BYMONTHDAY", oss
.str().c_str());
129 case Calendar::YearByDay
: // eg. every year on 3rd Wed of Jan
130 // see: DayOfWeek, WeekOfMonth, and
132 AddParam(attr
, "FREQ", "YEARLY");
133 if( cal
.DayOfWeek
<= 6 ) { // DayOfWeek is unsigned
135 oss
<< cal
.WeekOfMonth
<< WeekDays
[cal
.DayOfWeek
];
136 AddParam(attr
, "BYDAY", oss
.str().c_str());
139 oss
<< cal
.MonthOfYear
;
140 AddParam(attr
, "BYMONTH", oss
.str().c_str());
144 case Calendar::Week
: // eg. every week on Mon and Fri
146 AddParam(attr
, "FREQ", "WEEKLY");
149 for( int i
= 0, bm
= 1, cnt
= 0; i
< 7; i
++, bm
<<= 1 ) {
150 if( cal
.WeekDays
& bm
) {
157 AddParam(attr
, "BYDAY", oss
.str().c_str());
162 throw ConvertError("Unknown RecurringType in Barry Calendar object");
165 // add some common parameters
166 if( cal
.Interval
> 1 ) {
169 AddParam(attr
, "INTERVAL", oss
.str().c_str());
171 if( !cal
.Perpetual
) {
172 AddParam(attr
, "UNTIL",
173 m_vtz
.unix2vtime(&cal
.RecurringEndTime
).c_str());
184 /// Note: interval can be used on all of these recurring types to
185 /// make it happen "every other time" or more, etc.
189 RecurringCodeType RecurringType;
190 unsigned short Interval; // must be >= 1
191 time_t RecurringEndTime; // only pertains if Recurring is true
192 // sets the date and time when
193 // recurrence of this appointment
194 // should no longer occur
195 // If a perpetual appointment, this
196 // is 0xFFFFFFFF in the low level data
197 // Instead, set the following flag.
198 bool Perpetual; // if true, this will always recur
199 unsigned short TimeZoneCode; // the time zone originally used
200 // for the recurrence data...
201 // seems to have little use, but
202 // set to your current time zone
205 unsigned short // recurring details, depending on type
210 unsigned char WeekDays; // bitmask, bit 0 = sunday
212 #define CAL_WD_SUN 0x01
213 #define CAL_WD_MON 0x02
214 #define CAL_WD_TUE 0x04
215 #define CAL_WD_WED 0x08
216 #define CAL_WD_THU 0x10
217 #define CAL_WD_FRI 0x20
218 #define CAL_WD_SAT 0x40
224 void vCalendar::RecurToBarryCal()
226 // FIXME - needs to be implemented
231 // Main conversion routine for converting from Barry::Calendar to
232 // a vCalendar string of data.
233 const std::string
& vCalendar::ToVCal(const Barry::Calendar
&cal
)
235 // Trace trace("vCalendar::ToVCal");
236 std::ostringstream oss
;
238 // trace.logf("ToVCal, initial Barry record: %s", oss.str().c_str());
242 SetFormat( b_vformat_new() );
244 throw ConvertError("resource error allocating vformat");
246 // store the Barry object we're working with
249 // begin building vCalendar data
250 AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Calendar Record//EN"));
251 AddAttr(NewAttr("BEGIN", "VEVENT"));
252 AddAttr(NewAttr("SEQUENCE", "0"));
253 AddAttr(NewAttr("SUMMARY", cal
.Subject
.c_str()));
254 AddAttr(NewAttr("DESCRIPTION", cal
.Notes
.c_str()));
255 AddAttr(NewAttr("LOCATION", cal
.Location
.c_str()));
257 string
start(m_vtz
.unix2vtime(&cal
.StartTime
));
258 string
end(m_vtz
.unix2vtime(&cal
.EndTime
));
259 string
notify(m_vtz
.unix2vtime(&cal
.NotificationTime
));
261 AddAttr(NewAttr("DTSTART", start
.c_str()));
262 AddAttr(NewAttr("DTEND", end
.c_str()));
263 // FIXME - add a truly globally unique "UID" string?
266 AddAttr(NewAttr("BEGIN", "VALARM"));
267 AddAttr(NewAttr("ACTION", "AUDIO"));
269 // notify must be UTC, when specified in DATE-TIME
270 vAttrPtr trigger
= NewAttr("TRIGGER", notify
.c_str());
271 AddParam(trigger
, "VALUE", "DATE-TIME");
274 AddAttr(NewAttr("END", "VALARM"));
277 if( cal
.Recurring
) {
281 AddAttr(NewAttr("END", "VEVENT"));
283 // generate the raw VCALENDAR data
284 m_gCalData
= b_vformat_to_string(Format(), VFORMAT_EVENT_20
);
285 m_vCalData
= m_gCalData
;
287 // trace.logf("ToVCal, resulting vcal data: %s", m_vCalData.c_str());
291 // Main conversion routine for converting from vCalendar data string
292 // to a Barry::Calendar object.
293 const Barry::Calendar
& vCalendar::ToBarry(const char *vcal
, uint32_t RecordId
)
297 // Trace trace("vCalendar::ToBarry");
298 // trace.logf("ToBarry, working on vcal data: %s", vcal);
300 // we only handle vCalendar data with one vevent block
301 if( HasMultipleVEvents() )
302 throw ConvertError("vCalendar data contains more than one VEVENT block, unsupported");
307 // store the vCalendar raw data
310 // create format parser structures
311 SetFormat( b_vformat_new_from_string(vcal
) );
313 throw ConvertError("resource error allocating vformat");
315 string start
= GetAttr("DTSTART", "/vevent");
316 // trace.logf("DTSTART attr retrieved: %s", start.c_str());
317 string end
= GetAttr("DTEND", "/vevent");
318 // trace.logf("DTEND attr retrieved: %s", end.c_str());
319 string subject
= GetAttr("SUMMARY", "/vevent");
320 // trace.logf("SUMMARY attr retrieved: %s", subject.c_str());
321 if( subject
.size() == 0 ) {
322 subject
= "<blank subject>";
323 // trace.logf("ERROR: bad data, blank SUMMARY: %s", vcal);
325 vAttr trigger_obj
= GetAttrObj("TRIGGER", 0, "/valarm");
327 string location
= GetAttr("LOCATION", "/vevent");
328 // trace.logf("LOCATION attr retrieved: %s", location.c_str());
330 string notes
= GetAttr("DESCRIPTION", "/vevent");
331 // trace.logf("DESCRIPTION attr retrieved: %s", notes.c_str());
335 // Now, run checks and convert into Barry object
339 // FIXME - we are assuming that any non-UTC timestamps
340 // in the vcalendar record will be in the current timezone...
341 // This is wrong! fix this later.
343 // Also, we current ignore any time zone
344 // parameters that might be in the vcalendar format... this
347 time_t now
= time(NULL
);
348 int zoneoffset
= m_vtz
.timezone_diff(localtime(&now
));
350 Barry::Calendar
&rec
= m_BarryCal
;
351 rec
.SetIds(Barry::Calendar::GetDefaultRecType(), RecordId
);
354 throw ConvertError("Blank DTSTART");
355 rec
.StartTime
= m_vtz
.vtime2unix(start
.c_str(), zoneoffset
);
358 // DTEND is actually optional! According to the
359 // RFC, a DTSTART with no DTEND should be treated
360 // like a "special day" like an anniversary, which occupies
363 // Since the Blackberry doesn't really map well to this
364 // case, we'll set the end time to 1 day past start.
366 rec
.EndTime
= rec
.StartTime
+ 24 * 60 * 60;
369 rec
.EndTime
= m_vtz
.vtime2unix(end
.c_str(), zoneoffset
);
372 rec
.Subject
= subject
;
373 rec
.Location
= location
;
376 // convert trigger time into notification time
377 // assume no notification, by default
378 rec
.NotificationTime
= 0;
379 if( trigger_obj
.Get() ) {
380 string trigger_type
= trigger_obj
.GetParam("VALUE");
381 string trigger
= trigger_obj
.GetValue();
383 if( trigger
.size() == 0 ) {
384 // trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
386 else if( trigger_type
== "DATE-TIME" ) {
387 rec
.NotificationTime
= m_vtz
.vtime2unix(trigger
.c_str(), zoneoffset
);
389 else if( trigger_type
== "DURATION" || trigger_type
.size() == 0 ) {
390 // default is DURATION (RFC 4.8.6.3)
391 string related
= trigger_obj
.GetParam("RELATED");
393 // default to relative to start time
394 time_t *relative
= &rec
.StartTime
;
395 if( related
== "END" )
396 relative
= &rec
.EndTime
;
398 rec
.NotificationTime
= *relative
+ m_vtz
.alarmdu2sec(trigger
.c_str());
401 throw ConvertError("Unknown TRIGGER VALUE");
405 // trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
408 std::ostringstream oss
;
409 m_BarryCal
.Dump(oss
);
410 // trace.logf("ToBarry, resulting Barry record: %s", oss.str().c_str());
414 // Transfers ownership of m_gCalData to the caller.
415 char* vCalendar::ExtractVCal()
417 char *ret
= m_gCalData
;
422 void vCalendar::Clear()
434 }} // namespace Barry::Sync