Page MenuHomePhorge

kolabkcalconversion.cpp
No OneTemporary

Authored By
Unknown
Size
24 KB
Referenced Files
None
Subscribers
None

kolabkcalconversion.cpp

/*
* Copyright (C) 2011 Christian Mollekopf <mollekopf@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "kolabkcalconversion.h"
#include <kcalcore/recurrence.h>
#include <QtCore/QBitArray>
#include <QtCore/QVector>
#include <QtCore/QDebug>
#include <vector>
#include <KDE/KSystemTimeZones>
namespace Kolab {
namespace KCalConversion {
KDateTime toDate(const Kolab::DateTime &dt)
{
KDateTime date;
if (!dt.isValid()) {
// qDebug() << "invalid datetime converted";
return KDateTime();
}
if (dt.isDateOnly()) { //Date only
date.setDateOnly(true);
date.setDate(QDate(dt.year(), dt.month(), dt.day()));
date.setTimeSpec(KDateTime::Spec(KDateTime::ClockTime));
} else {
date.setDate(QDate(dt.year(), dt.month(), dt.day()));
date.setTime(QTime(dt.hour(), dt.minute(), dt.second()));
if (dt.isUTC()) { //UTC
date.setTimeSpec(KDateTime::Spec(KDateTime::UTC));
} else if (!dt.timezone().empty()) { //Timezone
const KTimeZone &tz = KSystemTimeZones::zone(QString::fromStdString(dt.timezone())); //Needs ktimezoned (timezone daemon running) http://api.kde.org/4.x-api/kdelibs-apidocs/kdecore/html/classKSystemTimeZones.html
if (!tz.isValid()) {
qWarning() << "timezone not found" << QString::fromStdString(dt.timezone());
if (!KSystemTimeZones::isTimeZoneDaemonAvailable()) {
qWarning() << "ktimezoned is not available and required for timezone interpretation";
}
}
date.setTimeSpec(KDateTime::Spec(tz));
} else { //Floating
date.setTimeSpec(KDateTime::Spec(KDateTime::ClockTime));
}
}
Q_ASSERT(date.timeSpec().isValid());
Q_ASSERT(date.isValid());
return date;
}
DateTime fromDate(const KDateTime &dt)
{
if (!dt.isValid()) {
// qDebug() << "invalid datetime converted";
return DateTime();
}
DateTime date;
if (dt.isDateOnly()) { //Date only
const QDate &d = dt.date();
date.setDate(d.year(), d.month(), d.day());
} else {
const QDate &d = dt.date();
date.setDate(d.year(), d.month(), d.day());
const QTime &t = dt.time();
date.setTime(t.hour(), t.minute(), t.second());
if (dt.timeType() == KDateTime::UTC) { //UTC
date.setUTC(true);
} else if (dt.timeType() == KDateTime::TimeZone) { //Timezone
//TODO handle local timezone?
date.setTimezone(dt.timeZone().name().toStdString()); //FIXME use system independent name according to spec
} else if (dt.timeType() != KDateTime::ClockTime) {
qWarning() << "invalid timespec, assuming floating time" << dt.timeType();
return DateTime();
}
}
Q_ASSERT(date.isValid());
return date;
}
KCalCore::Duration toDuration(const Kolab::Duration &d)
{
//TODO should we support negative durations?
if (d.hours() || d.minutes() || d.seconds()) {
return KCalCore::Duration(((((d.weeks() * 7 + d.days()) * 24 + d.hours()) * 60 + d.minutes()) * 60 + d.seconds()));
}
return KCalCore::Duration(d.weeks() * 7 + d.days(), KCalCore::Duration::Days);
}
Kolab::Duration fromDuration(const KCalCore::Duration &d)
{
if (d.value() <= 0) {
return Kolab::Duration();
}
//We don't know how the seconds/days were distributed before, so no point in distributing them (probably)
//TODO should we support negative durations?
if (d.isDaily()) {
int days = d.value();
return Kolab::Duration(days, 0, 0, 0, false);
}
int seconds = d.value();
// int minutes = seconds / 60;
// seconds = seconds % 60;
// int hours = minutes / 60;
// minutes = minutes % 60;
return Kolab::Duration(0, 0, 0, seconds, false);
}
KCalCore::Incidence::Secrecy toSecrecy(Kolab::Classification c)
{
switch(c) {
case Kolab::ClassPublic:
return KCalCore::Incidence::SecrecyPublic;
case Kolab::ClassPrivate:
return KCalCore::Incidence::SecrecyPrivate;
case Kolab::ClassConfidential:
return KCalCore::Incidence::SecrecyConfidential;
default:
qWarning() << "unhandled";
Q_ASSERT(0);
}
return KCalCore::Incidence::SecrecyPublic;
}
Kolab::Classification fromSecrecy(KCalCore::Incidence::Secrecy c)
{
switch(c) {
case KCalCore::Incidence::SecrecyPublic:
return Kolab::ClassPublic;
case KCalCore::Incidence::SecrecyPrivate:
return Kolab::ClassPrivate;
case KCalCore::Incidence::SecrecyConfidential:
return Kolab::ClassConfidential;
default:
qWarning() << "unhandled";
Q_ASSERT(0);
}
return Kolab::ClassPublic;
}
int toPriority(int priority)
{
//Same mapping
return priority;
}
int fromPriority(int priority)
{
//Same mapping
return priority;
}
KCalCore::Incidence::Status toStatus(Kolab::Status s)
{
switch (s) {
case StatusUndefined:
return KCalCore::Incidence::StatusNone;
case StatusNeedsAction:
return KCalCore::Incidence::StatusNeedsAction;
case StatusCompleted:
return KCalCore::Incidence::StatusCompleted;
case StatusInProcess:
return KCalCore::Incidence::StatusInProcess;
case StatusCancelled:
return KCalCore::Incidence::StatusCanceled;
case StatusTentative:
return KCalCore::Incidence::StatusTentative;
case StatusConfirmed:
return KCalCore::Incidence::StatusConfirmed;
case StatusDraft:
return KCalCore::Incidence::StatusDraft;
case StatusFinal:
return KCalCore::Incidence::StatusFinal;
default:
qWarning() << "unhandled";
Q_ASSERT(0);
}
return KCalCore::Incidence::StatusNone;
}
Kolab::Status fromStatus(KCalCore::Incidence::Status s)
{
switch (s) {
case KCalCore::Incidence::StatusNone:
return StatusUndefined;
case KCalCore::Incidence::StatusNeedsAction:
return StatusNeedsAction;
case KCalCore::Incidence::StatusCompleted:
return StatusCompleted;
case KCalCore::Incidence::StatusInProcess:
return StatusInProcess;
case KCalCore::Incidence::StatusCanceled:
return StatusCancelled;
case KCalCore::Incidence::StatusTentative:
return StatusTentative;
case KCalCore::Incidence::StatusConfirmed:
return StatusConfirmed;
case KCalCore::Incidence::StatusDraft:
return StatusDraft;
case KCalCore::Incidence::StatusFinal:
return StatusFinal;
default:
qWarning() << "unhandled";
Q_ASSERT(0);
}
return StatusUndefined;
}
QStringList toStringList(const std::vector<std::string> &l)
{
QStringList list;
foreach(const std::string &s, l) {
list.append(QString::fromStdString(s));
}
return list;
}
std::vector<std::string> fromStringList(const QStringList &l)
{
std::vector<std::string> list;
foreach(const QString &s, l) {
list.push_back(s.toStdString());
}
return list;
}
KCalCore::Attendee::PartStat toPartStat(Kolab::PartStatus p)
{
switch (p) {
case PartNeedsAction:
return KCalCore::Attendee::NeedsAction;
case PartAccepted:
return KCalCore::Attendee::Accepted;
case PartDeclined:
return KCalCore::Attendee::Declined;
case PartTentative:
return KCalCore::Attendee::Tentative;
case PartDelegated:
return KCalCore::Attendee::Delegated;
default:
qWarning() << "unhandled";
Q_ASSERT(0);
}
return KCalCore::Attendee::NeedsAction;
}
Kolab::PartStatus fromPartStat(KCalCore::Attendee::PartStat p)
{
switch (p) {
case KCalCore::Attendee::NeedsAction:
return PartNeedsAction;
case KCalCore::Attendee::Accepted:
return PartAccepted;
case KCalCore::Attendee::Declined:
return PartDeclined;
case KCalCore::Attendee::Tentative:
return PartTentative;
case KCalCore::Attendee::Delegated:
return PartDelegated;
default:
qWarning() << "unhandled";
Q_ASSERT(0);
}
return PartNeedsAction;
}
KCalCore::Attendee::Role toRole(Kolab::Role r)
{
switch (r) {
case Required:
return KCalCore::Attendee::ReqParticipant;
case Chair:
return KCalCore::Attendee::Chair;
case Optional:
return KCalCore::Attendee::OptParticipant;
case NonParticipant:
return KCalCore::Attendee::NonParticipant;
default:
qWarning() << "unhandled";
Q_ASSERT(0);
}
return KCalCore::Attendee::ReqParticipant;
}
Kolab::Role fromRole(KCalCore::Attendee::Role r)
{
switch (r) {
case KCalCore::Attendee::ReqParticipant:
return Required;
case KCalCore::Attendee::Chair:
return Chair;
case KCalCore::Attendee::OptParticipant:
return Optional;
case KCalCore::Attendee::NonParticipant:
return NonParticipant;
default:
qWarning() << "unhandled";
Q_ASSERT(0);
}
return Required;
}
template <typename T>
void setIncidence(KCalCore::Incidence &i, const T &e)
{
if (!e.uid().empty()) {
i.setUid(QString::fromStdString(e.uid()));
}
i.setCreated(toDate(e.created()));
i.setLastModified(toDate(e.lastModified()));
i.setRevision(e.sequence());
i.setSecrecy(toSecrecy(e.classification()));
i.setCategories(toStringList(e.categories()));
if (e.start().isValid()) {
i.setDtStart(toDate(e.start()));
}
i.setSummary(QString::fromStdString(e.summary())); //TODO detect richtext
i.setDescription(QString::fromStdString(e.description())); //TODO detect richtext
i.setStatus(toStatus(e.status()));
foreach (const Kolab::Attendee a, e.attendees()) {
i.addAttendee(KCalCore::Attendee::Ptr(new KCalCore::Attendee(QString::fromStdString(a.name()),
QString::fromStdString(a.email()),
a.rsvp(),
toPartStat(a.partStat()),
toRole(a.role()),
QString::fromStdString(a.uid()) )));
}
foreach (const Kolab::Attachment a, e.attachments()) {
KCalCore::Attachment::Ptr ptr;
if (!a.uri().empty()) {
ptr = KCalCore::Attachment::Ptr(new KCalCore::Attachment(QString::fromStdString(a.uri()), QString::fromStdString(a.mimetype())));
} else {
ptr = KCalCore::Attachment::Ptr(new KCalCore::Attachment(QByteArray::fromRawData(a.data().c_str(), a.data().size()), QString::fromStdString(a.mimetype())));
}
if (!a.label().empty()) {
ptr->setLabel(QString::fromStdString(a.label()));
}
i.addAttachment(ptr);
}
// i.addAlarm(); //TODO
// i.setCustomProperties(); //TODO
}
template <typename T, typename I>
void getIncidence(T &i, const I &e)
{
i.setUid(e.uid().toStdString());
i.setCreated(fromDate(e.created()));
i.setLastModified(fromDate(e.lastModified()));
i.setSequence(e.revision());
i.setClassification(fromSecrecy(e.secrecy()));
i.setCategories(fromStringList(e.categories()));
i.setStart(fromDate(e.dtStart()));
i.setSummary(e.summary().toStdString());
i.setDescription(e.description().toStdString());
i.setStatus(fromStatus(e.status()));
std::vector<Kolab::Attendee> attendees;
foreach (const KCalCore::Attendee::Ptr ptr, e.attendees()) {
Kolab::Attendee a(ptr->email().toStdString());
a.setName(ptr->name().toStdString());
a.setRSVP(ptr->RSVP());
a.setPartStat(fromPartStat(ptr->status()));
a.setRole(fromRole(ptr->role()));
a.setUid(ptr->uid().toStdString());
attendees.push_back(a);
}
i.setAttendees(attendees);
std::vector<Kolab::Attachment> attachments;
foreach (const KCalCore::Attachment::Ptr ptr, e.attachments()) {
Kolab::Attachment a;
if (ptr->isUri()) {
a.setUri(ptr->uri().toStdString(), ptr->mimeType().toStdString());
} else {
a.setData(std::string(ptr->decodedData().data(), ptr->decodedData().size()), ptr->mimeType().toStdString());
}
a.setLabel(ptr->label().toStdString());
attachments.push_back(a);
}
i.setAttachments(attachments);
//TODO alarms
//TODO custom properties
}
int toWeekDay(Kolab::Weekday wday)
{
switch (wday) {
case Kolab::Monday:
return 1;
case Kolab::Tuesday:
return 2;
case Kolab::Wednesday:
return 3;
case Kolab::Thursday:
return 4;
case Kolab::Friday:
return 5;
case Kolab::Saturday:
return 6;
case Kolab::Sunday:
return 7;
default:
qWarning() << "unhandled";
Q_ASSERT(0);
}
return 1;
}
Kolab::Weekday fromWeekDay(int wday)
{
switch (wday) {
case 1:
return Kolab::Monday;
case 2:
return Kolab::Tuesday;
case 3:
return Kolab::Wednesday;
case 4:
return Kolab::Thursday;
case 5:
return Kolab::Friday;
case 6:
return Kolab::Saturday;
case 7:
return Kolab::Sunday;
default:
qWarning() << "unhandled";
Q_ASSERT(0);
}
return Kolab::Monday;
}
KCalCore::RecurrenceRule::PeriodType toRecurrenceType(Kolab::RecurrenceRule::Frequency freq)
{
switch(freq) {
case Kolab::RecurrenceRule::FreqNone:
qWarning() << "no recurrence?";
break;
case Kolab::RecurrenceRule::Yearly:
return KCalCore::RecurrenceRule::rYearly;
case Kolab::RecurrenceRule::Monthly:
return KCalCore::RecurrenceRule::rMonthly;
case Kolab::RecurrenceRule::Weekly:
return KCalCore::RecurrenceRule::rWeekly;
case Kolab::RecurrenceRule::Daily:
return KCalCore::RecurrenceRule::rDaily;
case Kolab::RecurrenceRule::Hourly:
return KCalCore::RecurrenceRule::rHourly;
case Kolab::RecurrenceRule::Minutely:
return KCalCore::RecurrenceRule::rMinutely;
case Kolab::RecurrenceRule::Secondly:
return KCalCore::RecurrenceRule::rSecondly;
default:
qWarning() << "unhandled";
Q_ASSERT(0);
}
return KCalCore::RecurrenceRule::rNone;
}
Kolab::RecurrenceRule::Frequency fromRecurrenceType(KCalCore::RecurrenceRule::PeriodType freq)
{
switch(freq) {
case KCalCore::RecurrenceRule::rNone:
qWarning() << "no recurrence?";
break;
case KCalCore::RecurrenceRule::rYearly:
return Kolab::RecurrenceRule::Yearly;
case KCalCore::RecurrenceRule::rMonthly:
return Kolab::RecurrenceRule::Monthly;
case KCalCore::RecurrenceRule::rWeekly:
return Kolab::RecurrenceRule::Weekly;
case KCalCore::RecurrenceRule::rDaily:
return Kolab::RecurrenceRule::Daily;
case KCalCore::RecurrenceRule::rHourly:
return Kolab::RecurrenceRule::Hourly;
case KCalCore::RecurrenceRule::rMinutely:
return Kolab::RecurrenceRule::Minutely;
case KCalCore::RecurrenceRule::rSecondly:
return Kolab::RecurrenceRule::Secondly;
default:
qWarning() << "unhandled";
Q_ASSERT(0);
}
return Kolab::RecurrenceRule::FreqNone;
}
KCalCore::RecurrenceRule::WDayPos toWeekDayPos(const Kolab::DayPos &dp)
{
return KCalCore::RecurrenceRule::WDayPos(dp.occurence(), toWeekDay(dp.weekday()));
}
Kolab::DayPos fromWeekDayPos(const KCalCore::RecurrenceRule::WDayPos &dp)
{
return Kolab::DayPos(dp.pos(), fromWeekDay(dp.day()));
}
template <typename T>
void setRecurrence(KCalCore::Incidence &e, const T &event)
{
const Kolab::RecurrenceRule &rrule = event.recurrenceRule();
if (rrule.isValid()) {
KCalCore::Recurrence *rec = e.recurrence();
KCalCore::RecurrenceRule *defaultRR = rec->defaultRRule(true);
Q_ASSERT(defaultRR);
defaultRR->setWeekStart(toWeekDay(rrule.weekStart()));
defaultRR->setRecurrenceType(toRecurrenceType(rrule.frequency()));
defaultRR->setFrequency(rrule.interval());
if (rrule.end().isValid()) {
rec->setEndDateTime(toDate(rrule.end())); //TODO date/datetime setEndDate(). With date-only the start date has to be taken into account.
} else {
rec->setDuration(rrule.count());
}
if (!rrule.bysecond().empty()) {
defaultRR->setBySeconds(QVector<int>::fromStdVector(rrule.bysecond()).toList());
}
if (!rrule.byminute().empty()) {
defaultRR->setByMinutes(QVector<int>::fromStdVector(rrule.byminute()).toList());
}
if (!rrule.byhour().empty()) {
defaultRR->setByHours(QVector<int>::fromStdVector(rrule.byhour()).toList());
}
if (!rrule.byday().empty()) {
QList<KCalCore::RecurrenceRule::WDayPos> daypos;
foreach(const Kolab::DayPos &dp, rrule.byday()) {
daypos.append(toWeekDayPos(dp));
}
defaultRR->setByDays(daypos);
}
if (!rrule.bymonthday().empty()) {
defaultRR->setByMonthDays(QVector<int>::fromStdVector(rrule.bymonthday()).toList());
}
if (!rrule.byyearday().empty()) {
defaultRR->setByYearDays(QVector<int>::fromStdVector(rrule.byyearday()).toList());
}
if (!rrule.byweekno().empty()) {
defaultRR->setByWeekNumbers(QVector<int>::fromStdVector(rrule.byweekno()).toList());
}
if (!rrule.bymonth().empty()) {
defaultRR->setByMonths(QVector<int>::fromStdVector(rrule.bymonth()).toList());
}
}
foreach (const Kolab::DateTime &dt, event.recurrenceDates()) {
const KDateTime &date = toDate(dt);
if (date.isDateOnly()) {
e.recurrence()->addRDate(date.date());
} else {
e.recurrence()->addRDateTime(date);
}
}
foreach (const Kolab::DateTime &dt, event.exceptionDates()) {
const KDateTime &date = toDate(dt);
if (date.isDateOnly()) {
e.recurrence()->addExDate(date.date());
} else {
e.recurrence()->addExDateTime(date);
}
}
}
template <typename T, typename I>
void getRecurrence(T &i, const I &e)
{
if (!e.recurs()) {
return;
}
KCalCore::Recurrence *rec = e.recurrence();
KCalCore::RecurrenceRule *defaultRR = rec->defaultRRule(false);
if (!defaultRR) {
qWarning() << "no recurrence";
return;
}
Q_ASSERT(defaultRR);
Kolab::RecurrenceRule rrule;
rrule.setWeekStart(fromWeekDay(defaultRR->weekStart()));
rrule.setFrequency(fromRecurrenceType(defaultRR->recurrenceType()));
rrule.setInterval(defaultRR->frequency());
if (defaultRR->duration() != 0) { //Inidcates if end date is set or not
if (defaultRR->duration() > 0) {
rrule.setCount(defaultRR->duration());
}
} else {
rrule.setEnd(fromDate(defaultRR->endDt()));
}
rrule.setBysecond(defaultRR->bySeconds().toVector().toStdVector());
rrule.setByminute(defaultRR->byMinutes().toVector().toStdVector());
rrule.setByhour(defaultRR->byHours().toVector().toStdVector());
std::vector<Kolab::DayPos> daypos;
foreach (const KCalCore::RecurrenceRule::WDayPos &dp, defaultRR->byDays()) {
daypos.push_back(fromWeekDayPos(dp));
}
rrule.setByday(daypos);
rrule.setBymonthday(defaultRR->byMonthDays().toVector().toStdVector());
rrule.setByyearday(defaultRR->byYearDays().toVector().toStdVector());
rrule.setByweekno(defaultRR->byWeekNumbers().toVector().toStdVector());
rrule.setBymonth(defaultRR->byMonths().toVector().toStdVector());
i.setRecurrenceRule(rrule);
std::vector<Kolab::DateTime> rdates;
foreach (const KDateTime &dt, rec->rDateTimes()) {
rdates.push_back(fromDate(dt));
}
foreach (const QDate &dt, rec->rDates()) {
rdates.push_back(fromDate(KDateTime(dt)));
}
i.setRecurrenceDates(rdates);
std::vector<Kolab::DateTime> exdates;
foreach (const KDateTime &dt, rec->exDateTimes()) {
exdates.push_back(fromDate(dt));
}
foreach (const QDate &dt, rec->exDates()) {
exdates.push_back(fromDate(KDateTime(dt)));
}
i.setExceptionDates(exdates);
if (!rec->exRules().empty()) {
qWarning() << "exrules are not supported";
}
}
template <typename T>
void setTodoEvent(KCalCore::Incidence &i, const T &e)
{
i.setPriority(toPriority(e.priority()));
i.setLocation(QString::fromStdString(e.location())); //TODO detect richtext
i.setOrganizer(KCalCore::Person::Ptr(new KCalCore::Person(QString::fromStdString(e.organizerName()), QString::fromStdString(e.organizerEmail()))));
if (e.recurrenceID().isValid()) {
i.setRecurrenceId(toDate(e.recurrenceID())); //TODO THISANDFUTURE
}
setRecurrence(i, e);
}
template <typename T, typename I>
void getTodoEvent(T &i, const I &e)
{
i.setPriority(fromPriority(e.priority()));
i.setLocation(e.location().toStdString());
i.setOrganizer(e.organizer()->email().toStdString(),e.organizer()->name().toStdString());
i.setRecurrenceID(fromDate(e.recurrenceId()), false); //TODO THISANDFUTURE
getRecurrence(i, e);
}
KCalCore::Event::Ptr toKCalCore(const Kolab::Event &event)
{
KCalCore::Event::Ptr e(new KCalCore::Event);
setIncidence(*e, event);
setTodoEvent(*e, event);
if (event.end().isValid()) {
e->setDtEnd(toDate(event.end()));
}
if (event.duration().isValid()) {
e->setDuration(toDuration(event.duration()));
}
if (event.transparency()) {
e->setTransparency(KCalCore::Event::Transparent);
} else {
e->setTransparency(KCalCore::Event::Opaque);
}
return e;
}
Event fromKCalCore(const KCalCore::Event &event)
{
Event e;
getIncidence(e, event);
getTodoEvent(e, event);
if (event.hasEndDate()) {
e.setEnd(fromDate(event.dtEnd()));
} else if (event.hasDuration()) {
e.setDuration(fromDuration(event.duration()));
}
if (event.transparency() == KCalCore::Event::Transparent) {
e.setTransparency(true);
} else {
e.setTransparency(false);
}
return e;
}
KCalCore::Todo::Ptr toKCalCore ( const Todo &todo )
{
KCalCore::Todo::Ptr e(new KCalCore::Todo);
setIncidence(*e, todo);
setTodoEvent(*e, todo);
if (todo.due().isValid()) {
e->setDtDue(toDate(todo.due()));
}
return e;
}
Todo fromKCalCore ( const KCalCore::Todo &todo )
{
Todo t;
getIncidence(t, todo);
getTodoEvent(t, todo);
t.setDue(fromDate(todo.dtDue(true)));
return t;
}
KCalCore::Journal::Ptr toKCalCore ( const Journal &journal )
{
KCalCore::Journal::Ptr e(new KCalCore::Journal);
setIncidence(*e, journal);
//TODO contacts
return e;
}
Journal fromKCalCore ( const KCalCore::Journal &journal )
{
Journal j;
getIncidence(j, journal);
//TODO contacts
return j;
}
}
}

File Metadata

Mime Type
text/x-c++
Expires
Sat, Apr 4, 8:48 AM (2 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18744234
Default Alt Text
kolabkcalconversion.cpp (24 KB)

Event Timeline