vevent.cc

Go to the documentation of this file.
00001 ///
00002 /// \file       vevent.cc
00003 ///             Conversion routines for vevents (VCALENDAR, etc)
00004 ///
00005 
00006 /*
00007     Copyright (C) 2006-2012, Net Direct Inc. (http://www.netdirect.ca/)
00008     Copyright (C) 2010, Nicolas VIVIEN
00009     Copyright (C) 2009, Dr J A Gow <J.A.Gow@wellfrazzled.com>
00010 
00011     This program is free software; you can redistribute it and/or modify
00012     it under the terms of the GNU General Public License as published by
00013     the Free Software Foundation; either version 2 of the License, or
00014     (at your option) any later version.
00015 
00016     This program is distributed in the hope that it will be useful,
00017     but WITHOUT ANY WARRANTY; without even the implied warranty of
00018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00019 
00020     See the GNU General Public License in the COPYING file at the
00021     root directory of this project for more details.
00022 */
00023 
00024 #include "vevent.h"
00025 //#include "trace.h"
00026 #include "log.h"
00027 #include "time.h"
00028 #include <stdint.h>
00029 #include <glib.h>
00030 #include <strings.h>
00031 #include <stdlib.h>
00032 #include <sstream>
00033 #include <string>
00034 
00035 using namespace std;
00036 
00037 namespace Barry { namespace Sync {
00038 
00039 //////////////////////////////////////////////////////////////////////////////
00040 // vCalendar
00041 
00042 vCalendar::vCalendar(vTimeConverter &vtc)
00043         : m_vtc(vtc)
00044         , m_gCalData(0)
00045 {
00046 }
00047 
00048 vCalendar::~vCalendar()
00049 {
00050         if( m_gCalData ) {
00051                 g_free(m_gCalData);
00052         }
00053 }
00054 
00055 const char *vCalendar::WeekDays[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" };
00056 
00057 uint16_t vCalendar::GetWeekDayIndex(const char *dayname)
00058 {
00059         for( int i = 0; i < 7; i++ ) {
00060                 if( strcasecmp(dayname, WeekDays[i]) == 0 )
00061                         return i;
00062         }
00063         return 0;
00064 }
00065 
00066 void vCalendar::CheckUnsupportedArg(const ArgMapType &args,
00067                                         const std::string &name)
00068 {
00069         if( args.find(name) != args.end() ) {
00070                 barrylog("ERROR: recurrence rule contains " << name << ", unsupported by Barry. MIME conversion will be incorrect.");
00071                 barryverbose("Record data so far:\n" << m_BarryCal);
00072         }
00073 }
00074 
00075 std::vector<std::string> vCalendar::SplitBYDAY(const std::string &ByDay)
00076 {
00077         std::vector<std::string> v = Tokenize(ByDay);
00078 
00079         // BlackBerry recursion only supports one specification...
00080         // i.e. only 3rd Wed of month, not 3rd Wed and 2nd Fri of month...
00081         // if there's more than one item in v, warn the user, and just
00082         // use the first item
00083         if( v.size() > 1 ) {
00084                 barrylog("Warning: multiple items in BYDAY, not supported by device (" << ByDay << "). Using only the first item.");
00085                 barryverbose("Record data so far:\n" << m_BarryCal);
00086         }
00087 
00088         return v;
00089 }
00090 
00091 uint16_t vCalendar::GetMonthWeekNumFromBYDAY(const std::string& ByDay)
00092 {
00093         std::vector<std::string> v = SplitBYDAY(ByDay);
00094 
00095         if( !v.size() || v[0].size() < 2 ) {
00096                 return 0;
00097         }
00098         else {
00099                 int week = atoi(v[0].substr(0,v[0].length()-2).c_str());
00100                 if( week < 0 ) {
00101                         // assume 4 weeks per month
00102                         int pos_week = 4 + (week + 1);
00103                         if( pos_week < 1 || pos_week > 4 ) {
00104                                 pos_week = 1;
00105                         }
00106 
00107                         barrylog("Warning: negative week in BYDAY (" << week << "), unsupported by device. Converting to positive week, based on 4 week months: " << pos_week << ".");
00108                         barryverbose("Record data so far:\n" << m_BarryCal);
00109 
00110                         week = pos_week;
00111                 }
00112                 return week;
00113         }
00114 }
00115 
00116 uint16_t vCalendar::GetWeekDayIndexFromBYDAY(const std::string& ByDay)
00117 {
00118         std::vector<std::string> v = SplitBYDAY(ByDay);
00119 
00120         if( !v.size() || v[0].size() < 2 )
00121                 return 0;
00122         return GetWeekDayIndex(v[0].substr(v[0].length()-2).c_str());
00123 }
00124 
00125 // month_override is specified in 1-12, or -1 to use m_BarryCal.StartTime
00126 uint16_t vCalendar::GetDayOfMonthFromBYMONTHDAY(const ArgMapType &args,
00127                                                 int month_override)
00128 {
00129         time_t starttime = m_BarryCal.StartTime.Time;
00130         struct tm datestruct;
00131         localtime_r(&starttime,&datestruct);
00132         if( month_override != -1 )
00133                 datestruct.tm_mon = month_override - 1;
00134         int monthdays = DaysInMonth(datestruct);
00135 
00136         ArgMapType::const_iterator vi = args.find("BYMONTHDAY");
00137         if( vi == args.end() )
00138                 throw std::logic_error("Called GetDayOfMonthFromBYMONTHDAY() without a BYMONTHDAY");
00139 
00140         int val = atoi(vi->second.c_str());
00141         if( val == 0 ) {
00142                 barryverbose("Warning: BYMONTHDAY of 0, assuming 1.\n"
00143                         << "Record data so far:\n" << m_BarryCal);
00144                 val = 1;
00145         }
00146         else if( val > monthdays ) {
00147                 barryverbose("Warning: BYMONTHDAY larger than month (" << val << " days). Assuming 1.\nRecord data so far:\n" << m_BarryCal);
00148                 val = 1;
00149         }
00150         else if( val < 0 ) {
00151                 // See 4.3.10 RRULE in RFC 2445
00152                 // negative values mean "last n day of month", so
00153                 // convert to the fixed day, and then use that positive
00154                 // value instead, as an approximation
00155                 int pos_day = monthdays + (val + 1);
00156                 if( pos_day < 1 || pos_day > monthdays ) {
00157                         pos_day = 1;
00158                 }
00159                 barrylog("Warning: negative BYMONTHDAY (" << val << "), unsupported by device. Converting to positive day of month: " << pos_day << ".");
00160                 barryverbose("Record data so far:\n" << m_BarryCal);
00161 
00162                 val = pos_day;
00163         }
00164 
00165         return val;
00166 }
00167 
00168 
00169 bool vCalendar::HasMultipleVEvents() const
00170 {
00171         int count = 0;
00172         b_VFormat *format = const_cast<b_VFormat*>(Format());
00173         GList *attrs = format ? b_vformat_get_attributes(format) : 0;
00174         for( ; attrs; attrs = attrs->next ) {
00175                 b_VFormatAttribute *attr = (b_VFormatAttribute*) attrs->data;
00176                 if( strcasecmp(b_vformat_attribute_get_name(attr), "BEGIN") == 0 &&
00177                     strcasecmp(b_vformat_attribute_get_nth_value(attr, 0), "VEVENT") == 0 )
00178                 {
00179                         count++;
00180                 }
00181         }
00182         return count > 1;
00183 }
00184 
00185 void vCalendar::RecurToVCal()
00186 {
00187         using namespace Barry;
00188         using namespace std;
00189         Barry::Calendar &cal = m_BarryCal;
00190 
00191         if( !cal.Recurring )
00192                 return;
00193 
00194         vAttrPtr attr = NewAttr("RRULE");
00195 
00196         switch( cal.RecurringType )
00197         {
00198         case Calendar::Day:             // eg. every day
00199                 AddValue(attr,"FREQ=DAILY");
00200                 break;
00201 
00202         case Calendar::MonthByDate:     // eg. every month on the 12th
00203                                         // see: DayOfMonth
00204                 AddValue(attr,"FREQ=MONTHLY");
00205                 {
00206                         ostringstream oss;
00207                         oss << "BYMONTHDAY=" << cal.DayOfMonth;
00208                         AddValue(attr, oss.str().c_str());
00209                 }
00210                 break;
00211 
00212         case Calendar::MonthByDay:      // eg. every month on 3rd Wed
00213                                         // see: DayOfWeek and WeekOfMonth
00214                 AddValue(attr, "FREQ=MONTHLY");
00215                 if( cal.DayOfWeek <= 6 ) {      // DayOfWeek is unsigned
00216                         ostringstream oss;
00217                         oss << "BYDAY=" << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
00218                         AddValue(attr, oss.str().c_str());
00219                 }
00220                 break;
00221 
00222         case Calendar::YearByDate:      // eg. every year on March 5
00223                                         // see: DayOfMonth and MonthOfYear
00224                 AddValue(attr, "FREQ=YEARLY");
00225                 {
00226                         ostringstream oss;
00227                         oss << "BYMONTH=" << cal.MonthOfYear;
00228                         AddValue(attr, oss.str().c_str());
00229                 }
00230                 {
00231                         ostringstream oss;
00232                         oss << "BYMONTHDAY=" << cal.DayOfMonth;
00233                         AddValue(attr, oss.str().c_str());
00234                 }
00235                 break;
00236 
00237         case Calendar::YearByDay:       // eg. every year on 3rd Wed of Jan
00238                                         // see: DayOfWeek, WeekOfMonth, and
00239                                         //      MonthOfYear
00240                 AddValue(attr, "FREQ=YEARLY");
00241                 if( cal.DayOfWeek <= 6 ) {      // DayOfWeek is unsigned
00242                         ostringstream oss;
00243                         oss << "BYDAY=" << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
00244                         AddValue(attr, oss.str().c_str());
00245 
00246                         oss.str("");
00247                         oss << "BYMONTH=" << cal.MonthOfYear;
00248                         AddValue(attr, oss.str().c_str());
00249                 }
00250                 break;
00251 
00252         case Calendar::Week:            // eg. every week on Mon and Fri
00253                                         // see: WeekDays
00254                 AddValue(attr, "FREQ=WEEKLY");
00255                 {
00256                         ostringstream oss;
00257                         oss << "BYDAY=";
00258                         for( int i = 0, bm = 1, cnt = 0; i < 7; i++, bm <<= 1 ) {
00259                                 if( cal.WeekDays & bm ) {
00260                                         if( cnt )
00261                                                 oss << ",";
00262                                         oss << WeekDays[i];
00263                                         cnt++;
00264                                 }
00265                         }
00266                         AddValue(attr, oss.str().c_str());
00267                 }
00268                 break;
00269 
00270         default:
00271                 throw ConvertError("Unknown RecurringType in Barry Calendar object");
00272         }
00273 
00274         // add some common parameters
00275         if( cal.Interval > 1 ) {
00276                 ostringstream oss;
00277                 oss << "INTERVAL=" << cal.Interval;
00278                 AddValue(attr, oss.str().c_str());
00279         }
00280         if( !cal.Perpetual ) {
00281                 ostringstream oss;
00282                 oss << "UNTIL=" << m_vtc.unix2vtime(&cal.RecurringEndTime.Time);
00283                 AddValue(attr, oss.str().c_str());
00284         }
00285 
00286         AddAttr(attr);
00287 
00288 /*
00289         bool AllDayEvent;
00290 
00291         ///
00292         /// Recurring data
00293         ///
00294         /// Note: interval can be used on all of these recurring types to
00295         ///       make it happen "every other time" or more, etc.
00296         ///
00297 
00298         bool Recurring;
00299         RecurringCodeType RecurringType;
00300         uint16_t Interval;              // must be >= 1
00301         time_t RecurringEndTime;        // only pertains if Recurring is true
00302                                         // sets the date and time when
00303                                         // recurrence of this appointment
00304                                         // should no longer occur
00305                                         // If a perpetual appointment, this
00306                                         // is 0xFFFFFFFF in the low level data
00307                                         // Instead, set the following flag.
00308         bool Perpetual;                 // if true, this will always recur
00309         uint16_t TimeZoneCode;          // the time zone originally used
00310                                         // for the recurrence data...
00311                                         // seems to have little use, but
00312                                         // set to your current time zone
00313                                         // as a good default
00314 
00315         uint16_t                        // recurring details, depending on type
00316                 DayOfWeek,              // 0-6
00317                 WeekOfMonth,            // 1-5
00318                 DayOfMonth,             // 1-31
00319                 MonthOfYear;            // 1-12
00320         unsigned char WeekDays;         // bitmask, bit 0 = sunday
00321 
00322                 #define CAL_WD_SUN      0x01
00323                 #define CAL_WD_MON      0x02
00324                 #define CAL_WD_TUE      0x04
00325                 #define CAL_WD_WED      0x08
00326                 #define CAL_WD_THU      0x10
00327                 #define CAL_WD_FRI      0x20
00328                 #define CAL_WD_SAT      0x40
00329 
00330 */
00331 
00332 }
00333 
00334 namespace {
00335         struct tm GetConstLocalTime(const time_t &t)
00336         {
00337                 struct tm datestruct;
00338                 localtime_r(&t, &datestruct);
00339                 return datestruct;
00340         }
00341 }
00342 
00343 void vCalendar::RecurToBarryCal(vAttr& rrule, time_t starttime)
00344 {
00345         using namespace Barry;
00346         using namespace std;
00347         Barry::Calendar &cal = m_BarryCal;
00348 //      Trace trace("vCalendar::RecurToBarryCal");
00349         std::map<std::string,unsigned char> pmap;
00350         pmap["SU"] = CAL_WD_SUN;
00351         pmap["MO"] = CAL_WD_MON;
00352         pmap["TU"] = CAL_WD_TUE;
00353         pmap["WE"] = CAL_WD_WED;
00354         pmap["TH"] = CAL_WD_THU;
00355         pmap["FR"] = CAL_WD_FRI;
00356         pmap["SA"] = CAL_WD_SAT;
00357 
00358         const struct tm datestruct = GetConstLocalTime(starttime);
00359 
00360         int i=0;
00361         unsigned int count=0;
00362         string val;
00363         ArgMapType args;
00364         do {
00365                 val=rrule.GetValue(i++);
00366                 if(val.length()==0) {
00367                         break;
00368                 }
00369                 string n=val.substr(0,val.find("="));
00370                 string v=val.substr(val.find("=")+1);
00371                 args[n]=v;
00372 //              trace.logf("RecurToBarryCal: |%s|%s|",n.c_str(),v.c_str());
00373         } while(1);
00374 
00375         // now process the interval.
00376         cal.Recurring=TRUE;
00377 
00378         if(args.find(string("INTERVAL"))!=args.end()) {
00379                 int interval = atoi(args["INTERVAL"].c_str());
00380                 if( interval < 1 ) {
00381                         // force to at least 1, for math below
00382                         interval = 1;
00383                 }
00384                 cal.Interval = interval;
00385         }
00386         else {
00387                 // default to 1, for the math below.
00388                 // RecurBase::Clear() does this for us as well, but
00389                 // best to be safe
00390                 cal.Interval = 1;
00391         }
00392         if(args.find(string("UNTIL"))!=args.end()) {
00393                 cal.Perpetual = FALSE;
00394                 cal.RecurringEndTime.Time = m_vtc.vtime2unix(args["UNTIL"].c_str());
00395                 if( cal.RecurringEndTime.Time == (time_t)-1 ) {
00396 //                      trace.logf("osync_time_vtime2unix() failed: UNTIL = %s, zoneoffset = %d", args["UNTIL"].c_str(), zoneoffset);
00397                 }
00398         } else {
00399                 // if we do not also have COUNT, then we must be forerver
00400                 if(args.find(string("COUNT"))==args.end()) {
00401                         cal.Perpetual=TRUE;
00402                 } else {
00403                         // we do have COUNT. This means we won't have UNTIL.
00404                         // So we need to process the RecurringEndTime from
00405                         // the current start date. Set the count level to
00406                         // something other than zero to indicate we need
00407                         // to process it as the exact end date will
00408                         // depend upon the frequency.
00409                         count=atoi(args["COUNT"].c_str());
00410                         if( count == 0 ) {
00411                                 throw std::runtime_error("Invalid COUNT in recurring rule: " + args["COUNT"]);
00412                         }
00413                 }
00414         }
00415 
00416         // we need these if COUNT is true, or if we are a yearly job.
00417 
00418         // TO-DO: we must process COUNT in terms of an end date if we have it.
00419 
00420         // warn the user about unsupported arguments
00421         CheckUnsupportedArg(args, "BYSETPOS");// FIXME - theorectically supportable
00422         CheckUnsupportedArg(args, "BYYEARDAY");
00423         CheckUnsupportedArg(args, "BYWEEKNO");
00424         CheckUnsupportedArg(args, "WKST");
00425         CheckUnsupportedArg(args, "BYSECOND");
00426         CheckUnsupportedArg(args, "BYMINUTE");
00427         CheckUnsupportedArg(args, "BYHOUR");
00428 
00429         // Now deal with the freq
00430 
00431         if(args.find(string("FREQ"))==args.end()) {
00432 //              trace.logf("RecurToBarryCal: No frequency specified!");
00433                 return;
00434         }
00435 
00436         if(args["FREQ"]==string("DAILY")) {
00437                 cal.RecurringType=Calendar::Day;
00438 
00439                 if(count) {
00440                         // add count-1*interval days to find the end time:
00441                         // i.e. if starting on 2012/01/01 and going
00442                         // for 3 days, then the last day will be
00443                         // 2012/01/03.
00444                         //
00445                         // For intervals, the count is every interval days,
00446                         // so interval of 2 means 2012/01/01, 2012/01/03, etc.
00447                         // and the calculation still works.
00448                         cal.RecurringEndTime.Time =
00449                                 starttime + (count-1) * cal.Interval * 24*60*60;
00450                 }
00451         } else if(args["FREQ"]==string("WEEKLY")) {
00452                 cal.RecurringType=Calendar::Week;
00453                 // we must have a dayofweek entry
00454                 if(args.find(string("BYDAY"))!=args.end()) {
00455                         std::vector<std::string> v=Tokenize(args["BYDAY"]);
00456                         // iterate along our vector and convert
00457                         for(unsigned int idx=0;idx<v.size();idx++) {
00458                                 cal.WeekDays|=pmap[v[idx]];
00459                         }
00460                 } else {
00461                         // we must have at least one day selected, and if no
00462                         // BYDAY is selected, use the start time's day
00463                         cal.WeekDays = pmap[WeekDays[datestruct.tm_wday]];
00464 
00465                         barrylog("Warning: WEEKLY VEVENT without a day selected. Assuming day of start time.");
00466                         barryverbose("Record data so far:\n" << cal);
00467                 }
00468 
00469                 if(count) {
00470                         // need to process end date. This is easy
00471                         // for weeks, as a number of weeks can be
00472                         // reduced to seconds simply.
00473                         cal.RecurringEndTime.Time =
00474                                 starttime + (count-1) * cal.Interval * 60*60*24*7;
00475                 }
00476         } else if(args["FREQ"]=="MONTHLY") {
00477                 if(args.find(string("BYMONTHDAY"))!=args.end()) {
00478                         cal.RecurringType=Calendar::MonthByDate;
00479                         cal.DayOfMonth=GetDayOfMonthFromBYMONTHDAY(args);
00480                 } else {
00481                         if(args.find(string("BYDAY"))!=args.end()) {
00482                                 cal.RecurringType=Calendar::MonthByDay;
00483                                 cal.WeekOfMonth=GetMonthWeekNumFromBYDAY(args["BYDAY"]);
00484                                 cal.DayOfWeek=GetWeekDayIndexFromBYDAY(args["BYDAY"]);
00485                         } else {
00486                                 // must have a recurring type, so assume
00487                                 // that monthly means every day on the day
00488                                 // of month specified by starttime
00489                                 cal.RecurringType = Calendar::MonthByDate;
00490                                 cal.DayOfMonth = datestruct.tm_mday;
00491                                 barrylog("Warning: MONTHLY VEVENT without a day type specified (no BYMONTHDAY nor BYDAY). Assuming BYMONTHDAY, using day of start time.");
00492                                 barryverbose("Record data so far:\n" << cal);
00493                         }
00494                 }
00495                 if(count) {
00496                         // Nasty. We need to convert to struct tm,
00497                         // do some modulo-12 addition then back
00498                         // to time_t
00499                         struct tm tempdate = datestruct;
00500 
00501                         // now do some modulo-12 on the month and year
00502                         // We could end up with an illegal date if
00503                         // the day of month is >28 and the resulting
00504                         // month falls on a February. We don't need
00505                         // to worry about day of week as mktime()
00506                         // clobbers it.
00507                         //
00508                         // We let mktime() normalize any out of range values.
00509                         int add = (count-1) * cal.Interval;
00510                         tempdate.tm_year += (tempdate.tm_mon+add)/12;
00511                         tempdate.tm_mon = (tempdate.tm_mon+add)%12;
00512 
00513                         // Just in case we're crossing DST boundaries,
00514                         // add an hour, to make sure we reach the ending
00515                         // month, in the case of intervals
00516                         tempdate.tm_hour++;
00517                         cal.RecurringEndTime.Time = mktime(&tempdate);
00518                 }
00519         } else if(args["FREQ"]=="YEARLY") {
00520                 bool need_assumption = true;
00521                 if(args.find(string("BYMONTH"))!=args.end()) {
00522                         cal.MonthOfYear=atoi(args["BYMONTH"].c_str());
00523                         if( cal.MonthOfYear < 1 || cal.MonthOfYear > 12 ) {
00524                                 // doh... default to starttime's month
00525                                 cal.MonthOfYear = datestruct.tm_mon + 1;
00526                         }
00527                         if(args.find(string("BYMONTHDAY"))!=args.end()) {
00528                                 cal.RecurringType=Calendar::YearByDate;
00529                                 cal.DayOfMonth=GetDayOfMonthFromBYMONTHDAY(args, cal.MonthOfYear);
00530                                 need_assumption = false;
00531                         } else {
00532                                 if(args.find(string("BYDAY"))!=args.end()) {
00533                                         cal.RecurringType=Calendar::YearByDay;
00534                                         cal.WeekOfMonth=GetMonthWeekNumFromBYDAY(args["BYDAY"]);
00535                                         cal.DayOfWeek=GetWeekDayIndexFromBYDAY(args["BYDAY"]);
00536                                         need_assumption = false;
00537                                 } else {
00538                                         // fall through to assumption below...
00539                                 }
00540                         }
00541                 }
00542 
00543                 if( need_assumption ) {
00544                         // otherwise use the start date and translate
00545                         // to a BYMONTHDAY.
00546                         // cal.StartTime has already been processed
00547                         // when we get here we need month of year,
00548                         // and day of month.
00549                         cal.RecurringType=Calendar::YearByDate;
00550                         cal.MonthOfYear=datestruct.tm_mon;
00551                         cal.DayOfMonth=datestruct.tm_mday;
00552                         barrylog("Warning: YEARLY VEVENT without a day type specified (no BYMONTHDAY nor BYDAY). Assuming BYMONTHDAY, using day and month of start time.");
00553                         barryverbose("Record data so far:\n" << cal);
00554                 }
00555                 if(count) {
00556                         // convert to struct tm, then simply add to the year.
00557                         //
00558                         // Note: intervals do work in the device firmware,
00559                         // but not all of the devices allow you to edit it
00560                         // with their GUI... hmmm... oh well, allow it
00561                         // anyway, and do the multiplication below.
00562                         struct tm tempdate = datestruct;
00563                         tempdate.tm_year += (count-1) * cal.Interval;
00564                         cal.RecurringEndTime.Time = mktime(&tempdate);
00565                 }
00566         }
00567 
00568 //      unsigned char WeekDays;         // bitmask, bit 0 = sunday
00569 //
00570 //              #define CAL_WD_SUN      0x01
00571 //              #define CAL_WD_MON      0x02
00572 //              #define CAL_WD_TUE      0x04
00573 //              #define CAL_WD_WED      0x08
00574 //              #define CAL_WD_THU      0x10
00575 //              #define CAL_WD_FRI      0x20
00576 //              #define CAL_WD_SAT      0x40
00577 }
00578 
00579 // Main conversion routine for converting from Barry::Calendar to
00580 // a vCalendar string of data.
00581 const std::string& vCalendar::ToVCal(const Barry::Calendar &cal)
00582 {
00583 //      Trace trace("vCalendar::ToVCal");
00584         std::ostringstream oss;
00585         cal.Dump(oss);
00586 //      trace.logf("ToVCal, initial Barry record: %s", oss.str().c_str());
00587 
00588         // start fresh
00589         Clear();
00590         SetFormat( b_vformat_new() );
00591         if( !Format() )
00592                 throw ConvertError("resource error allocating vformat");
00593 
00594         // store the Barry object we're working with
00595         m_BarryCal = cal;
00596 
00597         // RFC section 4.8.7.2 requires DTSTAMP in all VEVENT, VTODO,
00598         // VJOURNAL, and VFREEBUSY calendar components, and it must be
00599         // in UTC.  DTSTAMP holds the timestamp of when the iCal object itself
00600         // was created, not when the object was created in the device or app.
00601         // So, find out what time it is "now".
00602         time_t now = time(NULL);
00603 
00604         // begin building vCalendar data
00605         AddAttr(NewAttr("PRODID", "-//OpenSync//NONSGML Barry Calendar Record//EN"));
00606         AddAttr(NewAttr("BEGIN", "VEVENT"));
00607         AddAttr(NewAttr("DTSTAMP", m_vtc.unix2vtime(&now).c_str())); // see note above
00608         AddAttr(NewAttr("SEQUENCE", "0"));
00609         AddAttr(NewAttr("SUMMARY", cal.Subject.c_str()));
00610         AddAttr(NewAttr("DESCRIPTION", cal.Notes.c_str()));
00611         AddAttr(NewAttr("LOCATION", cal.Location.c_str()));
00612 
00613         string start(m_vtc.unix2vtime(&cal.StartTime.Time));
00614         string end(m_vtc.unix2vtime(&cal.EndTime.Time));
00615         string notify(m_vtc.unix2vtime(&cal.NotificationTime.Time));
00616 
00617         // if an all day event, only print the date parts of the string
00618         if( cal.AllDayEvent && start.find('T') != string::npos ) {
00619                 // truncate start date
00620                 start = start.substr(0, start.find('T'));
00621 
00622                 // create end date 1 day in future
00623                 time_t end_t = cal.StartTime.Time + 24 * 60 * 60;
00624                 end = m_vtc.unix2vtime(&end_t);
00625                 end = end.substr(0, end.find('T'));
00626         }
00627 
00628         AddAttr(NewAttr("DTSTART", start.c_str()));
00629         AddAttr(NewAttr("DTEND", end.c_str()));
00630         // FIXME - add a truly globally unique "UID" string?
00631 
00632 
00633         AddAttr(NewAttr("BEGIN", "VALARM"));
00634         AddAttr(NewAttr("ACTION", "AUDIO"));
00635 
00636         // notify must be UTC, when specified in DATE-TIME
00637         vAttrPtr trigger = NewAttr("TRIGGER", notify.c_str());
00638         AddParam(trigger, "VALUE", "DATE-TIME");
00639         AddAttr(trigger);
00640 
00641         AddAttr(NewAttr("END", "VALARM"));
00642 
00643 
00644         if( cal.Recurring ) {
00645                 RecurToVCal();
00646         }
00647 
00648         AddAttr(NewAttr("END", "VEVENT"));
00649 
00650         // generate the raw VCALENDAR data
00651         m_gCalData = b_vformat_to_string(Format(), VFORMAT_EVENT_20);
00652         m_vCalData = m_gCalData;
00653 
00654 //      trace.logf("ToVCal, resulting vcal data: %s", m_vCalData.c_str());
00655         return m_vCalData;
00656 }
00657 
00658 // Main conversion routine for converting from vCalendar data string
00659 // to a Barry::Calendar object.
00660 const Barry::Calendar& vCalendar::ToBarry(const char *vcal, uint32_t RecordId)
00661 {
00662         using namespace std;
00663 
00664 //      Trace trace("vCalendar::ToBarry");
00665 //      trace.logf("ToBarry, working on vcal data: %s", vcal);
00666 
00667         // we only handle vCalendar data with one vevent block
00668         if( HasMultipleVEvents() )
00669                 throw ConvertError("vCalendar data contains more than one VEVENT block, unsupported");
00670 
00671         // start fresh
00672         Clear();
00673 
00674         // store the vCalendar raw data
00675         m_vCalData = vcal;
00676 
00677         // create format parser structures
00678         SetFormat( b_vformat_new_from_string(vcal) );
00679         if( !Format() )
00680                 throw ConvertError("resource error allocating vformat");
00681 
00682         string start = GetAttr("DTSTART", "/vevent");
00683 //      trace.logf("DTSTART attr retrieved: %s", start.c_str());
00684         string end = GetAttr("DTEND", "/vevent");
00685 //      trace.logf("DTEND attr retrieved: %s", end.c_str());
00686         string subject = GetAttr("SUMMARY", "/vevent");
00687 //      trace.logf("SUMMARY attr retrieved: %s", subject.c_str());
00688         if( subject.size() == 0 ) {
00689                 subject = "<blank subject>";
00690 //              trace.logf("ERROR: bad data, blank SUMMARY: %s", vcal);
00691         }
00692         vAttr trigger_obj = GetAttrObj("TRIGGER", 0, "/valarm");
00693 
00694         string location = GetAttr("LOCATION", "/vevent");
00695 //      trace.logf("LOCATION attr retrieved: %s", location.c_str());
00696 
00697         string notes = GetAttr("DESCRIPTION", "/vevent");
00698 //      trace.logf("DESCRIPTION attr retrieved: %s", notes.c_str());
00699 
00700         vAttr rrule = GetAttrObj("RRULE",0,"/vevent");
00701 
00702 
00703         //
00704         // Now, run checks and convert into Barry object
00705         //
00706 
00707 
00708         // FIXME - we are assuming that any non-UTC timestamps
00709         // in the vcalendar record will be in the current timezone...
00710         // This is wrong!  fix this later.
00711         //
00712         // Also, we currently ignore any time zone
00713         // parameters that might be in the vcalendar format... this
00714         // must be fixed.
00715         //
00716         Barry::Calendar &rec = m_BarryCal;
00717         rec.SetIds(Barry::Calendar::GetDefaultRecType(), RecordId);
00718 
00719         if( !start.size() )
00720                 throw ConvertError("Blank DTSTART");
00721         rec.StartTime.Time = m_vtc.vtime2unix(start.c_str());
00722 
00723         if( !end.size() ) {
00724                 // DTEND is actually optional!  According to the
00725                 // RFC, a DTSTART with no DTEND should be treated
00726                 // like a "special day" like an anniversary, which occupies
00727                 // no time.
00728                 //
00729                 // Since the Blackberry doesn't really map well to this
00730                 // case, we'll set the end time to 1 day past start.
00731                 //
00732                 rec.EndTime.Time = rec.StartTime.Time + 24 * 60 * 60;
00733         }
00734         else {
00735                 rec.EndTime.Time = m_vtc.vtime2unix(end.c_str());
00736         }
00737 
00738         // check for "all day event" which is specified by a DTSTART
00739         // and a DTEND with no times, and one day apart
00740         if( start.find('T') == string::npos && end.size() &&
00741             end.find('T') == string::npos &&
00742             (rec.EndTime.Time - rec.StartTime.Time) == 24 * 60 * 60 )
00743         {
00744                 rec.AllDayEvent = true;
00745         }
00746 
00747         rec.Subject = subject;
00748         rec.Location = location;
00749         rec.Notes = notes;
00750 
00751         if(rrule.Get()) {
00752                 RecurToBarryCal(rrule, rec.StartTime.Time);
00753         }
00754 
00755         // convert trigger time into notification time
00756         // assume no notification, by default
00757         rec.NotificationTime.Time = 0;
00758         if( trigger_obj.Get() ) {
00759                 string trigger_type = trigger_obj.GetParam("VALUE");
00760                 string trigger = trigger_obj.GetValue();
00761 
00762                 if( trigger.size() == 0 ) {
00763 //                      trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
00764                 }
00765                 else if( trigger_type == "DATE-TIME" ) {
00766                         rec.NotificationTime.Time = m_vtc.vtime2unix(trigger.c_str());
00767                 }
00768                 else if( trigger_type == "DURATION" || trigger_type.size() == 0 ) {
00769                         // default is DURATION (RFC 4.8.6.3)
00770                         string related = trigger_obj.GetParam("RELATED");
00771 
00772                         // default to relative to start time
00773                         time_t *relative = &rec.StartTime.Time;
00774                         if( related == "END" )
00775                                 relative = &rec.EndTime.Time;
00776 
00777                         rec.NotificationTime.Time = *relative + m_vtc.alarmduration2sec(trigger.c_str());
00778                 }
00779                 else {
00780                         throw ConvertError("Unknown TRIGGER VALUE");
00781                 }
00782         }
00783         else {
00784 //              trace.logf("ERROR: no TRIGGER found in calendar entry, assuming notification time as 15 minutes before start.");
00785         }
00786 
00787         std::ostringstream oss;
00788         m_BarryCal.Dump(oss);
00789 //      trace.logf("ToBarry, resulting Barry record: %s", oss.str().c_str());
00790         return m_BarryCal;
00791 }
00792 
00793 // Transfers ownership of m_gCalData to the caller.
00794 char* vCalendar::ExtractVCal()
00795 {
00796         char *ret = m_gCalData;
00797         m_gCalData = 0;
00798         return ret;
00799 }
00800 
00801 void vCalendar::Clear()
00802 {
00803         vBase::Clear();
00804         m_vCalData.clear();
00805         m_BarryCal.Clear();
00806 
00807         if( m_gCalData ) {
00808                 g_free(m_gCalData);
00809                 m_gCalData = 0;
00810         }
00811 }
00812 
00813 }} // namespace Barry::Sync
00814