- more porting of OpenSync module to 0.30
[barry.git] / opensync-plugin / src / vevent.cc
blobcb919be792d3a1bdbae962bfa2d73c3a9d214299
1 //
2 // \file vevent.cc
3 // Conversion routines for vevents (VCALENDAR, etc)
4 //
6 /*
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.
22 #include "vevent.h"
23 #include "environment.h"
24 #include "trace.h"
25 //#include "vformat.h" // comes from opensync, but not a public header yet
26 #include <stdint.h>
27 #include <glib.h>
28 #include <sstream>
29 #include <opensync/format/opensync_time.h>
33 //////////////////////////////////////////////////////////////////////////////
34 // vCalendar
36 vCalendar::vCalendar()
37 : m_gCalData(0),
38 m_format(0)
42 vCalendar::~vCalendar()
44 if( m_gCalData ) {
45 g_free(m_gCalData);
48 if( m_format ) {
49 vformat_free(m_format);
53 vAttrPtr vCalendar::NewAttr(const char *name)
55 Trace trace("vCalendar::NewAttr");
57 trace.logf("creating valueless attr: %s", name);
59 vAttrPtr attr(vformat_attribute_new(NULL, name));
60 if( !attr.Get() )
61 throw ConvertError("resource error allocating vformat attribute");
62 return attr;
65 vAttrPtr vCalendar::NewAttr(const char *name, const char *value)
67 Trace trace("vCalendar::NewAttr");
69 if( strlen(value) == 0 ) {
70 trace.logf("attribute '%s' contains no data, skipping", name);
71 return vAttrPtr();
74 trace.logf("creating attr: %s, %s", name, value);
76 vAttrPtr attr(vformat_attribute_new(NULL, name));
77 if( !attr.Get() )
78 throw ConvertError("resource error allocating vformat attribute");
80 vformat_attribute_add_value(attr.Get(), value);
81 return attr;
84 void vCalendar::AddAttr(vAttrPtr attr)
86 Trace trace("vCalendar::AddAttr");
88 if( !attr.Get() ) {
89 trace.log("attribute contains no data, skipping");
90 return;
93 vformat_add_attribute(m_format, attr.Extract());
96 void vCalendar::AddParam(vAttrPtr &attr, const char *name, const char *value)
98 Trace trace("vCalendar::AddParam");
100 if( !attr.Get() ) {
101 trace.log("attribute pointer contains no data, skipping");
102 return;
104 if( strlen(value) == 0 ) {
105 trace.log("parameter value is empty, skipping");
106 return;
109 VFormatParam *pParam = vformat_attribute_param_new(name);
110 vformat_attribute_param_add_value(pParam, value);
111 vformat_attribute_add_param(attr.Get(), pParam);
114 std::string vCalendar::GetAttr(const char *attrname)
116 Trace trace("vCalendar::GetAttr");
117 trace.logf("getting attr: %s", attrname);
119 std::string ret;
121 VFormatAttribute *attr = vformat_find_attribute(m_format, attrname);
122 if( attr ) {
123 if( vformat_attribute_is_single_valued(attr) ) {
124 ret = vformat_attribute_get_value(attr);
126 else {
127 // FIXME - does this ever happen?
128 ret = vformat_attribute_get_nth_value(attr, 0);
132 trace.logf("attr value: %s", ret.c_str());
134 return ret;
138 const char *vCalendar::WeekDays[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" };
140 unsigned short vCalendar::GetWeekDayIndex(const char *dayname)
142 for( int i = 0; i < 7; i++ ) {
143 if( strcasecmp(dayname, WeekDays[i]) == 0 )
144 return i;
146 return 0;
149 bool vCalendar::HasMultipleVEvents() const
151 int count = 0;
152 GList *attrs = vformat_get_attributes(m_format);
153 for( ; attrs; attrs = attrs->next ) {
154 VFormatAttribute *attr = (VFormatAttribute*) attrs->data;
155 if( strcasecmp(vformat_attribute_get_name(attr), "BEGIN") == 0 &&
156 strcasecmp(vformat_attribute_get_nth_value(attr, 0), "VEVENT") == 0 )
158 count++;
161 return count > 1;
164 void vCalendar::RecurToVCal()
166 using namespace Barry;
167 using namespace std;
168 Barry::Calendar &cal = m_BarryCal;
170 if( !cal.Recurring )
171 return;
173 vAttrPtr attr = NewAttr("RRULE");
175 switch( cal.RecurringType )
177 case Calendar::Day: // eg. every day
178 AddParam(attr, "FREQ", "DAILY");
179 break;
181 case Calendar::MonthByDate: // eg. every month on the 12th
182 // see: DayOfMonth
183 AddParam(attr, "FREQ", "MONTHLY");
185 ostringstream oss;
186 oss << cal.DayOfMonth;
187 AddParam(attr, "BYMONTHDAY", oss.str().c_str());
189 break;
191 case Calendar::MonthByDay: // eg. every month on 3rd Wed
192 // see: DayOfWeek and WeekOfMonth
193 AddParam(attr, "FREQ", "MONTHLY");
194 if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned
195 ostringstream oss;
196 oss << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
197 AddParam(attr, "BYDAY", oss.str().c_str());
199 break;
201 case Calendar::YearByDate: // eg. every year on March 5
202 // see: DayOfMonth and MonthOfYear
203 AddParam(attr, "FREQ", "YEARLY");
205 ostringstream oss;
206 oss << cal.MonthOfYear;
207 AddParam(attr, "BYMONTH", oss.str().c_str());
210 ostringstream oss;
211 oss << cal.DayOfMonth;
212 AddParam(attr, "BYMONTHDAY", oss.str().c_str());
214 break;
216 case Calendar::YearByDay: // eg. every year on 3rd Wed of Jan
217 // see: DayOfWeek, WeekOfMonth, and
218 // MonthOfYear
219 AddParam(attr, "FREQ", "YEARLY");
220 if( cal.DayOfWeek <= 6 ) { // DayOfWeek is unsigned
221 ostringstream oss;
222 oss << cal.WeekOfMonth << WeekDays[cal.DayOfWeek];
223 AddParam(attr, "BYDAY", oss.str().c_str());
225 oss.str("");
226 oss << cal.MonthOfYear;
227 AddParam(attr, "BYMONTH", oss.str().c_str());
229 break;
231 case Calendar::Week: // eg. every week on Mon and Fri
232 // see: WeekDays
233 AddParam(attr, "FREQ", "WEEKLY");
235 ostringstream oss;
236 for( int i = 0, bm = 1, cnt = 0; i < 7; i++, bm <<= 1 ) {
237 if( cal.WeekDays & bm ) {
238 if( cnt )
239 oss << ",";
240 oss << WeekDays[i];
241 cnt++;
244 AddParam(attr, "BYDAY", oss.str().c_str());
246 break;
248 default:
249 throw ConvertError("Unknown RecurringType in Barry Calendar object");
252 // add some common parameters
253 if( cal.Interval > 1 ) {
254 ostringstream oss;
255 oss << cal.Interval;
256 AddParam(attr, "INTERVAL", oss.str().c_str());
258 if( !cal.Perpetual ) {
259 gStringPtr rend(osync_time_unix2vtime(&cal.RecurringEndTime));
260 AddParam(attr, "UNTIL", rend.Get());
263 AddAttr(attr);
265 // bool AllDayEvent;
267 // ///
268 // /// Recurring data
269 // ///
270 // /// Note: interval can be used on all of these recurring types to
271 // /// make it happen "every other time" or more, etc.
272 // ///
274 // bool Recurring;
275 // RecurringCodeType RecurringType;
276 // unsigned short Interval; // must be >= 1
277 // time_t RecurringEndTime; // only pertains if Recurring is true
278 // // sets the date and time when
279 // // recurrence of this appointment
280 // // should no longer occur
281 // // If a perpetual appointment, this
282 // // is 0xFFFFFFFF in the low level data
283 // // Instead, set the following flag.
284 // bool Perpetual; // if true, this will always recur
285 // unsigned short TimeZoneCode; // the time zone originally used
286 // // for the recurrence data...
287 // // seems to have little use, but
288 // // set to your current time zone
289 // // as a good default
291 // unsigned short // recurring details, depending on type
292 // DayOfWeek, // 0-6
293 // WeekOfMonth, // 1-5
294 // DayOfMonth, // 1-31
295 // MonthOfYear; // 1-12
296 // unsigned char WeekDays; // bitmask, bit 0 = sunday
298 // #define CAL_WD_SUN 0x01
299 // #define CAL_WD_MON 0x02
300 // #define CAL_WD_TUE 0x04
301 // #define CAL_WD_WED 0x08
302 // #define CAL_WD_THU 0x10
303 // #define CAL_WD_FRI 0x20
304 // #define CAL_WD_SAT 0x40
309 void vCalendar::RecurToBarryCal()
311 // FIXME - needs to be implemented
313 // GetWeekDayIndex()
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)
320 // start fresh
321 Clear();
322 m_format = vformat_new();
323 if( !m_format )
324 throw ConvertError("resource error allocating vformat");
326 // store the Barry object we're working with
327 m_BarryCal = cal;
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");
352 AddAttr(trigger);
354 AddAttr(NewAttr("END", "VALARM"));
357 if( cal.Recurring ) {
358 RecurToVCal();
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;
367 return m_vCalData;
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)
374 using namespace std;
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");
382 // start fresh
383 Clear();
385 // store the vCalendar raw data
386 m_vCalData = vcal;
388 // create format parser structures
389 m_format = vformat_new_from_string(vcal);
390 if( !m_format )
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
408 // no time.
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
418 // must be fixed.
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
428 // in advance
429 rec.NotificationTime = rec.StartTime - 15 * 60;
430 rec.Subject = subject;
432 return m_BarryCal;
435 // Transfers ownership of m_gCalData to the caller.
436 char* vCalendar::ExtractVCal()
438 char *ret = m_gCalData;
439 m_gCalData = 0;
440 return ret;
443 void vCalendar::Clear()
445 m_vCalData.clear();
446 m_BarryCal.Clear();
448 if( m_gCalData ) {
449 g_free(m_gCalData);
450 m_gCalData = 0;
453 if( m_format ) {
454 vformat_free(m_format);
455 m_format = 0;
462 //////////////////////////////////////////////////////////////////////////////
465 VEventConverter::VEventConverter()
466 : m_Data(0)
470 VEventConverter::VEventConverter(uint32_t newRecordId)
471 : m_Data(0),
472 m_RecordId(newRecordId)
476 VEventConverter::~VEventConverter()
478 if( m_Data )
479 g_free(m_Data);
482 // Transfers ownership of m_Data to the caller
483 char* VEventConverter::ExtractData()
485 Trace trace("VEventConverter::ExtractData");
486 char *ret = m_Data;
487 m_Data = 0;
488 return ret;
491 bool VEventConverter::ParseData(const char *data)
493 Trace trace("VEventConverter::ParseData");
496 try {
498 vCalendar vcal;
499 m_Cal = vcal.ToBarry(data, m_RecordId);
502 catch( vCalendar::ConvertError &ce ) {
503 trace.logf("ERROR: vCalendar::ConvertError exception: %s", ce.what());
504 return false;
507 return true;
509 return false;
512 // Barry storage operator
513 void VEventConverter::operator()(const Barry::Calendar &rec)
515 Trace trace("VEventConverter::operator()");
518 // Delete data if some already exists
519 if( m_Data ) {
520 g_free(m_Data);
521 m_Data = 0;
524 try {
526 vCalendar vcal;
527 vcal.ToVCal(rec);
528 m_Data = vcal.ExtractVCal();
531 catch( vCalendar::ConvertError &ce ) {
532 trace.logf("ERROR: vCalendar::ConvertError exception: %s", ce.what());
537 // Barry builder operator
538 bool VEventConverter::operator()(Barry::Calendar &rec, unsigned int dbId)
540 Trace trace("VEventConverter::builder operator()");
542 rec = m_Cal;
543 return true;
546 // Handles calling of the Barry::Controller to fetch a specific
547 // record, indicated by index (into the RecordStateTable).
548 // Returns a g_malloc'd string of data containing the vevent20
549 // data. It is the responsibility of the caller to free it.
550 // This is intended to be passed into the GetChanges() function.
551 char* VEventConverter::GetRecordData(BarryEnvironment *env, unsigned int dbId,
552 Barry::RecordStateTable::IndexType index)
554 Trace trace("VEventConverter::GetRecordData()");
556 using namespace Barry;
558 VEventConverter cal2event;
559 RecordParser<Calendar, VEventConverter> parser(cal2event);
560 env->m_pCon->GetRecord(dbId, index, parser);
561 return cal2event.ExtractData();
564 bool VEventConverter::CommitRecordData(BarryEnvironment *env, unsigned int dbId,
565 Barry::RecordStateTable::IndexType StateIndex, uint32_t recordId,
566 const char *data, bool add, std::string &errmsg)
568 Trace trace("VEventConverter::CommitRecordData()");
570 uint32_t newRecordId;
571 if( add ) {
572 // use given id if possible
573 if( recordId && !env->m_CalendarSync.m_Table.GetIndex(recordId) ) {
574 // recordId is unique and non-zero
575 newRecordId = recordId;
577 else {
578 trace.log("Can't use recommended recordId, generating new one.");
579 newRecordId = env->m_CalendarSync.m_Table.MakeNewRecordId();
582 else {
583 newRecordId = env->m_CalendarSync.m_Table.StateMap[StateIndex].RecordId;
585 trace.logf("newRecordId: %lu", newRecordId);
587 VEventConverter convert(newRecordId);
588 if( !convert.ParseData(data) ) {
589 std::ostringstream oss;
590 oss << "unable to parse change data for new RecordId: "
591 << newRecordId << " data: " << data;
592 errmsg = oss.str();
593 trace.logf(errmsg.c_str());
594 return false;
597 Barry::RecordBuilder<Barry::Calendar, VEventConverter> builder(convert);
599 if( add ) {
600 trace.log("adding record");
601 env->m_pCon->AddRecord(dbId, builder);
603 else {
604 trace.log("setting record");
605 env->m_pCon->SetRecord(dbId, StateIndex, builder);
606 trace.log("clearing dirty flag");
607 env->m_pCon->ClearDirty(dbId, StateIndex);
610 return true;