1/*
2 This file is part of the kcalcore library.
3
4 Copyright (c) 2002,2006,2010 David Jarvie <djarvie@kde.org>
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Library General Public License for more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
20*/
21/**
22 @file
23 This file is part of the API for handling calendar data and
24 defines the CustomProperties class.
25
26 @brief
27 A class to manage custom calendar properties.
28
29 @author David Jarvie \<djarvie@kde.org\>
30*/
31
32#include "customproperties.h"
33
34#include <QDataStream>
35#include <KDebug>
36
37using namespace KCalCore;
38
39//@cond PRIVATE
40static bool checkName(const QByteArray &name);
41
42class CustomProperties::Private
43{
44public:
45 bool operator==(const Private &other) const;
46 QMap<QByteArray, QString> mProperties; // custom calendar properties
47 QMap<QByteArray, QString> mPropertyParameters;
48
49 // Volatile properties are not written back to the serialized format and are not compared in operator==
50 // They are only used for runtime purposes and are not part of the payload.
51 QMap<QByteArray, QString> mVolatileProperties;
52
53
54 bool isVolatileProperty(const QString &name) const
55 {
56 return name.startsWith(QLatin1String("X-KDE-VOLATILE"));
57 }
58};
59
60bool CustomProperties::Private::operator==(const CustomProperties::Private &other) const
61{
62 if (mProperties.count() != other.mProperties.count()) {
63 // kDebug() << "Property count is different:" << mProperties << other.mProperties;
64 return false;
65 }
66 for (QMap<QByteArray, QString>::ConstIterator it = mProperties.begin();
67 it != mProperties.end(); ++it) {
68 QMap<QByteArray, QString>::ConstIterator itOther =
69 other.mProperties.find(it.key());
70 if (itOther == other.mProperties.end() || itOther.value() != it.value()) {
71 return false;
72 }
73 }
74 for (QMap<QByteArray, QString>::ConstIterator it = mPropertyParameters.begin();
75 it != mPropertyParameters.end(); ++it) {
76 QMap<QByteArray, QString>::ConstIterator itOther =
77 other.mPropertyParameters.find(it.key());
78 if (itOther == other.mPropertyParameters.end() || itOther.value() != it.value()) {
79 return false;
80 }
81 }
82 return true;
83}
84//@endcond
85
86CustomProperties::CustomProperties()
87 : d(new Private)
88{
89}
90
91CustomProperties::CustomProperties(const CustomProperties &cp)
92 : d(new Private(*cp.d))
93{
94}
95
96CustomProperties &CustomProperties::operator=(const CustomProperties &other)
97{
98 // check for self assignment
99 if (&other == this) {
100 return *this;
101 }
102
103 *d = *other.d;
104 return *this;
105}
106
107CustomProperties::~CustomProperties()
108{
109 delete d;
110}
111
112bool CustomProperties::operator==(const CustomProperties &other) const
113{
114 return *d == *other.d;
115}
116
117void CustomProperties::setCustomProperty(const QByteArray &app, const QByteArray &key,
118 const QString &value)
119{
120 if (value.isNull() || key.isEmpty() || app.isEmpty()) {
121 return;
122 }
123 QByteArray property = "X-KDE-" + app + '-' + key;
124 if (!checkName(property)) {
125 return;
126 }
127 customPropertyUpdate();
128
129 if (d->isVolatileProperty(property)) {
130 d->mVolatileProperties[property] = value;
131 } else {
132 d->mProperties[property] = value;
133 }
134
135 customPropertyUpdated();
136}
137
138void CustomProperties::removeCustomProperty(const QByteArray &app, const QByteArray &key)
139{
140 removeNonKDECustomProperty(QByteArray("X-KDE-" + app + '-' + key));
141}
142
143QString CustomProperties::customProperty(const QByteArray &app, const QByteArray &key) const
144{
145 return nonKDECustomProperty(QByteArray("X-KDE-" + app + '-' + key));
146}
147
148QByteArray CustomProperties::customPropertyName(const QByteArray &app, const QByteArray &key)
149{
150 QByteArray property("X-KDE-" + app + '-' + key);
151 if (!checkName(property)) {
152 return QByteArray();
153 }
154 return property;
155}
156
157void CustomProperties::setNonKDECustomProperty(const QByteArray &name, const QString &value,
158 const QString &parameters)
159{
160 if (value.isNull() || !checkName(name)) {
161 return;
162 }
163 customPropertyUpdate();
164 d->mProperties[name] = value;
165 d->mPropertyParameters[name] = parameters;
166 customPropertyUpdated();
167}
168void CustomProperties::removeNonKDECustomProperty(const QByteArray &name)
169{
170 if (d->mProperties.contains(name)) {
171 customPropertyUpdate();
172 d->mProperties.remove(name);
173 d->mPropertyParameters.remove(name);
174 customPropertyUpdated();
175 } else if (d->mVolatileProperties.contains(name)) {
176 customPropertyUpdate();
177 d->mVolatileProperties.remove(name);
178 customPropertyUpdated();
179 }
180}
181
182QString CustomProperties::nonKDECustomProperty(const QByteArray &name) const
183{
184 return d->isVolatileProperty(name) ? d->mVolatileProperties.value(name) : d->mProperties.value(name);
185}
186
187QString CustomProperties::nonKDECustomPropertyParameters(const QByteArray &name) const
188{
189 return d->mPropertyParameters.value(name);
190}
191
192void CustomProperties::setCustomProperties(const QMap<QByteArray, QString> &properties)
193{
194 bool changed = false;
195 for (QMap<QByteArray, QString>::ConstIterator it = properties.begin();
196 it != properties.end(); ++it) {
197 // Validate the property name and convert any null string to empty string
198 if (checkName(it.key())) {
199 if (d->isVolatileProperty(it.key())) {
200 d->mVolatileProperties[it.key()] = it.value().isNull() ? QLatin1String("") : it.value();
201 } else {
202 d->mProperties[it.key()] = it.value().isNull() ? QLatin1String("") : it.value();
203 }
204 if (!changed) {
205 customPropertyUpdate();
206 }
207 changed = true;
208 }
209 }
210 if (changed) {
211 customPropertyUpdated();
212 }
213}
214
215QMap<QByteArray, QString> CustomProperties::customProperties() const
216{
217 QMap<QByteArray, QString> result;
218 result.unite(d->mProperties);
219 result.unite(d->mVolatileProperties);
220
221 return result;
222}
223
224void CustomProperties::customPropertyUpdate()
225{
226}
227
228void CustomProperties::customPropertyUpdated()
229{
230}
231
232void CustomProperties::virtual_hook(int id, void *data)
233{
234 Q_UNUSED(id);
235 Q_UNUSED(data);
236 Q_ASSERT(false);
237}
238
239//@cond PRIVATE
240bool checkName(const QByteArray &name)
241{
242 // Check that the property name starts with 'X-' and contains
243 // only the permitted characters
244 const char *n = name;
245 int len = name.length();
246 if (len < 2 || n[0] != 'X' || n[1] != '-') {
247 return false;
248 }
249 for (int i = 2; i < len; ++i) {
250 char ch = n[i];
251 if ((ch >= 'A' && ch <= 'Z') ||
252 (ch >= 'a' && ch <= 'z') ||
253 (ch >= '0' && ch <= '9') ||
254 ch == '-') {
255 continue;
256 }
257 return false; // invalid character found
258 }
259 return true;
260}
261//@endcond
262
263QDataStream &KCalCore::operator<<(QDataStream &stream,
264 const KCalCore::CustomProperties &properties)
265{
266 return stream << properties.d->mProperties
267 << properties.d->mPropertyParameters;
268}
269
270QDataStream &KCalCore::operator>>(QDataStream &stream,
271 KCalCore::CustomProperties &properties)
272{
273 properties.d->mVolatileProperties.clear();
274 return stream >> properties.d->mProperties
275 >> properties.d->mPropertyParameters;
276}
277
278