1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2015 The Qt Company Ltd. |
4 | ** Contact: http://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtVersit module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL21$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see http://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at http://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 2.1 or version 3 as published by the Free |
20 | ** Software Foundation and appearing in the file LICENSE.LGPLv21 and |
21 | ** LICENSE.LGPLv3 included in the packaging of this file. Please review the |
22 | ** following information to ensure the GNU Lesser General Public License |
23 | ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and |
24 | ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
25 | ** |
26 | ** As a special exception, The Qt Company gives you certain additional |
27 | ** rights. These rights are described in The Qt Company LGPL Exception |
28 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
29 | ** |
30 | ** $QT_END_LICENSE$ |
31 | ** |
32 | ****************************************************************************/ |
33 | |
34 | #include "qversitcontactimporter_p.h" |
35 | |
36 | #include <QtContacts/qcontactdetails.h> |
37 | |
38 | #include "qversitcontacthandler.h" |
39 | #include "qversitcontactpluginloader_p.h" |
40 | #include "qversitcontactsdefs_p.h" |
41 | #include "qversitdocument.h" |
42 | #include "qversitpluginsearch_p.h" |
43 | #include "qversitproperty.h" |
44 | #include "qversitutils_p.h" |
45 | |
46 | QTCONTACTS_USE_NAMESPACE |
47 | |
48 | QT_BEGIN_NAMESPACE_VERSIT |
49 | |
50 | /*! |
51 | * Constructor. |
52 | */ |
53 | QVersitContactImporterPrivate::QVersitContactImporterPrivate(const QStringList& profiles) : |
54 | mPropertyHandler(NULL), |
55 | mPropertyHandler2(NULL), |
56 | mPropertyHandlerVersion(0), |
57 | mDefaultResourceHandler(new QVersitDefaultResourceHandler), |
58 | mResourceHandler(mDefaultResourceHandler) |
59 | { |
60 | // Contact detail mappings |
61 | int versitPropertyCount = |
62 | sizeof(versitContactDetailMappings)/sizeof(VersitContactDetailMapping); |
63 | for (int i=0; i < versitPropertyCount; i++) { |
64 | QString versitPropertyName = |
65 | QLatin1Literal(versitContactDetailMappings[i].versitPropertyName); |
66 | QPair<QContactDetail::DetailType, int> contactDetail; |
67 | contactDetail.first = |
68 | versitContactDetailMappings[i].detailType; |
69 | contactDetail.second = |
70 | versitContactDetailMappings[i].detailField; |
71 | mDetailMappings.insert(akey: versitPropertyName,avalue: contactDetail); |
72 | } |
73 | |
74 | // Context mappings |
75 | int contextCount = sizeof(versitContextMappings)/sizeof(VersitContextMapping); |
76 | for (int i=0; i < contextCount; i++) { |
77 | mContextMappings.insert( |
78 | akey: versitContextMappings[i].contactContext, |
79 | avalue: QLatin1String(versitContextMappings[i].versitString)); |
80 | } |
81 | |
82 | // Subtype mappings (insert in reverse order, so first definition (most recent insertion) is found first) |
83 | int subTypeCount = sizeof(versitSubTypeMappings)/sizeof(VersitSubTypeMapping); |
84 | for (int i=(subTypeCount - 1); i >= 0; --i) { |
85 | mSubTypeMappings.insert( |
86 | akey: QLatin1String(versitSubTypeMappings[i].versitString), |
87 | avalue: QPair<QContactDetail::DetailType, int>( |
88 | versitSubTypeMappings[i].detailType, |
89 | versitSubTypeMappings[i].contactSubType)); |
90 | } |
91 | |
92 | mPluginPropertyHandlers = QVersitContactPluginLoader::instance()->createContactHandlers(profiles); |
93 | } |
94 | |
95 | /*! |
96 | * Destructor. |
97 | */ |
98 | QVersitContactImporterPrivate::~QVersitContactImporterPrivate() |
99 | { |
100 | delete mDefaultResourceHandler; |
101 | foreach (QVersitContactHandler* pluginHandler, mPluginPropertyHandlers) { |
102 | delete pluginHandler; |
103 | } |
104 | } |
105 | |
106 | /*! |
107 | * Generates a QContact from \a versitDocument. |
108 | */ |
109 | bool QVersitContactImporterPrivate::importContact( |
110 | const QVersitDocument& document, int contactIndex, QContact* contact, |
111 | QVersitContactImporter::Error* error) |
112 | { |
113 | if (document.componentType() != QStringLiteral("VCARD" ) |
114 | && document.type() != QVersitDocument::VCard21Type |
115 | && document.type() != QVersitDocument::VCard30Type) { |
116 | *error = QVersitContactImporter::InvalidDocumentError; |
117 | return false; |
118 | } |
119 | const QList<QVersitProperty> properties = document.properties(); |
120 | if (properties.size() == 0) { |
121 | *error = QVersitContactImporter::EmptyDocumentError; |
122 | return false; |
123 | } |
124 | |
125 | // First, do the properties with PREF set so they appear first in the contact details |
126 | foreach (const QVersitProperty& property, properties) { |
127 | QStringList typeParameters = property.parameters().values(QStringLiteral("TYPE" )); |
128 | if (typeParameters.contains(QStringLiteral("PREF" ), cs: Qt::CaseInsensitive)) |
129 | importProperty(document, property, contactIndex, contact); |
130 | } |
131 | // ... then, do the rest of the properties. |
132 | foreach (const QVersitProperty& property, properties) { |
133 | QStringList typeParameters = property.parameters().values(QStringLiteral("TYPE" )); |
134 | if (!typeParameters.contains(QStringLiteral("PREF" ), cs: Qt::CaseInsensitive)) |
135 | importProperty(document, property, contactIndex, contact); |
136 | } |
137 | |
138 | contact->setType(QContactType::TypeContact); |
139 | |
140 | mRestoreHandler.documentProcessed(); |
141 | // run plugin handlers |
142 | foreach (QVersitContactImporterPropertyHandlerV2* handler, mPluginPropertyHandlers) { |
143 | handler->documentProcessed(document, contact); |
144 | } |
145 | // run the v2 handler, if set |
146 | if (mPropertyHandler2 && mPropertyHandlerVersion > 1) { |
147 | mPropertyHandler2->documentProcessed(document, contact); |
148 | } |
149 | |
150 | return true; |
151 | } |
152 | |
153 | void QVersitContactImporterPrivate::importProperty( |
154 | const QVersitDocument& document, const QVersitProperty& property, int contactIndex, |
155 | QContact* contact) |
156 | { |
157 | if (mPropertyHandler |
158 | && mPropertyHandlerVersion == 1 |
159 | && mPropertyHandler->preProcessProperty(document, property, contactIndex, contact)) |
160 | return; |
161 | |
162 | QPair<QContactDetail::DetailType, int> detailDefinition = |
163 | mDetailMappings.value(akey: property.name()); |
164 | QContactDetail::DetailType detailType = detailDefinition.first; |
165 | |
166 | QList<QContactDetail> updatedDetails; |
167 | |
168 | bool success = false; |
169 | |
170 | // The following functions create and save the details to the contact |
171 | switch (detailType) { |
172 | case QContactDetail::TypeAddress: |
173 | success = createAddress(property, contact, updatedDetails: &updatedDetails); // pass in group |
174 | break; |
175 | case QContactDetail::TypeAnniversary: |
176 | success = createAnniversary(property, contact, updatedDetails: &updatedDetails); |
177 | break; |
178 | case QContactDetail::TypeAvatar: |
179 | success = createAvatar(property, contact, updatedDetails: &updatedDetails); |
180 | break; |
181 | case QContactDetail::TypeBirthday: |
182 | success = createBirthday(property, contact, updatedDetails: &updatedDetails); |
183 | break; |
184 | case QContactDetail::TypeExtendedDetail: |
185 | success = createExtendedDetail(property, contact, updatedDetails: &updatedDetails); |
186 | break; |
187 | case QContactDetail::TypeFamily: |
188 | success = createFamily(property, contact, updatedDetails: &updatedDetails); |
189 | break; |
190 | case QContactDetail::TypeFavorite: |
191 | success = createFavorite(property, contact, updatedDetails: &updatedDetails); |
192 | break; |
193 | case QContactDetail::TypeGender: |
194 | success = createGender(property, contact, updatedDetails: &updatedDetails); |
195 | break; |
196 | case QContactDetail::TypeGeoLocation: |
197 | success = createGeoLocation(property, contact, updatedDetails: &updatedDetails); |
198 | break; |
199 | case QContactDetail::TypeName: |
200 | success = createName(property, contact, updatedDetails: &updatedDetails); |
201 | break; |
202 | case QContactDetail::TypeNickname: |
203 | success = createNicknames(property, contact, updatedDetails: &updatedDetails); |
204 | break; |
205 | case QContactDetail::TypeDisplayLabel: |
206 | success = createDisplaylabel(property, contact, updatedDetails: &updatedDetails); |
207 | break; |
208 | case QContactDetail::TypeOnlineAccount: |
209 | success = createOnlineAccount(property, contact, updatedDetails: &updatedDetails); |
210 | break; |
211 | case QContactDetail::TypeOrganization: |
212 | success = createOrganization(property, contact, updatedDetails: &updatedDetails); |
213 | break; |
214 | case QContactDetail::TypePhoneNumber: |
215 | success = createPhone(property, contact, updatedDetails: &updatedDetails); |
216 | break; |
217 | case QContactDetail::TypeRingtone: |
218 | success = createRingtone(property, contact, updatedDetails: &updatedDetails); |
219 | break; |
220 | case QContactDetail::TypeTag: |
221 | success = createTags(property, contact, updatedDetails: &updatedDetails); |
222 | break; |
223 | case QContactDetail::TypeTimestamp: |
224 | success = createTimeStamp(property, contact, updatedDetails: &updatedDetails); |
225 | break; |
226 | case QContactDetail::TypeVersion: |
227 | success = createVersion(property, contact, updatedDetails: &updatedDetails); |
228 | break; |
229 | default: |
230 | // Look up mDetailMappings for a simple mapping from property to detail. |
231 | success = createNameValueDetail(property, contact, updatedDetails: &updatedDetails); |
232 | break; |
233 | } |
234 | |
235 | if (mRestoreHandler.propertyProcessed(property, updatedDetails: &updatedDetails)) |
236 | success = true; |
237 | |
238 | // run plugin handlers |
239 | foreach (QVersitContactImporterPropertyHandlerV2* handler, mPluginPropertyHandlers) { |
240 | handler->propertyProcessed(document, property, contact: *contact, alreadyProcessed: &success, updatedDetails: &updatedDetails); |
241 | } |
242 | // run the v2 handler, if set |
243 | if (mPropertyHandler2 && mPropertyHandlerVersion > 1) { |
244 | mPropertyHandler2->propertyProcessed(document, property, contact: *contact, alreadyProcessed: &success, updatedDetails: &updatedDetails); |
245 | } |
246 | |
247 | foreach (QContactDetail detail, updatedDetails) { |
248 | contact->saveDetail(detail: &detail); |
249 | } |
250 | |
251 | // run the v1 handler, if set |
252 | if (mPropertyHandler && mPropertyHandlerVersion == 1) |
253 | mPropertyHandler->postProcessProperty(document, property, alreadyProcessed: success, contactIndex, contact); |
254 | } |
255 | /*! |
256 | * Creates a QContactName from \a property |
257 | */ |
258 | bool QVersitContactImporterPrivate::createName( |
259 | const QVersitProperty& property, |
260 | QContact* contact, |
261 | QList<QContactDetail>* updatedDetails) |
262 | { |
263 | QContactName name; |
264 | QContactDetail detail = contact->detail(type: QContactName::Type); |
265 | if (!detail.isEmpty()) { |
266 | // If multiple name properties exist, |
267 | // discard all except the first occurrence |
268 | if (!detail.value(field: QContactName::FieldFirstName).toString().isEmpty()) |
269 | return false; |
270 | else |
271 | name = QContactName(static_cast<QContactName>(detail)); |
272 | } |
273 | |
274 | QVariant variant = property.variantValue(); |
275 | if (property.valueType() != QVersitProperty::CompoundType |
276 | || variant.type() != QVariant::StringList) |
277 | return false; |
278 | QStringList values = variant.toStringList(); |
279 | QString value(takeFirst(list&: values)); |
280 | if (!value.isEmpty()) |
281 | name.setLastName(value); |
282 | value = takeFirst(list&: values); |
283 | if (!value.isEmpty()) |
284 | name.setFirstName(value); |
285 | value = takeFirst(list&: values); |
286 | if (!value.isEmpty()) |
287 | name.setMiddleName(value); |
288 | value = takeFirst(list&: values); |
289 | if (!value.isEmpty()) |
290 | name.setPrefix(value); |
291 | value = takeFirst(list&: values); |
292 | if (!value.isEmpty()) |
293 | name.setSuffix(value); |
294 | |
295 | saveDetailWithContext(updatedDetails, detail: name, contexts: extractContexts(property)); |
296 | return true; |
297 | } |
298 | |
299 | /*! |
300 | * Creates a QContactPhoneNumber from \a property |
301 | */ |
302 | bool QVersitContactImporterPrivate::createPhone( |
303 | const QVersitProperty& property, |
304 | QContact* contact, |
305 | QList<QContactDetail>* updatedDetails) |
306 | { |
307 | Q_UNUSED(contact) |
308 | QContactPhoneNumber phone; |
309 | QString value(property.value()); |
310 | if (value.isEmpty()) |
311 | return false; |
312 | phone.setNumber(property.value()); |
313 | QStringList subTypes(extractSubTypes(property)); |
314 | QList<int> subTypesInt; |
315 | |
316 | foreach (const QString &stringValue, subTypes) { |
317 | if (!mContextMappings.values().contains(t: stringValue)) { |
318 | QMultiHash<QString, QPair<QContactDetail::DetailType, int> >::const_iterator |
319 | it = mSubTypeMappings.constFind(akey: stringValue); |
320 | if (it != mSubTypeMappings.constEnd()) { |
321 | int mappedValue = it.value().second; |
322 | subTypesInt << mappedValue; |
323 | } |
324 | } |
325 | } |
326 | |
327 | if (property.name() == QStringLiteral("X-ASSISTANT-TEL" )) |
328 | subTypesInt << QContactPhoneNumber::SubTypeAssistant; |
329 | if (!subTypesInt.isEmpty()) |
330 | phone.setSubTypes(subTypesInt); |
331 | saveDetailWithContext(updatedDetails, detail: phone, contexts: extractContexts(property)); |
332 | return true; |
333 | } |
334 | |
335 | /*! |
336 | * Creates a QContactAddress from \a property |
337 | */ |
338 | bool QVersitContactImporterPrivate::createAddress( |
339 | const QVersitProperty& property, |
340 | QContact* contact, |
341 | QList<QContactDetail>* updatedDetails) |
342 | { |
343 | Q_UNUSED(contact) |
344 | QContactAddress address; |
345 | |
346 | QVariant variant = property.variantValue(); |
347 | if (property.valueType() != QVersitProperty::CompoundType |
348 | || variant.type() != QVariant::StringList) |
349 | return false; |
350 | QStringList addressParts = variant.toStringList(); |
351 | QString value(takeFirst(list&: addressParts)); |
352 | if (!value.isEmpty()) |
353 | address.setPostOfficeBox(value); |
354 | // There is no setter for the Extended Address in QContactAddress: |
355 | if (!addressParts.isEmpty()) |
356 | addressParts.removeFirst(); |
357 | value = takeFirst(list&: addressParts); |
358 | if (!value.isEmpty()) |
359 | address.setStreet(value); |
360 | value = takeFirst(list&: addressParts); |
361 | if (!value.isEmpty()) |
362 | address.setLocality(value); |
363 | value = takeFirst(list&: addressParts); |
364 | if (!value.isEmpty()) |
365 | address.setRegion(value); |
366 | value = takeFirst(list&: addressParts); |
367 | if (!value.isEmpty()) |
368 | address.setPostcode(value); |
369 | value = takeFirst(list&: addressParts); |
370 | if (!value.isEmpty()) |
371 | address.setCountry(value); |
372 | QStringList subTypes(extractSubTypes(property)); |
373 | QList<int> subTypesInt; |
374 | |
375 | foreach (const QString &stringValue, subTypes) { |
376 | QMultiHash<QString, QPair<QContactDetail::DetailType, int> >::const_iterator |
377 | it = mSubTypeMappings.constFind(akey: stringValue); |
378 | if (it != mSubTypeMappings.constEnd()) { |
379 | int mappedValue = it.value().second; |
380 | subTypesInt << mappedValue; |
381 | } |
382 | } |
383 | |
384 | if (!subTypesInt.isEmpty()) |
385 | address.setSubTypes(subTypesInt); |
386 | |
387 | saveDetailWithContext(updatedDetails, detail: address, contexts: extractContexts(property)); |
388 | return true; |
389 | } |
390 | |
391 | /*! |
392 | * Creates a QContactOrganization from \a property |
393 | */ |
394 | bool QVersitContactImporterPrivate::createOrganization( |
395 | const QVersitProperty& property, |
396 | QContact* contact, |
397 | QList<QContactDetail>* updatedDetails) |
398 | { |
399 | QContactOrganization organization; |
400 | QPair<QContactDetail::DetailType, int> detailTypeAndFieldName = |
401 | mDetailMappings.value(akey: property.name()); |
402 | int fieldName = detailTypeAndFieldName.second; |
403 | QList<QContactOrganization> organizations = contact->details<QContactOrganization>(); |
404 | foreach(const QContactOrganization& current, organizations) { |
405 | if (current.value(field: fieldName).toString().length() == 0) { |
406 | organization = current; |
407 | break; |
408 | } |
409 | } |
410 | if (fieldName == QContactOrganization::FieldName) { |
411 | setOrganizationNames(org&: organization, property); |
412 | } else if (fieldName == QContactOrganization::FieldTitle) { |
413 | organization.setTitle(property.value()); |
414 | } else if (fieldName == QContactOrganization::FieldRole) { |
415 | organization.setRole(property.value()); |
416 | } else if (fieldName == QContactOrganization::FieldLogoUrl) { |
417 | setOrganizationLogo(org&: organization, property); |
418 | } else if (fieldName == QContactOrganization::FieldAssistantName) { |
419 | organization.setAssistantName(property.value()); |
420 | } else { |
421 | return false; |
422 | } |
423 | |
424 | saveDetailWithContext(updatedDetails, detail: organization, contexts: extractContexts(property)); |
425 | return true; |
426 | } |
427 | |
428 | /*! |
429 | * Set the organization name and department(s) from \a property. |
430 | */ |
431 | void QVersitContactImporterPrivate::setOrganizationNames( |
432 | QContactOrganization& organization, const QVersitProperty& property) const |
433 | { |
434 | QVariant variant = property.variantValue(); |
435 | if (property.valueType() == QVersitProperty::CompoundType |
436 | && variant.type() == QVariant::StringList) { |
437 | QStringList values = variant.toStringList(); |
438 | QString name(takeFirst(list&: values)); |
439 | if (!name.isEmpty()) |
440 | organization.setName(name); |
441 | if (!values.isEmpty()) |
442 | organization.setDepartment(values); |
443 | } |
444 | } |
445 | |
446 | /*! |
447 | * Set the organization logo from \a property. |
448 | */ |
449 | void QVersitContactImporterPrivate::setOrganizationLogo( |
450 | QContactOrganization& org, const QVersitProperty& property) const |
451 | { |
452 | QString location; |
453 | QByteArray data; |
454 | saveDataFromProperty(property, location: &location, data: &data); |
455 | if (!location.isEmpty()) |
456 | org.setLogoUrl(QUrl(location)); |
457 | } |
458 | |
459 | /*! |
460 | * Creates a QContactTimeStamp from \a property |
461 | */ |
462 | bool QVersitContactImporterPrivate::createTimeStamp( |
463 | const QVersitProperty& property, |
464 | QContact* contact, |
465 | QList<QContactDetail>* updatedDetails) |
466 | { |
467 | Q_UNUSED(contact) |
468 | QContactTimestamp timeStamp; |
469 | QString value(property.value()); |
470 | QDateTime dateTime = parseDateTime(text: value); |
471 | if (!dateTime.isValid()) |
472 | return false; |
473 | timeStamp.setLastModified(dateTime); |
474 | saveDetailWithContext(updatedDetails, detail: timeStamp, contexts: extractContexts(property)); |
475 | return true; |
476 | } |
477 | |
478 | /*! |
479 | * Creates a QContactVersion from \a property |
480 | */ |
481 | bool QVersitContactImporterPrivate::createVersion( |
482 | const QVersitProperty& property, |
483 | QContact* contact, |
484 | QList<QContactDetail>* updatedDetails) |
485 | { |
486 | // Allow only on version detail, discard others. |
487 | QContactDetail detail = contact->detail(type: QContactVersion::Type); |
488 | if (!detail.isEmpty()) |
489 | return false; // Only one version detail is created |
490 | |
491 | QVariant variant = property.variantValue(); |
492 | if (property.valueType() != QVersitProperty::CompoundType |
493 | || variant.type() != QVariant::StringList) |
494 | return false; |
495 | QStringList values = variant.toStringList(); |
496 | bool ok; |
497 | QContactVersion version; |
498 | version.setSequenceNumber(takeFirst(list&: values).toInt(ok: &ok)); |
499 | version.setExtendedVersion(takeFirst(list&: values).toLocal8Bit()); |
500 | if (ok) { |
501 | saveDetailWithContext(updatedDetails, detail: version, contexts: extractContexts(property)); |
502 | return true; |
503 | } else { |
504 | return false; |
505 | } |
506 | } |
507 | |
508 | /*! |
509 | * Creates a QContactAnniversary from \a property |
510 | */ |
511 | bool QVersitContactImporterPrivate::createAnniversary( |
512 | const QVersitProperty& property, |
513 | QContact* contact, |
514 | QList<QContactDetail>* updatedDetails) |
515 | { |
516 | Q_UNUSED(contact) |
517 | QContactAnniversary anniversary; |
518 | bool justDate = false; |
519 | QDateTime dateTime = parseDateTime(text: property.value(), justDate: &justDate); |
520 | if (!dateTime.isValid()) |
521 | return false; |
522 | if (justDate) |
523 | anniversary.setOriginalDate(dateTime.date()); |
524 | else |
525 | anniversary.setOriginalDateTime(dateTime); |
526 | saveDetailWithContext(updatedDetails, detail: anniversary, contexts: extractContexts(property)); |
527 | return true; |
528 | } |
529 | |
530 | /*! |
531 | * Creates a QContactBirthday from \a property |
532 | */ |
533 | bool QVersitContactImporterPrivate::createBirthday( |
534 | const QVersitProperty& property, |
535 | QContact* contact, |
536 | QList<QContactDetail>* updatedDetails) |
537 | { |
538 | Q_UNUSED(contact) |
539 | QContactBirthday bday; |
540 | bool justDate = false; |
541 | QDateTime dateTime = parseDateTime(text: property.value(), justDate: &justDate); |
542 | if (!dateTime.isValid()) |
543 | return false; |
544 | if (justDate) |
545 | bday.setDate(dateTime.date()); |
546 | else |
547 | bday.setDateTime(dateTime); |
548 | saveDetailWithContext(updatedDetails, detail: bday, contexts: extractContexts(property)); |
549 | return true; |
550 | } |
551 | |
552 | /*! |
553 | * Creates a QContactDisplayLabel from \a property |
554 | */ |
555 | bool QVersitContactImporterPrivate::createDisplaylabel( |
556 | const QVersitProperty& property, |
557 | QContact* contact, |
558 | QList<QContactDetail>* updatedDetails) |
559 | { |
560 | QString label(property.value()); |
561 | if (!label.isEmpty()) { |
562 | QContactDisplayLabel displayLabel; |
563 | QContactDisplayLabel existingDisplayLabel = contact->detail<QContactDisplayLabel>(); |
564 | if (!existingDisplayLabel.isEmpty()) { |
565 | displayLabel = existingDisplayLabel; |
566 | } |
567 | displayLabel.setLabel(property.value()); |
568 | saveDetailWithContext(updatedDetails, detail: displayLabel, contexts: extractContexts(property)); |
569 | return true; |
570 | } else { |
571 | return false; |
572 | } |
573 | } |
574 | |
575 | /*! |
576 | * Creates QContactNicknames from \a property |
577 | */ |
578 | bool QVersitContactImporterPrivate::createNicknames( |
579 | const QVersitProperty& property, |
580 | QContact* contact, |
581 | QList<QContactDetail>* updatedDetails) |
582 | { |
583 | Q_UNUSED(contact) |
584 | QVariant variant = property.variantValue(); |
585 | if (property.valueType() != QVersitProperty::ListType |
586 | || variant.type() != QVariant::StringList) |
587 | return false; |
588 | QStringList values = variant.toStringList(); |
589 | QList<int> contexts = extractContexts(property); |
590 | |
591 | // We don't want to make duplicates of existing nicknames |
592 | QSet<QString> existingNicknames; |
593 | foreach (const QContactNickname& nickname, contact->details<QContactNickname>()) { |
594 | existingNicknames.insert(value: nickname.nickname()); |
595 | } |
596 | foreach(const QString& value, values) { |
597 | if (!value.isEmpty() && !existingNicknames.contains(value)) { |
598 | QContactNickname nickname; |
599 | nickname.setNickname(value); |
600 | saveDetailWithContext(updatedDetails, detail: nickname, contexts); |
601 | existingNicknames.insert(value); |
602 | } |
603 | } |
604 | return true; |
605 | } |
606 | |
607 | /*! |
608 | * Creates QContactTags from \a property |
609 | */ |
610 | bool QVersitContactImporterPrivate::createTags( |
611 | const QVersitProperty& property, |
612 | QContact* contact, |
613 | QList<QContactDetail>* updatedDetails) |
614 | { |
615 | Q_UNUSED(contact) |
616 | QVariant variant = property.variantValue(); |
617 | if (property.valueType() != QVersitProperty::ListType |
618 | || variant.type() != QVariant::StringList) |
619 | return false; |
620 | QStringList values = variant.toStringList(); |
621 | QList<int> contexts = extractContexts(property); |
622 | |
623 | // We don't want to make duplicates of existing tags |
624 | QSet<QString> existingTags; |
625 | foreach (const QContactTag& tag, contact->details<QContactTag>()) { |
626 | existingTags.insert(value: tag.tag()); |
627 | } |
628 | foreach(const QString& value, values) { |
629 | if (!value.isEmpty() && !existingTags.contains(value)) { |
630 | QContactTag tag; |
631 | tag.setTag(value); |
632 | saveDetailWithContext(updatedDetails, detail: tag, contexts); |
633 | existingTags.insert(value); |
634 | } |
635 | } |
636 | return true; |
637 | } |
638 | |
639 | /*! |
640 | * Creates a QContactOnlineAccount from \a property |
641 | */ |
642 | bool QVersitContactImporterPrivate::createOnlineAccount( |
643 | const QVersitProperty& property, |
644 | QContact* contact, |
645 | QList<QContactDetail>* updatedDetails) |
646 | { |
647 | Q_UNUSED(contact) |
648 | QContactOnlineAccount onlineAccount; |
649 | QString value(property.value()); |
650 | if (value.isEmpty()) |
651 | return false; |
652 | onlineAccount.setAccountUri(property.value()); |
653 | if (property.name() == QStringLiteral("X-SIP" )) { |
654 | QStringList subTypes = extractSubTypes(property); |
655 | QList<int> subTypesInt; |
656 | |
657 | foreach (const QString &stringValue, subTypes) { |
658 | QMultiHash<QString, QPair<QContactDetail::DetailType, int> >::const_iterator |
659 | it = mSubTypeMappings.constFind(akey: stringValue); |
660 | if (it != mSubTypeMappings.constEnd()) { |
661 | int mappedValue = it.value().second; |
662 | subTypesInt << mappedValue; |
663 | } |
664 | } |
665 | if (subTypes.isEmpty()) |
666 | subTypesInt.append(t: QContactOnlineAccount::SubTypeSip); |
667 | onlineAccount.setSubTypes(subTypesInt); |
668 | } else if (property.name() == QStringLiteral("X-IMPP" ) || |
669 | property.name() == QStringLiteral("IMPP" )) { |
670 | QList<int> subTypeImppList; |
671 | subTypeImppList << QContactOnlineAccount::SubTypeImpp; |
672 | onlineAccount.setSubTypes(subTypeImppList); |
673 | } else if (property.name() == QStringLiteral("X-JABBER" )) { |
674 | QList<int> subTypeImppList; |
675 | subTypeImppList << QContactOnlineAccount::SubTypeImpp; |
676 | onlineAccount.setSubTypes(subTypeImppList); |
677 | onlineAccount.setValue(field: QContactOnlineAccount::FieldProtocol, |
678 | value: QContactOnlineAccount::ProtocolJabber); |
679 | } else if (property.name() == QStringLiteral("X-AIM" )) { |
680 | onlineAccount.setValue(field: QContactOnlineAccount::FieldProtocol, |
681 | value: QContactOnlineAccount::ProtocolAim); |
682 | } else if (property.name() == QStringLiteral("X-ICQ" )) { |
683 | onlineAccount.setValue(field: QContactOnlineAccount::FieldProtocol, |
684 | value: QContactOnlineAccount::ProtocolIcq); |
685 | } else if (property.name() == QStringLiteral("X-MSN" )) { |
686 | onlineAccount.setValue(field: QContactOnlineAccount::FieldProtocol, |
687 | value: QContactOnlineAccount::ProtocolMsn); |
688 | } else if (property.name() == QStringLiteral("X-QQ" )) { |
689 | onlineAccount.setValue(field: QContactOnlineAccount::FieldProtocol, |
690 | value: QContactOnlineAccount::ProtocolQq); |
691 | } else if (property.name() == QStringLiteral("X-YAHOO" )) { |
692 | onlineAccount.setValue(field: QContactOnlineAccount::FieldProtocol, |
693 | value: QContactOnlineAccount::ProtocolYahoo); |
694 | } else if (property.name() == QStringLiteral("X-SKYPE" ) || |
695 | property.name() == QStringLiteral("X-SKYPE-USERNAME" )) { |
696 | onlineAccount.setValue(field: QContactOnlineAccount::FieldProtocol, |
697 | value: QContactOnlineAccount::ProtocolSkype); |
698 | } else { |
699 | onlineAccount.setValue(field: QContactOnlineAccount::FieldProtocol, |
700 | value: QContactOnlineAccount::ProtocolUnknown); |
701 | } |
702 | |
703 | saveDetailWithContext(updatedDetails, detail: onlineAccount, contexts: extractContexts(property)); |
704 | return true; |
705 | } |
706 | |
707 | /*! |
708 | * Creates a QContactRingtone from \a property |
709 | */ |
710 | bool QVersitContactImporterPrivate::createRingtone( |
711 | const QVersitProperty& property, |
712 | QContact* contact, |
713 | QList<QContactDetail>* updatedDetails) |
714 | { |
715 | Q_UNUSED(contact) |
716 | QString location; |
717 | QByteArray data; |
718 | if (saveDataFromProperty(property, location: &location, data: &data) && !location.isEmpty()) { |
719 | QContactRingtone ringtone; |
720 | ringtone.setAudioRingtoneUrl(location); |
721 | saveDetailWithContext(updatedDetails, detail: ringtone, contexts: extractContexts(property)); |
722 | return true; |
723 | } |
724 | return false; |
725 | } |
726 | |
727 | /*! |
728 | * Creates a QContactAvatar from \a property |
729 | */ |
730 | bool QVersitContactImporterPrivate::createAvatar( |
731 | const QVersitProperty& property, |
732 | QContact* contact, |
733 | QList<QContactDetail>* updatedDetails) |
734 | { |
735 | Q_UNUSED(contact) |
736 | QString location; |
737 | QByteArray data; |
738 | bool success = false; |
739 | |
740 | if (saveDataFromProperty(property, location: &location, data: &data) && !location.isEmpty()) { |
741 | QContactAvatar avatar; |
742 | avatar.setImageUrl(location); |
743 | saveDetailWithContext(updatedDetails, detail: avatar, contexts: extractContexts(property)); |
744 | success = true; |
745 | } |
746 | return success; |
747 | } |
748 | |
749 | /*! |
750 | * Creates a QContactGeoLocation from \a property |
751 | */ |
752 | bool QVersitContactImporterPrivate::createGeoLocation( |
753 | const QVersitProperty& property, |
754 | QContact* contact, |
755 | QList<QContactDetail>* updatedDetails) |
756 | { |
757 | Q_UNUSED(contact) |
758 | QContactGeoLocation geo; |
759 | QVariant variant = property.variantValue(); |
760 | if (property.valueType() != QVersitProperty::CompoundType |
761 | || variant.type() != QVariant::StringList) |
762 | return false; |
763 | QStringList values = variant.toStringList(); |
764 | bool ok1; |
765 | geo.setLatitude(takeFirst(list&: values).toDouble(ok: &ok1)); |
766 | bool ok2; |
767 | geo.setLongitude(takeFirst(list&: values).toDouble(ok: &ok2)); |
768 | |
769 | if (ok1 && ok2) { |
770 | saveDetailWithContext(updatedDetails, detail: geo, contexts: extractContexts(property)); |
771 | return true; |
772 | } else { |
773 | return false; |
774 | } |
775 | } |
776 | |
777 | /*! |
778 | * Creates a QContactFamily from \a property |
779 | */ |
780 | bool QVersitContactImporterPrivate::createFamily( |
781 | const QVersitProperty& property, |
782 | QContact* contact, |
783 | QList<QContactDetail>* updatedDetails) |
784 | { |
785 | QString val = property.value(); |
786 | QContactFamily family = contact->detail<QContactFamily>(); |
787 | if (property.name() == QStringLiteral("X-SPOUSE" )) { |
788 | if (val.isEmpty()) |
789 | return false; |
790 | family.setSpouse(val); |
791 | } else if (property.name() == QStringLiteral("X-CHILDREN" )) { |
792 | QVariant variant = property.variantValue(); |
793 | if (property.valueType() != QVersitProperty::ListType |
794 | || variant.type() != QVariant::StringList) |
795 | return false; |
796 | QStringList values = variant.toStringList(); |
797 | if (values.isEmpty()) |
798 | return false; |
799 | family.setChildren(values); |
800 | } else { |
801 | return false; |
802 | } |
803 | |
804 | saveDetailWithContext(updatedDetails, detail: family, contexts: extractContexts(property)); |
805 | return true; |
806 | } |
807 | |
808 | /*! |
809 | * Creates a QContactFavorite from \a property |
810 | */ |
811 | bool QVersitContactImporterPrivate::createFavorite( |
812 | const QVersitProperty& property, |
813 | QContact* contact, |
814 | QList<QContactDetail>* updatedDetails) |
815 | { |
816 | QContactDetail detail = contact->detail(type: QContactFavorite::Type); |
817 | if (!detail.isEmpty()) { |
818 | // If multiple favorite properties exist, |
819 | // discard all except the first occurrence |
820 | return false; |
821 | } |
822 | |
823 | QContactFavorite favorite; |
824 | QVariant variant = property.variantValue(); |
825 | if (property.valueType() != QVersitProperty::CompoundType |
826 | || variant.type() != QVariant::StringList) |
827 | return false; |
828 | |
829 | QStringList values = variant.toStringList(); |
830 | |
831 | QString value(takeFirst(list&: values)); |
832 | if (value.isEmpty()) |
833 | return false; |
834 | if (value == QStringLiteral("true" )) |
835 | favorite.setFavorite(true); |
836 | else if (value == QStringLiteral("false" )) |
837 | favorite.setFavorite(false); |
838 | else |
839 | return false; |
840 | |
841 | value = takeFirst(list&: values); |
842 | if (value.isEmpty()) |
843 | return false; |
844 | bool ok = true; |
845 | int index = value.toInt(ok: &ok); |
846 | if (ok) |
847 | favorite.setIndex(index); |
848 | else |
849 | return false; |
850 | |
851 | saveDetailWithContext(updatedDetails, detail: favorite, contexts: extractContexts(property)); |
852 | return true; |
853 | } |
854 | |
855 | /*! |
856 | * Creates a QContactGender from \a property |
857 | */ |
858 | bool QVersitContactImporterPrivate::createGender( |
859 | const QVersitProperty& property, |
860 | QContact* contact, |
861 | QList<QContactDetail>* updatedDetails) |
862 | { |
863 | QContactGender gender; |
864 | QContactDetail detail = contact->detail(type: QContactGender::Type); |
865 | if (!detail.isEmpty()) { |
866 | // If multiple gender properties exist, |
867 | // discard all except the first occurrence |
868 | if (!detail.value(field: QContactGender::FieldGender).toBool()) |
869 | return false; |
870 | else |
871 | gender = QContactGender(static_cast<QContactGender>(detail)); |
872 | } |
873 | QString val = property.value().toUpper(); |
874 | if (property.name() != QStringLiteral("X-GENDER" ) || val.isEmpty()) |
875 | return false; |
876 | if (val == QStringLiteral("MALE" )) { |
877 | gender.setGender(QContactGender::GenderMale); |
878 | } else if (val == QStringLiteral("FEMALE" )) { |
879 | gender.setGender(QContactGender::GenderFemale); |
880 | } else if (val == QStringLiteral("UNSPECIFIED" )) { |
881 | gender.setGender(QContactGender::GenderUnspecified); |
882 | } else { |
883 | return false; |
884 | } |
885 | |
886 | saveDetailWithContext(updatedDetails, detail: gender, contexts: extractContexts(property)); |
887 | return true; |
888 | } |
889 | |
890 | /*! |
891 | * Creates a QContactExtendedDetail from \a property |
892 | */ |
893 | bool QVersitContactImporterPrivate::createExtendedDetail( |
894 | const QVersitProperty& property, |
895 | QContact* contact, |
896 | QList<QContactDetail>* updatedDetails) |
897 | { |
898 | Q_UNUSED(contact) |
899 | QContactExtendedDetail extendedDetail; |
900 | const QVariant variant = property.variantValue(); |
901 | if (property.valueType() != QVersitProperty::CompoundType |
902 | || variant.type() != QVariant::StringList) |
903 | return false; |
904 | |
905 | QStringList values = variant.toStringList(); |
906 | extendedDetail.setName(takeFirst(list&: values)); |
907 | QVariant data; |
908 | if (VersitUtils::convertFromJson(json: takeFirst(list&: values), data: &data)) |
909 | extendedDetail.setData(data); |
910 | else |
911 | return false; |
912 | |
913 | saveDetailWithContext(updatedDetails, detail: extendedDetail, contexts: extractContexts(property)); |
914 | return true; |
915 | } |
916 | |
917 | /*! |
918 | * Creates a simple name-value contact detail. |
919 | */ |
920 | bool QVersitContactImporterPrivate::createNameValueDetail( |
921 | const QVersitProperty& property, |
922 | QContact* contact, |
923 | QList<QContactDetail>* updatedDetails) |
924 | { |
925 | Q_UNUSED(contact) |
926 | QString value(property.value()); |
927 | if (value.isEmpty()) |
928 | return false; |
929 | QPair<QContactDetail::DetailType, int> nameAndValueType = |
930 | mDetailMappings.value(akey: property.name()); |
931 | if (nameAndValueType.first == QContactDetail::TypeUndefined) |
932 | return false; |
933 | |
934 | QContactDetail detail(nameAndValueType.first); |
935 | detail.setValue(field: nameAndValueType.second, value); |
936 | |
937 | saveDetailWithContext(updatedDetails, detail, contexts: extractContexts(property)); |
938 | return true; |
939 | } |
940 | |
941 | /*! |
942 | * Extracts the list of contexts from \a types |
943 | */ |
944 | QList<int> QVersitContactImporterPrivate::( |
945 | const QVersitProperty& property) const |
946 | { |
947 | QStringList types = property.parameters().values(QStringLiteral("TYPE" )); |
948 | QList<int> contexts; |
949 | foreach (const QString& type, types) { |
950 | QString value = type.toUpper(); |
951 | if (mContextMappings.values().contains(t: value)) |
952 | contexts << mContextMappings.key(avalue: value); |
953 | } |
954 | return contexts; |
955 | } |
956 | |
957 | /*! |
958 | * Extracts the list of subtypes from \a property |
959 | */ |
960 | QStringList QVersitContactImporterPrivate::( |
961 | const QVersitProperty& property) const |
962 | { |
963 | QStringList types = property.parameters().values(QStringLiteral("TYPE" )); |
964 | QStringList subTypes; |
965 | foreach (const QString& type, types) { |
966 | QString subType = type.toUpper(); |
967 | if (subType.length() > 0) |
968 | subTypes += subType; |
969 | } |
970 | return subTypes; |
971 | } |
972 | |
973 | /*! |
974 | * Takes the first value in \a list, or an empty QString is if the list is empty. |
975 | */ |
976 | QString QVersitContactImporterPrivate::takeFirst(QList<QString>& list) const |
977 | { |
978 | return list.empty() ? QString() : list.takeFirst(); |
979 | } |
980 | |
981 | /*! |
982 | * Parses a date and time from text |
983 | */ |
984 | QDateTime QVersitContactImporterPrivate::parseDateTime(QString value, bool *justDate) const |
985 | { |
986 | bool hasTime = false; |
987 | bool utc = value.endsWith(c: QLatin1Char('Z'), cs: Qt::CaseInsensitive); |
988 | if (utc) |
989 | value.chop(n: 1); // take away z from end; |
990 | |
991 | QDateTime dateTime; |
992 | if (value.contains(c: QLatin1Char('-'))) { |
993 | dateTime = QDateTime::fromString(s: value,f: Qt::ISODate); |
994 | hasTime = dateTime.isValid() && value.contains(c: QLatin1Char('T')); |
995 | } else { |
996 | switch (value.length()) { |
997 | case 8: |
998 | dateTime = QDateTime::fromString(s: value, QStringLiteral("yyyyMMdd" )); |
999 | break; |
1000 | case 15: |
1001 | dateTime = QDateTime::fromString(s: value, QStringLiteral("yyyyMMddThhmmss" )); |
1002 | hasTime = true; |
1003 | break; |
1004 | // default: return invalid |
1005 | } |
1006 | } |
1007 | |
1008 | if (utc) |
1009 | dateTime.setTimeSpec(Qt::UTC); |
1010 | |
1011 | if (justDate) |
1012 | *justDate = !hasTime && !utc; // UTC implies a time of midnight |
1013 | |
1014 | return dateTime; |
1015 | } |
1016 | |
1017 | /*! |
1018 | * Extracts either a location (URI/filepath) from a \a property, or data (eg. if it was base64 |
1019 | * encoded). If the property contains data, an attempt is made to save it and the location of the |
1020 | * saved resource is recovered to *\a location. The data is stored into *\a data. |
1021 | */ |
1022 | bool QVersitContactImporterPrivate::saveDataFromProperty(const QVersitProperty &property, |
1023 | QString *location, |
1024 | QByteArray *data) const |
1025 | { |
1026 | bool found = false; |
1027 | const QString valueParam = property.parameters().value(QStringLiteral("VALUE" )).toUpper(); |
1028 | QVariant variant(property.variantValue()); |
1029 | if (variant.type() == QVariant::String |
1030 | || valueParam == QStringLiteral("URL" ) |
1031 | || valueParam == QStringLiteral("URI" )) { |
1032 | *location = property.value(); |
1033 | found |= !location->isEmpty(); |
1034 | } else if (variant.type() == QVariant::ByteArray) { |
1035 | *data = variant.toByteArray(); |
1036 | if (!data->isEmpty()) { |
1037 | found = true; |
1038 | *location = saveContentToFile(property, data: *data); |
1039 | } |
1040 | } |
1041 | return found; |
1042 | } |
1043 | |
1044 | /*! |
1045 | * Writes \a data to a file and returns the filename. \a property specifies the context in which |
1046 | * the data was found. |
1047 | */ |
1048 | QString QVersitContactImporterPrivate::saveContentToFile( |
1049 | const QVersitProperty& property, const QByteArray& data) const |
1050 | { |
1051 | QString filename; |
1052 | bool ok = false; |
1053 | if (mResourceHandler) |
1054 | ok = mResourceHandler->saveResource(contents: data, property, location: &filename); |
1055 | return ok ? filename : QString(); |
1056 | } |
1057 | |
1058 | /*! |
1059 | * Adds \a detail to the \a updatedDetails list. Also sets the contexts to \a contexts if it is not |
1060 | * empty. |
1061 | */ |
1062 | void QVersitContactImporterPrivate::saveDetailWithContext( |
1063 | QList<QContactDetail>* updatedDetails, |
1064 | QContactDetail detail, |
1065 | const QList<int>& contexts) |
1066 | { |
1067 | if (!contexts.isEmpty()) |
1068 | detail.setContexts(contexts); |
1069 | updatedDetails->append(t: detail); |
1070 | } |
1071 | |
1072 | QT_END_NAMESPACE_VERSIT |
1073 | |