maint: added proper sample of Barry yum repo file for Fedora
[barry/progweb.git] / src / vevent.cc
blobbbf900cecf1f4670a67c108c7fb0dcc05c01b8e1
1 ///
2 /// \file vevent.cc
3 /// Conversion routines for vevents (VCALENDAR, etc)
4 ///
6 /*
7 Copyright (C) 2006-2012, Net Direct Inc. (http://www.netdirect.ca/)
8 Copyright (C) 2010, Nicolas VIVIEN
9 Copyright (C) 2009, Dr J A Gow <J.A.Gow@wellfrazzled.com>
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
20 See the GNU General Public License in the COPYING file at the
21 root directory of this project for more details.
24 #include "vevent.h"
25 //#include "trace.h"
26 #include <stdint.h>
27 #include <glib.h>
28 #include <strings.h>
29 #include <stdlib.h>
30 #include <sstream>
31 #include <string>
33 using namespace std;
35 namespace Barry { namespace Sync {
37 //////////////////////////////////////////////////////////////////////////////
38 // vCalendar
40 vCalendar::vCalendar(vTimeConverter &vtc)
41 : m_vtc(vtc)
42 , m_gCalData(0)
46 vCalendar::~vCalendar()
48 if( m_gCalData ) {
49 g_free(m_gCalData);
53 const char *vCalendar::WeekDays[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" };
55 uint16_t vCalendar::GetWeekDayIndex(const char *dayname)
57 for( int i = 0; i < 7; i++ ) {
58 if( strcasecmp(dayname, WeekDays[i]) == 0 )
59 return i;
61 return 0;
64 uint16_t vCalendar::GetMonthWeekNumFromBYDAY(const std::string& ByDay)
66 return atoi(ByDay.substr(0,ByDay.length()-2).c_str());
69 uint16_t vCalendar::GetWeekDayIndexFromBYDAY(const std::string& ByDay)
71 return GetWeekDayIndex(ByDay.substr(ByDay.length()-2).c_str());
75 bool vCalendar::HasMultipleVEvents() const
77 int count = 0;
78 b_VFormat *format = const_cast<b_VFormat*>(Format());
79 GList *attrs = format ? b_vformat_get_attributes(format) : 0;
80 for( ; attrs; attrs = attrs->next ) {
81 b_VFormatAttribute *attr = (b_VFormatAttribute*) attrs->data;
82 if( strcasecmp(b_vformat_attribute_get_name(attr), "BEGIN") == 0 &&
83 strcasecmp(b_vformat_attribute_get_nth_value(attr, 0), "VEVENT") == 0 )
85 count++;
88 return count > 1;
91 void vCalendar::RecurToVCal()
93 using namespace Barry;
94 using namespace std;
95 Barry::Calendar &cal = m_BarryCal;
97 if( !cal.Recurring )
98 return;
100 vAttrPtr attr = NewAttr("RRULE");
102 switch( cal.RecurringType )
104 case Calendar::Day: // eg. every day
105 AddValue(attr,"FREQ=DAILY");
106 break;
108 case Calendar::MonthByDate: // eg. every month on the 12th
109 // see: DayOfMonth
110 AddValue(attr,"FREQ=MONTHLY");
112 ostringstream oss;
113 oss << "BYMONTHDAY=" << cal.DayOfMonth;
114 AddValue(attr, oss.str().c_str());
116 break;
118 case Calendar::MonthByDay: // eg. every month on 3rd Wed
119 // see: DayOfWeek and WeekOfMonth
120 AddValue(attr, "FREQ=MONTHLY");
121 if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned
122 ostringstream oss;
123 oss << "BYDAY=" << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
124 AddValue(attr, oss.str().c_str());
126 break;
128 case Calendar::YearByDate: // eg. every year on March 5
129 // see: DayOfMonth and MonthOfYear
130 AddValue(attr, "FREQ=YEARLY");
132 ostringstream oss;
133 oss << "BYMONTH=" << cal.MonthOfYear;
134 AddValue(attr, oss.str().c_str());
137 ostringstream oss;
138 oss << "BYMONTHDAY=" << cal.DayOfMonth;
139 AddValue(attr, oss.str().c_str());
141 break;
143 case Calendar::YearByDay: // eg. every year on 3rd Wed of Jan
144 // see: DayOfWeek, WeekOfMonth, and
145 // MonthOfYear
146 AddValue(attr, "FREQ=YEARLY");
147 if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned
148 ostringstream oss;
149 oss << "BYDAY=" << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
150 AddValue(attr, oss.str().c_str());
152 oss.str("");
153 oss << "BYMONTH=" << cal.MonthOfYear;
154 AddValue(attr, oss.str().c_str());
156 break;
158 case Calendar::Week: // eg. every week on Mon and Fri
159 // see: WeekDays
160 AddValue(attr, "FREQ=WEEKLY");
162 ostringstream oss;
163 oss << "BYDAY=";
164 for( int i = 0, bm = 1, cnt = 0; i < 7; i++, bm <<= 1 ) {
165 if( cal.WeekDays & bm ) {
166 if( cnt )
167 oss << ",";
168 oss << WeekDays[i];
169 cnt++;
172 AddValue(attr, oss.str().c_str());
174 break;
176 default:
177 throw ConvertError("Unknown RecurringType in Barry Calendar object");
180 // add some common parameters
181 if( cal.Interval > 1 ) {
182 ostringstream oss;
183 oss << "INTERVAL=" << cal.Interval;
184 AddValue(attr, oss.str().c_str());
186 if( !cal.Perpetual ) {
187 ostringstream oss;
188 oss << "UNTIL=" << m_vtc.unix2vtime(&cal.RecurringEndTime.Time);
189 AddValue(attr, oss.str().c_str());
192 AddAttr(attr);
195 bool AllDayEvent;
198 /// Recurring data
200 /// Note: interval can be used on all of these recurring types to
201 /// make it happen "every other time" or more, etc.
204 bool Recurring;
205 RecurringCodeType RecurringType;
206 uint16_t Interval; // must be >= 1
207 time_t RecurringEndTime; // only pertains if Recurring is true
208 // sets the date and time when
209 // recurrence of this appointment
210 // should no longer occur
211 // If a perpetual appointment, this
212 // is 0xFFFFFFFF in the low level data
213 // Instead, set the following flag.
214 bool Perpetual; // if true, this will always recur
215 uint16_t TimeZoneCode; // the time zone originally used
216 // for the recurrence data...
217 // seems to have little use, but
218 // set to your current time zone
219 // as a good default
221 uint16_t // recurring details, depending on type
222 DayOfWeek, // 0-6
223 WeekOfMonth, // 1-5
224 DayOfMonth, // 1-31
225 MonthOfYear; // 1-12
226 unsigned char WeekDays; // bitmask, bit 0 = sunday
228 #define CAL_WD_SUN 0x01
229 #define CAL_WD_MON 0x02
230 #define CAL_WD_TUE 0x04
231 #define CAL_WD_WED 0x08
232 #define CAL_WD_THU 0x10
233 #define CAL_WD_FRI 0x20
234 #define CAL_WD_SAT 0x40
240 void vCalendar::RecurToBarryCal(vAttr& rrule, time_t starttime)
242 using namespace Barry;
243 using namespace std;
244 Barry::Calendar &cal = m_BarryCal;
245 // Trace trace("vCalendar::RecurToBarryCal");
246 std::map<std::string,unsigned char> pmap;
247 pmap["SU"] = CAL_WD_SUN;
248 pmap["MO"] = CAL_WD_MON;
249 pmap["TU"] = CAL_WD_TUE;
250 pmap["WE"] = CAL_WD_WED;
251 pmap["TH"] = CAL_WD_THU;
252 pmap["FR"] = CAL_WD_FRI;
253 pmap["SA"] = CAL_WD_SAT;
256 int i=0;
257 unsigned int count=0;
258 string val;
259 std::map<std::string,std::string> args;
260 do {
261 val=rrule.GetValue(i++);
262 if(val.length()==0) {
263 break;
265 string n=val.substr(0,val.find("="));
266 string v=val.substr(val.find("=")+1);
267 args[n]=v;
268 // trace.logf("RecurToBarryCal: |%s|%s|",n.c_str(),v.c_str());
269 } while(1);
271 // now process the interval.
272 cal.Recurring=TRUE;
274 if(args.find(string("INTERVAL"))!=args.end()) {
275 cal.Interval = atoi(args["INTERVAL"].c_str());
277 if(args.find(string("UNTIL"))!=args.end()) {
278 cal.Perpetual = FALSE;
279 cal.RecurringEndTime.Time = m_vtc.vtime2unix(args["UNTIL"].c_str());
280 if( cal.RecurringEndTime.Time == (time_t)-1 ) {
281 // trace.logf("osync_time_vtime2unix() failed: UNTIL = %s, zoneoffset = %d", args["UNTIL"].c_str(), zoneoffset);
283 } else {
284 // if we do not also have COUNT, then we must be forerver
285 if(args.find(string("COUNT"))==args.end()) {
286 cal.Perpetual=TRUE;
287 } else {
288 // we do have COUNT. This means we won't have UNTIL.
289 // So we need to process the RecurringEndTime from
290 // the current start date. Set the count level to
291 // something other than zero to indicate we need
292 // to process it as the exact end date will
293 // depend upon the frequency.
294 count=atoi(args["COUNT"].c_str());
295 if( count == 0 ) {
296 throw std::runtime_error("Invalid COUNT in recurring rule: " + args["COUNT"]);
301 // we need these if COUNT is true, or if we are a yearly job.
303 // TO-DO: we must process COUNT in terms of an end date if we have it.
305 // Now deal with the freq
307 if(args.find(string("FREQ"))==args.end()) {
308 // trace.logf("RecurToBarryCal: No frequency specified!");
309 return;
312 if(args["FREQ"]==string("DAILY")) {
313 cal.RecurringType=Calendar::Day;
315 } else if(args["FREQ"]==string("WEEKLY")) {
316 cal.RecurringType=Calendar::Week;
317 // we must have a dayofweek entry
318 if(args.find(string("BYDAY"))!=args.end()) {
319 std::vector<std::string> v=Tokenize(args["BYDAY"]);
320 // iterate along our vector and convert
321 for(unsigned int idx=0;idx<v.size();idx++) {
322 cal.WeekDays|=pmap[v[idx]];
324 } else {
325 // handle error here
326 // trace.logf("RecurToBarryCal: no BYDAY on weekly event");
328 if(count) {
329 // need to process end date. This is easy
330 // for weeks, as a number of weeks can be
331 // reduced to seconds simply.
332 cal.RecurringEndTime.Time = starttime + ((count-1)*60*60*24*7);
334 } else if(args["FREQ"]=="MONTHLY") {
335 if(args.find(string("BYMONTHDAY"))!=args.end()) {
336 cal.RecurringType=Calendar::MonthByDate;
337 cal.DayOfMonth=atoi(args["BYMONTHDAY"].c_str());
338 } else {
339 if(args.find(string("BYDAY"))!=args.end()) {
340 cal.RecurringType=Calendar::MonthByDay;
341 cal.WeekOfMonth=GetMonthWeekNumFromBYDAY(args["BYDAY"]);
342 cal.DayOfWeek=GetWeekDayIndexFromBYDAY(args["BYDAY"]);
343 } else {
344 // trace.logf("RecurToBarryCal: No qualifier on MONTHLY freq");
347 if(count) {
348 // Nasty. We need to convert to struct tm,
349 // do some modulo-12 addition then back
350 // to time_t
351 struct tm datestruct;
352 localtime_r(&starttime,&datestruct);
353 // now do some modulo-12 on the month and year
354 // We could end up with an illegal date if
355 // the day of month is >28 and the resulting
356 // month falls on a February. We don't need
357 // to worry about day of week as mktime()
358 // clobbers it.
359 datestruct.tm_year += (datestruct.tm_mon+count)/12;
360 datestruct.tm_mon = (datestruct.tm_mon+count)%12;
361 if(datestruct.tm_mday>28 && datestruct.tm_mon==1) {
362 // force it to 1st Mar
363 // TODO Potential bug on leap years
364 datestruct.tm_mon=2;
365 datestruct.tm_mday=1;
367 if(datestruct.tm_mday==31 && (datestruct.tm_mon==8 ||
368 datestruct.tm_mon==3 ||
369 datestruct.tm_mon==5 ||
370 datestruct.tm_mon==10)) {
371 datestruct.tm_mon+=1;
372 datestruct.tm_mday=1;
374 cal.RecurringEndTime.Time = mktime(&datestruct);
376 } else if(args["FREQ"]=="YEARLY") {
377 if(args.find(string("BYMONTH"))!=args.end()) {
378 cal.MonthOfYear=atoi(args["BYMONTH"].c_str());
379 if(args.find(string("BYMONTHDAY"))!=args.end()) {
380 cal.RecurringType=Calendar::YearByDate;
381 cal.DayOfMonth=atoi(args["BYMONTHDAY"].c_str());
382 } else {
383 if(args.find(string("BYDAY"))!=args.end()) {
384 cal.RecurringType=Calendar::YearByDay;
385 cal.WeekOfMonth=GetMonthWeekNumFromBYDAY(args["BYDAY"]);
386 cal.DayOfWeek=GetWeekDayIndexFromBYDAY(args["BYDAY"]);
387 } else {
388 // trace.logf("RecurToBarryCal: No qualifier on YEARLY freq");
391 } else {
392 // otherwise use the start date and translate
393 // to a BYMONTHDAY.
394 // cal.StartTime has already been processed
395 // when we get here we need month of year,
396 // and day of month.
397 struct tm datestruct;
398 localtime_r(&starttime,&datestruct);
399 cal.RecurringType=Calendar::YearByDate;
400 cal.MonthOfYear=datestruct.tm_mon;
401 cal.DayOfMonth=datestruct.tm_mday;
403 if(count) {
404 // convert to struct tm, then simply add to the year.
405 struct tm datestruct;
406 localtime_r(&starttime,&datestruct);
407 datestruct.tm_year += count;
408 cal.RecurringEndTime.Time = mktime(&datestruct);
412 // unsigned char WeekDays; // bitmask, bit 0 = sunday
414 // #define CAL_WD_SUN 0x01
415 // #define CAL_WD_MON 0x02
416 // #define CAL_WD_TUE 0x04
417 // #define CAL_WD_WED 0x08
418 // #define CAL_WD_THU 0x10
419 // #define CAL_WD_FRI 0x20
420 // #define CAL_WD_SAT 0x40
423 // Main conversion routine for converting from Barry::Calendar to
424 // a vCalendar string of data.
425 const std::string& vCalendar::ToVCal(const Barry::Calendar &cal)
427 // Trace trace("vCalendar::ToVCal");
428 std::ostringstream oss;
429 cal.Dump(oss);
430 // trace.logf("ToVCal, initial Barry record: %s", oss.str().c_str());
432 // start fresh
433 Clear();
434 SetFormat( b_vformat_new() );
435 if( !Format() )
436 throw ConvertError("resource error allocating vformat");
438 // store the Barry object we're working with
439 m_BarryCal = cal;
441 // RFC section 4.8.7.2 requires DTSTAMP in all VEVENT, VTODO,
442 // VJOURNAL, and VFREEBUSY calendar components, and it must be
443 // in UTC. DTSTAMP holds the timestamp of when the iCal object itself
444 // was created, not when the object was created in the device or app.
445 // So, find out what time it is "now".
446 time_t now = time(NULL);
448 // begin building vCalendar data
449 AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Calendar Record//EN"));
450 AddAttr(NewAttr("BEGIN", "VEVENT"));
451 AddAttr(NewAttr("DTSTAMP", m_vtc.unix2vtime(&now).c_str())); // see note above
452 AddAttr(NewAttr("SEQUENCE", "0"));
453 AddAttr(NewAttr("SUMMARY", cal.Subject.c_str()));
454 AddAttr(NewAttr("DESCRIPTION", cal.Notes.c_str()));
455 AddAttr(NewAttr("LOCATION", cal.Location.c_str()));
457 string start(m_vtc.unix2vtime(&cal.StartTime.Time));
458 string end(m_vtc.unix2vtime(&cal.EndTime.Time));
459 string notify(m_vtc.unix2vtime(&cal.NotificationTime.Time));
461 // if an all day event, only print the date parts of the string
462 if( cal.AllDayEvent && start.find('T') != string::npos ) {
463 // truncate start date
464 start = start.substr(0, start.find('T'));
466 // create end date 1 day in future
467 time_t end_t = cal.StartTime.Time + 24 * 60 * 60;
468 end = m_vtc.unix2vtime(&end_t);
469 end = end.substr(0, end.find('T'));
472 AddAttr(NewAttr("DTSTART", start.c_str()));
473 AddAttr(NewAttr("DTEND", end.c_str()));
474 // FIXME - add a truly globally unique "UID" string?
477 AddAttr(NewAttr("BEGIN", "VALARM"));
478 AddAttr(NewAttr("ACTION", "AUDIO"));
480 // notify must be UTC, when specified in DATE-TIME
481 vAttrPtr trigger = NewAttr("TRIGGER", notify.c_str());
482 AddParam(trigger, "VALUE", "DATE-TIME");
483 AddAttr(trigger);
485 AddAttr(NewAttr("END", "VALARM"));
488 if( cal.Recurring ) {
489 RecurToVCal();
492 AddAttr(NewAttr("END", "VEVENT"));
494 // generate the raw VCALENDAR data
495 m_gCalData = b_vformat_to_string(Format(), VFORMAT_EVENT_20);
496 m_vCalData = m_gCalData;
498 // trace.logf("ToVCal, resulting vcal data: %s", m_vCalData.c_str());
499 return m_vCalData;
502 // Main conversion routine for converting from vCalendar data string
503 // to a Barry::Calendar object.
504 const Barry::Calendar& vCalendar::ToBarry(const char *vcal, uint32_t RecordId)
506 using namespace std;
508 // Trace trace("vCalendar::ToBarry");
509 // trace.logf("ToBarry, working on vcal data: %s", vcal);
511 // we only handle vCalendar data with one vevent block
512 if( HasMultipleVEvents() )
513 throw ConvertError("vCalendar data contains more than one VEVENT block, unsupported");
515 // start fresh
516 Clear();
518 // store the vCalendar raw data
519 m_vCalData = vcal;
521 // create format parser structures
522 SetFormat( b_vformat_new_from_string(vcal) );
523 if( !Format() )
524 throw ConvertError("resource error allocating vformat");
526 string start = GetAttr("DTSTART", "/vevent");
527 // trace.logf("DTSTART attr retrieved: %s", start.c_str());
528 string end = GetAttr("DTEND", "/vevent");
529 // trace.logf("DTEND attr retrieved: %s", end.c_str());
530 string subject = GetAttr("SUMMARY", "/vevent");
531 // trace.logf("SUMMARY attr retrieved: %s", subject.c_str());
532 if( subject.size() == 0 ) {
533 subject = "<blank subject>";
534 // trace.logf("ERROR: bad data, blank SUMMARY: %s", vcal);
536 vAttr trigger_obj = GetAttrObj("TRIGGER", 0, "/valarm");
538 string location = GetAttr("LOCATION", "/vevent");
539 // trace.logf("LOCATION attr retrieved: %s", location.c_str());
541 string notes = GetAttr("DESCRIPTION", "/vevent");
542 // trace.logf("DESCRIPTION attr retrieved: %s", notes.c_str());
544 vAttr rrule = GetAttrObj("RRULE",0,"/vevent");
548 // Now, run checks and convert into Barry object
552 // FIXME - we are assuming that any non-UTC timestamps
553 // in the vcalendar record will be in the current timezone...
554 // This is wrong! fix this later.
556 // Also, we currently ignore any time zone
557 // parameters that might be in the vcalendar format... this
558 // must be fixed.
560 Barry::Calendar &rec = m_BarryCal;
561 rec.SetIds(Barry::Calendar::GetDefaultRecType(), RecordId);
563 if( !start.size() )
564 throw ConvertError("Blank DTSTART");
565 rec.StartTime.Time = m_vtc.vtime2unix(start.c_str());
567 if( !end.size() ) {
568 // DTEND is actually optional! According to the
569 // RFC, a DTSTART with no DTEND should be treated
570 // like a "special day" like an anniversary, which occupies
571 // no time.
573 // Since the Blackberry doesn't really map well to this
574 // case, we'll set the end time to 1 day past start.
576 rec.EndTime.Time = rec.StartTime.Time + 24 * 60 * 60;
578 else {
579 rec.EndTime.Time = m_vtc.vtime2unix(end.c_str());
582 // check for "all day event" which is specified by a DTSTART
583 // and a DTEND with no times, and one day apart
584 if( start.find('T') == string::npos && end.size() &&
585 end.find('T') == string::npos &&
586 (rec.EndTime.Time - rec.StartTime.Time) == 24 * 60 * 60 )
588 rec.AllDayEvent = true;
591 rec.Subject = subject;
592 rec.Location = location;
593 rec.Notes = notes;
595 if(rrule.Get()) {
596 RecurToBarryCal(rrule, rec.StartTime.Time);
599 // convert trigger time into notification time
600 // assume no notification, by default
601 rec.NotificationTime.Time = 0;
602 if( trigger_obj.Get() ) {
603 string trigger_type = trigger_obj.GetParam("VALUE");
604 string trigger = trigger_obj.GetValue();
606 if( trigger.size() == 0 ) {
607 // trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
609 else if( trigger_type == "DATE-TIME" ) {
610 rec.NotificationTime.Time = m_vtc.vtime2unix(trigger.c_str());
612 else if( trigger_type == "DURATION" || trigger_type.size() == 0 ) {
613 // default is DURATION (RFC 4.8.6.3)
614 string related = trigger_obj.GetParam("RELATED");
616 // default to relative to start time
617 time_t *relative = &rec.StartTime.Time;
618 if( related == "END" )
619 relative = &rec.EndTime.Time;
621 rec.NotificationTime.Time = *relative + m_vtc.alarmduration2sec(trigger.c_str());
623 else {
624 throw ConvertError("Unknown TRIGGER VALUE");
627 else {
628 // trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
631 std::ostringstream oss;
632 m_BarryCal.Dump(oss);
633 // trace.logf("ToBarry, resulting Barry record: %s", oss.str().c_str());
634 return m_BarryCal;
637 // Transfers ownership of m_gCalData to the caller.
638 char* vCalendar::ExtractVCal()
640 char *ret = m_gCalData;
641 m_gCalData = 0;
642 return ret;
645 void vCalendar::Clear()
647 vBase::Clear();
648 m_vCalData.clear();
649 m_BarryCal.Clear();
651 if( m_gCalData ) {
652 g_free(m_gCalData);
653 m_gCalData = 0;
657 }} // namespace Barry::Sync