1 /**
2 * This file is part of TelepathyQt
3 *
4 * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
5 * @copyright Copyright (C) 2008-2010 Nokia Corporation
6 * @license LGPL 2.1
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22
23 #include "TelepathyQt/manager-file.h"
24
25 #include "TelepathyQt/debug-internal.h"
26 #include "TelepathyQt/key-file.h"
27
28 #include <TelepathyQt/Constants>
29 #include <TelepathyQt/Utils>
30
31 #include <QtCore/QDir>
32 #include <QtCore/QHash>
33 #include <QtCore/QString>
34 #include <QtCore/QStringList>
35 #include <QtDBus/QDBusVariant>
36
37 namespace Tp
38 {
39
40 struct TP_QT_NO_EXPORT ManagerFile::Private
41 {
42 Private();
43 Private(const QString &cnName);
44
45 void init();
46 bool parse(const QString &fileName);
47 bool isValid() const;
48
49 bool hasParameter(const QString &protocol, const QString ¶mName) const;
50 ParamSpec *getParameter(const QString &protocol, const QString ¶mName);
51 QStringList protocols() const;
52 ParamSpecList parameters(const QString &protocol) const;
53
54 QVariant valueForKey(const QString ¶m, const QString &dbusSignature);
55
56 struct ProtocolInfo
57 {
ProtocolInfoTp::ManagerFile::Private::ProtocolInfo58 ProtocolInfo() {}
ProtocolInfoTp::ManagerFile::Private::ProtocolInfo59 ProtocolInfo(const ParamSpecList ¶ms, const PresenceSpecList &statuses)
60 : params(params),
61 statuses(statuses)
62 {
63 }
64
65 ParamSpecList params;
66 QString vcardField;
67 QString englishName;
68 QString iconName;
69 RequestableChannelClassList rccs;
70 PresenceSpecList statuses;
71 AvatarSpec avatarRequirements;
72 QStringList addressableVCardFields;
73 QStringList addressableUriSchemes;
74 };
75
76 QString cmName;
77 KeyFile keyFile;
78 QHash<QString, ProtocolInfo> protocolsMap;
79 bool valid;
80 };
81
Private()82 ManagerFile::Private::Private()
83 : valid(false)
84 {
85 }
86
Private(const QString & cmName)87 ManagerFile::Private::Private(const QString &cmName)
88 : cmName(cmName),
89 valid(false)
90 {
91 init();
92 }
93
init()94 void ManagerFile::Private::init()
95 {
96 // TODO: should we cache the configDirs anywhere?
97 QStringList configDirs;
98
99 QString xdgDataHome = QString::fromLocal8Bit(qgetenv("XDG_DATA_HOME"));
100 if (xdgDataHome.isEmpty()) {
101 configDirs << QDir::homePath() + QLatin1String("/.local/share/data/telepathy/managers/");
102 }
103 else {
104 configDirs << xdgDataHome + QLatin1String("/telepathy/managers/");
105 }
106
107 QString xdgDataDirsEnv = QString::fromLocal8Bit(qgetenv("XDG_DATA_DIRS"));
108 if (xdgDataDirsEnv.isEmpty()) {
109 configDirs << QLatin1String("/usr/local/share/telepathy/managers/");
110 configDirs << QLatin1String("/usr/share/telepathy/managers/");
111 }
112 else {
113 QStringList xdgDataDirs = xdgDataDirsEnv.split(QLatin1Char(':'));
114 foreach (const QString xdgDataDir, xdgDataDirs) {
115 configDirs << xdgDataDir + QLatin1String("/telepathy/managers/");
116 }
117 }
118
119 foreach (const QString configDir, configDirs) {
120 QString fileName = configDir + cmName + QLatin1String(".manager");
121 if (QFile::exists(fileName)) {
122 debug() << "parsing manager file" << fileName;
123 protocolsMap.clear();
124 if (!parse(fileName)) {
125 warning() << "error parsing manager file" << fileName;
126 continue;
127 }
128 valid = true;
129 return;
130 }
131 }
132 }
133
parse(const QString & fileName)134 bool ManagerFile::Private::parse(const QString &fileName)
135 {
136 keyFile.setFileName(fileName);
137 if (keyFile.status() != KeyFile::NoError) {
138 return false;
139 }
140
141 /* read supported protocols and parameters */
142 QString protocol;
143 QStringList groups = keyFile.allGroups();
144 foreach (const QString group, groups) {
145 if (group.startsWith(QLatin1String("Protocol "))) {
146 protocol = group.right(group.length() - 9);
147 keyFile.setGroup(group);
148
149 ParamSpecList paramSpecList;
150 SimpleStatusSpecMap statuses;
151 QString param;
152 QStringList params = keyFile.keys();
153 foreach (param, params) {
154 ParamSpec spec;
155 SimpleStatusSpec status;
156 spec.flags = 0;
157
158 QStringList values = keyFile.value(param).split(QLatin1String(" "));
159
160 if (param.startsWith(QLatin1String("param-"))) {
161 spec.name = param.right(param.length() - 6);
162
163 if (values.length() == 0) {
164 warning() << "param" << spec.name << "set but no signature defined";
165 return false;
166 }
167
168 if (spec.name.endsWith(QLatin1String("password"))) {
169 spec.flags |= ConnMgrParamFlagSecret;
170 }
171
172 spec.signature = values[0];
173
174 if (values.contains(QLatin1String("secret"))) {
175 spec.flags |= ConnMgrParamFlagSecret;
176 }
177
178 if (values.contains(QLatin1String("dbus-property"))) {
179 spec.flags |= ConnMgrParamFlagDBusProperty;
180 }
181
182 if (values.contains(QLatin1String("required"))) {
183 spec.flags |= ConnMgrParamFlagRequired;
184 }
185
186 if (values.contains(QLatin1String("register"))) {
187 spec.flags |= ConnMgrParamFlagRegister;
188 }
189
190 paramSpecList.append(spec);
191 } else if (param.startsWith(QLatin1String("status-"))) {
192 QString statusName = param.right(param.length() - 7);
193
194 if (values.length() == 0) {
195 warning() << "status" << statusName << "set but no type defined";
196 return false;
197 }
198
199 bool ok;
200 status.type = values[0].toUInt(&ok);
201 if (!ok) {
202 warning() << "status" << statusName << "set but type is not an uint";
203 return false;
204 }
205
206 if (values.contains(QLatin1String("settable"))) {
207 status.maySetOnSelf = true;
208 } else {
209 status.maySetOnSelf = false;
210 }
211
212 if (values.contains(QLatin1String("message"))) {
213 status.canHaveMessage = true;
214 } else {
215 status.canHaveMessage = false;
216 }
217
218 if (statuses.contains(statusName)) {
219 warning() << "status" << statusName << "defined more than once, "
220 "replacing it";
221 }
222
223 statuses.insert(statusName, status);
224 }
225 }
226
227 protocolsMap.insert(protocol, ProtocolInfo(paramSpecList, PresenceSpecList(statuses)));
228
229 /* now that we have all param-* created, let's find their default values */
230 foreach (param, params) {
231 if (param.startsWith(QLatin1String("default-"))) {
232 QString paramName = param.right(param.length() - 8);
233
234 if (!hasParameter(protocol, paramName)) {
235 warning() << "param" << paramName
236 << "has default value set, but not a definition";
237 continue;
238 }
239
240 ParamSpec *spec = getParameter(protocol, paramName);
241
242 spec->flags |= ConnMgrParamFlagHasDefault;
243
244 /* map based on the param dbus signature, otherwise use
245 * QString */
246 QVariant value = valueForKey(param, spec->signature);
247 if (value.type() == QVariant::Invalid) {
248 warning() << "param" << paramName
249 << "has invalid signature";
250 protocolsMap.clear();
251 return false;
252 }
253 spec->defaultValue = QDBusVariant(value);
254 }
255 }
256
257 ProtocolInfo &info = protocolsMap[protocol];
258 info.vcardField = keyFile.value(QLatin1String("VCardField"));
259 info.englishName = keyFile.value(QLatin1String("EnglishName"));
260 if (info.englishName.isEmpty()) {
261 QStringList words = protocol.split(QLatin1Char('-'));
262 for (int i = 0; i < words.size(); ++i) {
263 words[i][0] = words[i].at(0).toUpper();
264 }
265 info.englishName = words.join(QLatin1String(" "));
266 }
267
268 info.iconName = keyFile.value(QLatin1String("Icon"));
269 if (info.iconName.isEmpty()) {
270 info.iconName = QString(QLatin1String("im-%1")).arg(protocol);
271 }
272
273 QStringList supportedMimeTypes = keyFile.valueAsStringList(
274 QLatin1String("SupportedAvatarMIMETypes"));
275 uint minHeight = keyFile.value(QLatin1String("MinimumAvatarHeight")).toUInt();
276 uint maxHeight = keyFile.value(QLatin1String("MaximumAvatarHeight")).toUInt();
277 uint recommendedHeight = keyFile.value(
278 QLatin1String("RecommendedAvatarHeight")).toUInt();
279 uint minWidth = keyFile.value(QLatin1String("MinimumAvatarWidth")).toUInt();
280 uint maxWidth = keyFile.value(QLatin1String("MaximumAvatarWidth")).toUInt();
281 uint recommendedWidth = keyFile.value(
282 QLatin1String("RecommendedAvatarWidth")).toUInt();
283 uint maxBytes = keyFile.value(QLatin1String("MaximumAvatarBytes")).toUInt();
284 info.avatarRequirements = AvatarSpec(supportedMimeTypes,
285 minHeight, maxHeight, recommendedHeight,
286 minWidth, maxWidth, recommendedWidth,
287 maxBytes);
288
289 info.addressableVCardFields = keyFile.valueAsStringList(
290 QLatin1String("AddressableVCardFields"));
291 info.addressableUriSchemes = keyFile.valueAsStringList(
292 QLatin1String("AddressableURISchemes"));
293
294 QStringList rccGroups = keyFile.valueAsStringList(
295 QLatin1String("RequestableChannelClasses"));
296 RequestableChannelClass rcc;
297 foreach (const QString &rccGroup, rccGroups) {
298 keyFile.setGroup(rccGroup);
299
300 foreach (const QString &key, keyFile.keys()) {
301 int spaceIdx = key.indexOf(QLatin1String(" "));
302 if (spaceIdx == -1) {
303 continue;
304 }
305
306 QString propertyName = key.mid(0, spaceIdx);
307 QString signature = key.mid(spaceIdx + 1);
308 QString param = keyFile.value(key);
309 QVariant value = valueForKey(key, signature);
310 rcc.fixedProperties.insert(propertyName, value);
311 }
312
313 rcc.allowedProperties = keyFile.valueAsStringList(
314 QLatin1String("allowed"));
315
316 info.rccs.append(rcc);
317
318 rcc.fixedProperties.clear();
319 rcc.allowedProperties.clear();
320 }
321 }
322 }
323
324 return true;
325 }
326
isValid() const327 bool ManagerFile::Private::isValid() const
328 {
329 return ((keyFile.status() == KeyFile::NoError) && (valid));
330 }
331
hasParameter(const QString & protocol,const QString & paramName) const332 bool ManagerFile::Private::hasParameter(const QString &protocol,
333 const QString ¶mName) const
334 {
335 ParamSpecList paramSpecList = protocolsMap[protocol].params;
336 foreach (const ParamSpec ¶mSpec, paramSpecList) {
337 if (paramSpec.name == paramName) {
338 return true;
339 }
340 }
341 return false;
342 }
343
getParameter(const QString & protocol,const QString & paramName)344 ParamSpec *ManagerFile::Private::getParameter(const QString &protocol,
345 const QString ¶mName)
346 {
347 ParamSpecList ¶mSpecList = protocolsMap[protocol].params;
348 for (int i = 0; i < paramSpecList.size(); ++i) {
349 ParamSpec ¶mSpec = paramSpecList[i];
350 if (paramSpec.name == paramName) {
351 return ¶mSpec;
352 }
353 }
354 return NULL;
355 }
356
protocols() const357 QStringList ManagerFile::Private::protocols() const
358 {
359 return protocolsMap.keys();
360 }
361
parameters(const QString & protocol) const362 ParamSpecList ManagerFile::Private::parameters(const QString &protocol) const
363 {
364 return protocolsMap.value(protocol).params;
365 }
366
valueForKey(const QString & param,const QString & dbusSignature)367 QVariant ManagerFile::Private::valueForKey(const QString ¶m,
368 const QString &dbusSignature)
369 {
370 QString value = keyFile.rawValue(param);
371 return parseValueWithDBusSignature(value, dbusSignature);
372 }
373
374
375 /**
376 * \class ManagerFile
377 * \ingroup utils
378 * \headerfile TelepathyQt/manager-file.h <TelepathyQt/ManagerFile>
379 *
380 * \brief The ManagerFile class provides an easy way to read Telepathy manager
381 * files according to the \telepathy_spec.
382 */
383
384 /**
385 * Create a ManagerFile object used to read .manager compliant files.
386 */
ManagerFile()387 ManagerFile::ManagerFile()
388 : mPriv(new Private())
389 {
390 }
391
392 /**
393 * Create a ManagerFile object used to read .manager compliant files.
394 *
395 * \param cmName Name of the connection manager to read the file for.
396 */
ManagerFile(const QString & cmName)397 ManagerFile::ManagerFile(const QString &cmName)
398 : mPriv(new Private(cmName))
399 {
400 }
401
402 /**
403 * Create a ManagerFile object used to read .manager compliant files.
404 */
ManagerFile(const ManagerFile & other)405 ManagerFile::ManagerFile(const ManagerFile &other)
406 : mPriv(new Private())
407 {
408 mPriv->cmName = other.mPriv->cmName;
409 mPriv->keyFile = other.mPriv->keyFile;
410 mPriv->protocolsMap = other.mPriv->protocolsMap;
411 mPriv->valid = other.mPriv->valid;
412 }
413
414 /**
415 * Class destructor.
416 */
~ManagerFile()417 ManagerFile::~ManagerFile()
418 {
419 delete mPriv;
420 }
421
operator =(const ManagerFile & other)422 ManagerFile &ManagerFile::operator=(const ManagerFile &other)
423 {
424 mPriv->cmName = other.mPriv->cmName;
425 mPriv->keyFile = other.mPriv->keyFile;
426 mPriv->protocolsMap = other.mPriv->protocolsMap;
427 mPriv->valid = other.mPriv->valid;
428 return *this;
429 }
430
431 /**
432 * Check whether or not a ManagerFile object is valid. If the file for the
433 * specified connection manager cannot be found it will be considered invalid.
434 *
435 * \return true if valid, false otherwise.
436 */
isValid() const437 bool ManagerFile::isValid() const
438 {
439 return mPriv->isValid();
440 }
441
442 /**
443 * Return a list of all protocols defined in the manager file.
444 *
445 * \return List of all protocols defined in the file.
446 */
protocols() const447 QStringList ManagerFile::protocols() const
448 {
449 return mPriv->protocols();
450 }
451
452 /**
453 * Return a list of parameters for the given \a protocol.
454 *
455 * \param protocol Name of the protocol to look for.
456 * \return List of ParamSpec of a specific protocol defined in the file, or an
457 * empty list if the protocol is not defined.
458 */
parameters(const QString & protocol) const459 ParamSpecList ManagerFile::parameters(const QString &protocol) const
460 {
461 return mPriv->parameters(protocol);
462 }
463
464 /**
465 * Return the name of the most common vcard field used for the given \a protocol's
466 * contact identifiers, normalized to lower case.
467 *
468 * \param protocol Name of the protocol to look for.
469 * \return The most common vcard field used for the given protocol's contact
470 * identifiers, or an empty string if there is no such field or the
471 * protocol is not defined.
472 */
vcardField(const QString & protocol) const473 QString ManagerFile::vcardField(const QString &protocol) const
474 {
475 return mPriv->protocolsMap.value(protocol).vcardField;
476 }
477
addressableVCardFields(const QString & protocol) const478 QStringList ManagerFile::addressableVCardFields(const QString &protocol) const
479 {
480 return mPriv->protocolsMap.value(protocol).addressableVCardFields;
481 }
482
addressableUriSchemes(const QString & protocol) const483 QStringList ManagerFile::addressableUriSchemes(const QString &protocol) const
484 {
485 return mPriv->protocolsMap.value(protocol).addressableUriSchemes;
486 }
487
488 /**
489 * Return the English-language name of the given \a protocol, such as "AIM" or "Yahoo!".
490 *
491 * The name can be used as a fallback if an application doesn't have a localized name for the
492 * protocol.
493 *
494 * If the manager file doesn't specify the english name, it is inferred from the protocol name, such
495 * that for example "google-talk" becomes "Google Talk", but "local-xmpp" becomes "Local Xmpp".
496 *
497 * \param protocol Name of the protocol to look for.
498 * \return An English-language name for the given \a protocol.
499 */
englishName(const QString & protocol) const500 QString ManagerFile::englishName(const QString &protocol) const
501 {
502 return mPriv->protocolsMap.value(protocol).englishName;
503 }
504
505 /**
506 * Return the name of an icon for the given \a protocol in the system's icon
507 * theme, such as "im-msn".
508 *
509 * If the manager file doesn't specify the icon name, "im-<protocolname>" is assumed.
510 *
511 * \param protocol Name of the protocol to look for.
512 * \return The likely name of an icon for the given \a protocol.
513 */
iconName(const QString & protocol) const514 QString ManagerFile::iconName(const QString &protocol) const
515 {
516 return mPriv->protocolsMap.value(protocol).iconName;
517 }
518
519 /**
520 * Return a list of channel classes which might be requestable from a connection
521 * to the given \a protocol.
522 *
523 * \param protocol Name of the protocol to look for.
524 * \return A list of channel classes which might be requestable from a
525 * connection to the given \a protocol or a default constructed
526 * RequestableChannelClassList instance if the protocol is not defined.
527 */
requestableChannelClasses(const QString & protocol) const528 RequestableChannelClassList ManagerFile::requestableChannelClasses(
529 const QString &protocol) const
530 {
531 return mPriv->protocolsMap.value(protocol).rccs;
532 }
533
534 /**
535 * Return a list of PresenceSpec representing the possible presence statuses
536 * from a connection to the given \a protocol.
537 *
538 * \param protocol Name of the protocol to look for.
539 * \return A list of PresenceSpec representing the possible presence statuses
540 * from a connection to the given \a protocol or an empty list
541 * if the protocol is not defined.
542 */
allowedPresenceStatuses(const QString & protocol) const543 PresenceSpecList ManagerFile::allowedPresenceStatuses(const QString &protocol) const
544 {
545 return mPriv->protocolsMap.value(protocol).statuses;
546 }
547
548 /**
549 * Return the requirements (size limits, supported MIME types, etc)
550 * for avatars used on the given \a protocol.
551 *
552 * \param protocol Name of the protocol to look for.
553 * \return The requirements for avatars used on the given \a protocol or an invalid
554 * AvatarSpec if the protocol is not defined.
555 */
avatarRequirements(const QString & protocol) const556 AvatarSpec ManagerFile::avatarRequirements(const QString &protocol) const
557 {
558 return mPriv->protocolsMap.value(protocol).avatarRequirements;
559 }
560
561 } // Tp
562