menu: added new Keywords tag to .desktop files
[barry.git] / src / r_calendar.cc
blob4a70a2e4143b878d5e0a7ba7ca0ddf55bf051f89
1 ///
2 /// \file r_calendar.cc
3 /// Blackberry database record parser class for calendar records.
4 ///
6 /*
7 Copyright (C) 2005-2013, 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 "i18n.h"
23 #include "r_calendar.h"
24 #include "r_recur_base-int.h"
25 #include "record-internal.h"
26 #include "protocol.h"
27 #include "protostructs.h"
28 #include "data.h"
29 #include "time.h"
30 #include "error.h"
31 #include "endian.h"
32 #include "iconv.h"
33 #include <ostream>
34 #include <iomanip>
35 #include <time.h>
36 #include <string.h>
37 #include <stdexcept>
38 #include "ios_state.h"
40 #define __DEBUG_MODE__
41 #include "debug.h"
43 using namespace std;
44 using namespace Barry::Protocol;
46 namespace Barry {
49 ///////////////////////////////////////////////////////////////////////////////
50 // Calendar class, static members
53 // Note! These functions currently only pass the same values through.
54 // In actuality, these are technically two different values:
55 // one on the raw protocol side, and the other part of the
56 // guaranteed Barry API. If the Blackberry ever changes the
57 // meanings for these codes, do the translation here.
60 Calendar::FreeBusyFlagType Calendar::FreeBusyFlagProto2Rec(uint8_t f)
62 return (FreeBusyFlagType)f;
65 uint8_t Calendar::FreeBusyFlagRec2Proto(FreeBusyFlagType f)
67 return f;
70 Calendar::ClassFlagType Calendar::ClassFlagProto2Rec(uint8_t f)
72 return (ClassFlagType)f;
75 uint8_t Calendar::ClassFlagRec2Proto(ClassFlagType f)
77 return f;
82 ///////////////////////////////////////////////////////////////////////////////
83 // Calendar class
85 // calendar field codes
86 #define CALFC_APPT_TYPE_FLAG 0x01
87 #define CALFC_SUBJECT 0x02
88 #define CALFC_NOTES 0x03
89 #define CALFC_LOCATION 0x04
90 #define CALFC_NOTIFICATION_TIME 0x05
91 #define CALFC_START_TIME 0x06
92 #define CALFC_END_TIME 0x07
93 #define CALFC_ACCEPTED_BY 0x0b
94 #define CALFC_VERSION_DATA 0x10
95 #define CALFC_INVITED 0x15
96 #define CALFC_ORGANIZER 0x16
97 #define CALFC_NOTIFICATION_DATA 0x1a
98 #define CALFC_FREEBUSY_FLAG 0x1c
99 #define CALFC_TIMEZONE_CODE 0x1e // only seems to show up if recurring
100 #define CALFC_CLASS_FLAG 0x28 // private flag from outlook
101 #define CALFC_CALENDAR_ID 0x2b // Calendar using (new devices have several calendar)
102 #define CALFC_ALLDAYEVENT_FLAG 0xff
103 #define CALFC_END 0xffff
105 static FieldLink<Calendar> CalendarFieldLinks[] = {
106 { CALFC_SUBJECT, N_("Subject"), 0, 0, &Calendar::Subject, 0, 0, 0, 0, true },
107 { CALFC_NOTES, N_("Notes"), 0, 0, &Calendar::Notes, 0, 0, 0, 0, true },
108 { CALFC_LOCATION, N_("Location"), 0, 0, &Calendar::Location, 0, 0, 0, 0, true },
109 { CALFC_NOTIFICATION_TIME,N_("Notification Time"),0,0, 0, 0, &Calendar::NotificationTime, 0, 0, false },
110 { CALFC_START_TIME, N_("Start Time"), 0, 0, 0, 0, &Calendar::StartTime, 0, 0, false },
111 { CALFC_END_TIME, N_("End Time"), 0, 0, 0, 0, &Calendar::EndTime, 0, 0, false },
112 { CALFC_ORGANIZER, N_("Organizer"), 0, 0, 0, &Calendar::Organizer, 0, 0, 0, true },
113 { CALFC_ACCEPTED_BY,N_("Accepted By"),0, 0, 0, &Calendar::AcceptedBy, 0, 0, 0, true },
114 { CALFC_INVITED, N_("Invited"), 0, 0, 0, &Calendar::Invited, 0, 0, 0, true },
115 { CALFC_END, N_("End of List"),0, 0, 0, 0, 0, 0, 0, false }
118 Calendar::Calendar()
120 Clear();
123 Calendar::~Calendar()
127 const unsigned char* Calendar::ParseField(const unsigned char *begin,
128 const unsigned char *end,
129 const IConverter *ic)
131 const CommonField *field = (const CommonField *) begin;
133 // advance and check size
134 begin += COMMON_FIELD_HEADER_SIZE + btohs(field->size);
135 if( begin > end ) // if begin==end, we are ok
136 return begin;
138 if( !btohs(field->size) ) // if field has no size, something's up
139 return begin;
141 // cycle through the type table
142 for( FieldLink<Calendar> *b = CalendarFieldLinks;
143 b->type != CALFC_END;
144 b++ )
146 if( b->type == field->type ) {
147 if( b->strMember ) {
148 std::string &s = this->*(b->strMember);
149 s = ParseFieldString(field);
150 if( b->iconvNeeded && ic )
151 s = ic->FromBB(s);
152 return begin; // done!
154 else if( b->timeMember && btohs(field->size) == 4 ) {
155 TimeT &t = this->*(b->timeMember);
156 dout("min1900: " << field->u.min1900);
157 t.Time = min2time(field->u.min1900);
158 return begin;
160 else if( b->addrMember ) {
162 // parse email address
163 // get dual addr+name string first
164 // Note: this is a different format than
165 // used in r_message*.cc
167 std::string dual((const char*)field->u.raw, btohs(field->size));
169 EmailAddress a;
171 // assign first string, using null terminator
172 // letting std::string add it for us if it
173 // doesn't exist
174 a.Email = dual.c_str();
176 // assign second string, using first size
177 // as starting point
178 a.Name = dual.c_str() + a.Email.size() + 1;
180 // if the address is non-empty, add to list
181 if( a.size() ) {
182 // i18n convert if needed
183 if( b->iconvNeeded && ic ) {
184 a.Name = ic->FromBB(a.Name);
185 a.Email = ic->FromBB(a.Email);
188 EmailAddressList &al = this->*(b->addrMember);
189 al.push_back(a);
192 return begin;
197 // handle special cases
198 switch( field->type )
200 case CALFC_APPT_TYPE_FLAG:
201 switch( field->u.raw[0] )
203 case 'a': // regular non-recurring appointment
204 Recurring = false;
205 return begin;
207 case '*': // recurring appointment
208 Recurring = true;
209 return begin;
211 default:
212 throw Error(_("Calendar::ParseField: unknown appointment type"));
214 break;
216 case CALFC_ALLDAYEVENT_FLAG:
217 AllDayEvent = field->u.raw[0] == 1;
218 return begin;
220 case CALFC_TIMEZONE_CODE:
221 if( btohs(field->size) == 2 ) {
222 // good data
223 TimeZoneCode = btohs(field->u.code);
224 TimeZoneValid = true;
226 else {
227 throw Error(_("Calendar::ParseField: not enough data in time zone code field"));
229 return begin;
231 case CALFC_FREEBUSY_FLAG:
232 if( field->u.raw[0] > CR_FREEBUSY_RANGE_HIGH ) {
233 throw Error(_("Calendar::ParseField: FreeBusyFlag out of range"));
235 FreeBusyFlag = FreeBusyFlagProto2Rec(field->u.raw[0]);
236 return begin;
238 case CALFC_CALENDAR_ID:
239 if( btohs(field->size) == 8 ) {
240 CalendarID = btohll(field->u.uint64);
242 else {
243 throw Error(_("Calendar::ParseField: size data unknown in calendar field"));
245 return begin;
247 case CALFC_CLASS_FLAG:
248 if( field->u.raw[0] > CR_CLASS_RANGE_HIGH ) {
249 throw Error(_("Calendar::ParseField: ClassFlag out of range" ));
251 ClassFlag = ClassFlagProto2Rec(field->u.raw[0]);
252 return begin;
255 // base class handles recurring data
256 if( RecurBase::ParseField(field->type, field->u.raw, btohs(field->size), ic) )
257 return begin;
259 // if still not handled, add to the Unknowns list
260 UnknownField uf;
261 uf.type = field->type;
262 uf.data.assign((const char*)field->u.raw, btohs(field->size));
263 Unknowns.push_back(uf);
265 // return new pointer for next field
266 return begin;
269 void Calendar::ParseHeader(const Data &data, size_t &offset)
271 // no header in Calendar records
274 void Calendar::ParseFields(const Data &data, size_t &offset, const IConverter *ic)
276 const unsigned char *finish = ParseCommonFields(*this,
277 data.GetData() + offset, data.GetData() + data.GetSize(), ic);
278 offset += finish - (data.GetData() + offset);
281 void Calendar::Validate() const
283 RecurBase::Validate();
286 void Calendar::BuildHeader(Data &data, size_t &offset) const
288 // no header in Calendar records
292 // Build
294 /// Build fields part of record.
296 void Calendar::BuildFields(Data &data, size_t &offset, const IConverter *ic) const
298 data.Zap();
300 // output the type first
301 BuildField(data, offset, CALFC_APPT_TYPE_FLAG, Recurring ? '*' : 'a');
303 // output all day event flag only if set
304 if( AllDayEvent )
305 BuildField(data, offset, CALFC_ALLDAYEVENT_FLAG, (char)1);
307 // cycle through the type table
308 for( const FieldLink<Calendar> *b = CalendarFieldLinks;
309 b->type != CALFC_END;
310 b++ )
312 if( b->strMember ) {
313 const std::string &s = this->*(b->strMember);
314 if( s.size() )
315 BuildField(data, offset, b->type, (b->iconvNeeded && ic) ? ic->ToBB(s) : s);
317 else if( b->timeMember ) {
318 TimeT t = this->*(b->timeMember);
319 if( t.Time > 0 )
320 BuildField1900(data, offset, b->type, t.Time);
322 else if( b->addrMember ) {
323 const EmailAddressList &al = this->*(b->addrMember);
324 EmailAddressList::const_iterator lb = al.begin(), le = al.end();
326 // add all entries in list
327 for( ; lb != le; ++lb ) {
329 // skip empty entries
330 if( !lb->size() )
331 continue;
333 std::string Name = lb->Name,
334 Email = lb->Email;
336 // do i18n conversion only if needed
337 if( b->iconvNeeded && ic ) {
338 Name = ic->ToBB(Name);
339 Email = ic->ToBB(Email);
343 // Build an addr+name field, each string
344 // null terminated.
345 // Note: this is a different format than
346 // what is used in r_message*.cc
348 std::string field(lb->Email.c_str(), lb->Email.size() + 1);
349 field.append(lb->Name.c_str(), lb->Name.size() + 1);
350 BuildField(data, offset, b->type, field.data(), field.size());
355 // handle special cases
356 if( Recurring ) {
357 CalendarRecurrenceDataField recur;
358 BuildRecurrenceData(StartTime.Time, &recur);
359 BuildField(data, offset, RecurBase::RecurringFieldType(),
360 &recur, CALENDAR_RECURRENCE_DATA_FIELD_SIZE);
363 if( TimeZoneValid )
364 BuildField(data, offset, CALFC_TIMEZONE_CODE, TimeZoneCode);
366 BuildField(data, offset, CALFC_FREEBUSY_FLAG, FreeBusyFlagRec2Proto(FreeBusyFlag));
367 BuildField(data, offset, CALFC_CLASS_FLAG, ClassFlagRec2Proto(ClassFlag));
369 // If CalendarID is defined and most of supported !
370 // (by default 0xffff ffff ffff ffff)
371 if( CalendarID != (uint64_t) -1 )
372 BuildField(data, offset, CALFC_CALENDAR_ID, CalendarID);
374 // and finally save unknowns
375 UnknownsType::const_iterator
376 ub = Unknowns.begin(), ue = Unknowns.end();
377 for( ; ub != ue; ub++ ) {
378 BuildField(data, offset, *ub);
381 data.ReleaseBuffer(offset);
384 void Calendar::Clear()
386 // clear the base class too
387 RecurBase::Clear();
389 // clear our fields
390 RecType = GetDefaultRecType();
391 RecordId = 0;
393 AllDayEvent = false;
394 Subject.clear();
395 Notes.clear();
396 Location.clear();
397 NotificationTime.clear();
398 StartTime.clear();
399 EndTime.clear();
400 Organizer.clear();
401 AcceptedBy.clear();
402 Invited.clear();
404 FreeBusyFlag = Free;
405 ClassFlag = Public;
407 CalendarID = btohll((uint64_t) -1);
409 TimeZoneCode = GetStaticTimeZoneCode(0, 0); // default to GMT
410 TimeZoneValid = false;
412 Unknowns.clear();
415 const FieldHandle<Calendar>::ListT& Calendar::GetFieldHandles()
417 static FieldHandle<Calendar>::ListT fhv;
419 if( fhv.size() )
420 return fhv;
422 #undef CONTAINER_OBJECT_NAME
423 #define CONTAINER_OBJECT_NAME fhv
425 #undef RECORD_CLASS_NAME
426 #define RECORD_CLASS_NAME Calendar
428 #define ALL_COMMON_CALENDAR_FIELDS \
429 FHP(RecType, _("Record Type Code")); \
430 FHP(RecordId, _("Unique Record ID")); \
432 FHP(AllDayEvent, _("All Day Event")); \
433 FHD(Subject, _("Subject"), CALFC_SUBJECT, true); \
434 FHD(Notes, _("Notes"), CALFC_NOTES, true); \
435 FHD(Location, _("Location"), CALFC_LOCATION, true); \
436 FHD(NotificationTime, _("Notification Time (0 is off)"), \
437 CALFC_NOTIFICATION_TIME, false); \
438 FHD(StartTime, _("Start Time"), CALFC_START_TIME, false); \
439 FHD(EndTime, _("End Time"), CALFC_END_TIME, false); \
440 FHD(Organizer, _("Organizer"), CALFC_ORGANIZER, true); \
441 FHD(AcceptedBy, _("Accepted By"), CALFC_ACCEPTED_BY, true); \
442 FHD(Invited, _("Invited"), CALFC_INVITED, true); \
444 FHE(fbf, FreeBusyFlagType, FreeBusyFlag, _("Free or Busy Flag")); \
445 FHE_CONST(fbf, Free, _("Free")); \
446 FHE_CONST(fbf, Tentative, _("Tentative")); \
447 FHE_CONST(fbf, Busy, _("Busy")); \
448 FHE_CONST(fbf, OutOfOffice, _("Out of Office")); \
450 FHE(cf, ClassFlagType, ClassFlag, _("Event Class")); \
451 FHE_CONST(cf, Public, _("Public")); \
452 FHE_CONST(cf, Confidential, _("Confidential")); \
453 FHE_CONST(cf, Private, _("Private")); \
455 FHP(TimeZoneCode, _("Time Zone Code")); \
456 FHP(TimeZoneValid, _("Time Zone Validity")); \
458 FHP(Unknowns, _("Unknown Fields"));
460 ALL_COMMON_CALENDAR_FIELDS
462 // the fields unique to Calendar, or different in CalendarALL
463 FHD(CalendarID, _("Calendar ID"), CALFC_CALENDAR_ID, false);
465 // and finally, the RecurBase fields
466 RECUR_BASE_FIELD_HANDLES
468 return fhv;
471 std::string Calendar::GetDescription() const
473 return Subject;
476 void Calendar::DumpSpecialFields(std::ostream &os) const
478 ios_format_state state(os);
480 static const char *ClassTypes[] = {
481 N_("Public"),
482 N_("Confidential"),
483 N_("Private")
485 static const char *FreeBusy[] = {
486 N_("Free"),
487 N_("Tentative"),
488 N_("Busy"),
489 N_("Out of Office")
492 os << _(" Calendar ID: ")
493 << "0x" << setbase(16) << CalendarID << "\n";
494 os << _(" All Day Event: ") << (AllDayEvent ? "yes" : "no") << "\n";
495 os << _(" Class: ") << ClassTypes[ClassFlag] << "\n";
496 os << _(" Free/Busy: ") << FreeBusy[FreeBusyFlag] << "\n";
497 if( TimeZoneValid )
498 os << _(" Time Zone: ") << gettext(GetStaticTimeZone(TimeZoneCode)->Name) << "\n";
501 void Calendar::Dump(std::ostream &os) const
503 ios_format_state state(os);
505 // FIXME - need a "check all data" function that make sure that all
506 // recurrence data is within range. Then call that before using
507 // the data, such as in Build and in Dump.
509 os << _("Calendar entry: ") << "0x" << setbase(16) << RecordId
510 << " (" << (unsigned int)RecType << ")\n";
511 DumpSpecialFields(os);
513 // cycle through the type table
514 for( const FieldLink<Calendar> *b = CalendarFieldLinks;
515 b->type != CALFC_END;
516 b++ )
518 if( b->strMember ) {
519 const std::string &s = this->*(b->strMember);
520 if( s.size() )
521 os << " " << gettext(b->name) << ": " << s << "\n";
523 else if( b->timeMember ) {
524 TimeT t = this->*(b->timeMember);
525 if( t.Time > 0 )
526 os << " " << gettext(b->name) << ": " << t << "\n";
527 else
528 os << " " << gettext(b->name) << ": " << _("disabled") << "\n";
530 else if( b->addrMember ) {
531 const EmailAddressList &al = this->*(b->addrMember);
532 EmailAddressList::const_iterator lb = al.begin(), le = al.end();
534 for( ; lb != le; ++lb ) {
535 if( !lb->size() )
536 continue;
538 os << " " << gettext(b->name) << ": " << *lb << "\n";
543 // print recurrence data if available
544 RecurBase::Dump(os);
546 // print any unknowns
547 os << Unknowns;
550 bool Calendar::operator<(const Calendar &other) const
552 if( StartTime < other.StartTime )
553 return true;
554 else if( other.StartTime < StartTime )
555 return false;
557 // times are equal, so secondary sort based on Subject + Location
558 int cmp = Subject.compare(other.Subject);
559 if( cmp == 0 )
560 cmp = Location.compare(other.Location);
561 return cmp < 0;
565 ///////////////////////////////////////////////////////////////////////////////
566 // Calendar-All class
568 // calendar-all field codes
569 #define CALALLFC_CALENDAR_ID 0x02 // Calendar using (new devices have several calendar)
570 #define CALALLFC_MAIL_ACCOUNT 0x03
571 #define CALALLFC_UNIQUEID 0x05
572 #define CALALLFC_CAL_OBJECT 0x0a
573 #define CALALLFC_END 0xffff
575 void CalendarAll::Clear()
577 Calendar::Clear();
579 MailAccount.clear();
582 const FieldHandle<CalendarAll>::ListT& CalendarAll::GetFieldHandles()
584 static FieldHandle<CalendarAll>::ListT fhv;
586 if( fhv.size() )
587 return fhv;
589 #undef CONTAINER_OBJECT_NAME
590 #define CONTAINER_OBJECT_NAME fhv
592 #undef RECORD_CLASS_NAME
593 #define RECORD_CLASS_NAME CalendarAll
595 ALL_COMMON_CALENDAR_FIELDS
597 // Calendar:: field, but with a CalendarAll ID
598 FHD(CalendarID, _("Calendar ID"), CALALLFC_CALENDAR_ID, false);
600 // add the fields specific to CalendarAll
601 FHD(MailAccount, _("Mail Account"), CALALLFC_MAIL_ACCOUNT, true);
603 // and finally, the RecurBase fields
604 RECUR_BASE_FIELD_HANDLES
606 return fhv;
609 void CalendarAll::ParseHeader(const Data &data, size_t &offset)
611 const unsigned char *b = (const unsigned char*) (data.GetData() + offset);
612 const unsigned char *e = (const unsigned char*) (data.GetData() + data.GetSize());
614 while( (b + COMMON_FIELD_HEADER_SIZE) < e ) {
615 const CommonField *field = (const CommonField *) b;
617 // advance and check size
618 b += COMMON_FIELD_HEADER_SIZE + btohs(field->size);
619 if( b > e ) // if begin==end, we are ok
620 continue;
622 if( !btohs(field->size) ) // if field has no size, something's up
623 continue;
625 // handle special cases
626 if( field->type == CALALLFC_CAL_OBJECT )
628 b -= btohs(field->size);
629 // end of header
630 break;
633 switch( field->type )
635 case CALALLFC_CALENDAR_ID:
636 if( btohs(field->size) == 8 ) {
637 CalendarID = btohll(field->u.uint64);
639 else {
640 throw Error(_("CalendarAll::ParseField: size data unknown in calendar field"));
642 continue;
644 case CALALLFC_MAIL_ACCOUNT:
645 MailAccount = ParseFieldString(field);
646 continue;
648 case CALALLFC_UNIQUEID:
649 if( btohs(field->size) == 4 ) {
650 RecordId = btohl(field->u.uint32);
652 else {
653 throw Error(_("CalendarAll::ParseHeader: size data unknown in calendar field"));
655 continue;
658 // if still not handled, add to the Unknowns list
659 UnknownField uf;
660 uf.type = field->type;
661 uf.data.assign((const char*)field->u.raw, btohs(field->size));
662 Unknowns.push_back(uf);
665 offset += b - (data.GetData() + offset);
668 void CalendarAll::DumpSpecialFields(std::ostream &os) const
670 ios_format_state state(os);
672 Calendar::DumpSpecialFields(os);
673 os << _(" Mail Account: ") << MailAccount << "\n";
676 } // namespace Barry