KPeople

kpeoplevcard.cpp
1/*
2 SPDX-FileCopyrightText: 2015 Aleix Pol Gonzalez <aleixpol@kde.org>
3 SPDX-FileCopyrightText: 2015 Martin Klapetek <mklapetek@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.1-or-later
6*/
7
8#include "kpeoplevcard.h"
9#include <KContacts/Picture>
10#include <KContacts/VCardConverter>
11#include <KLocalizedString>
12#include <QDebug>
13#include <QDir>
14#include <QImage>
15#include <QStandardPaths>
16
17#include <KFileUtils>
18#include <KPluginFactory>
19
20using namespace KPeople;
21
22#ifdef Q_OS_WIN
23Q_GLOBAL_STATIC_WITH_ARGS(QString, vcardsLocation, (QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + QString::fromLatin1("/Contacts")))
24#else
25Q_GLOBAL_STATIC_WITH_ARGS(QString,
26 vcardsLocation,
28#endif
29
30#ifdef Q_OS_WIN
31Q_GLOBAL_STATIC_WITH_ARGS(QString, vcardsWriteLocation, (QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + QString::fromLatin1("/Contacts/own")))
32#else
33Q_GLOBAL_STATIC_WITH_ARGS(QString,
34 vcardsWriteLocation,
36#endif
37
38class VCardContact : public AbstractEditableContact
39{
40public:
41 VCardContact()
42 {
43 }
44 VCardContact(const KContacts::Addressee &addr, const QUrl &location)
45 : m_addressee(addr)
46 , m_location(location)
47 {
48 }
49 void setAddressee(const KContacts::Addressee &addr)
50 {
51 m_addressee = addr;
52 }
53 void setUrl(const QUrl &url)
54 {
55 m_location = url;
56 }
57
58 QVariant customProperty(const QString &key) const override
59 {
60 QVariant ret;
61 if (key == NameProperty) {
62 const QString name = m_addressee.realName();
63 if (!name.isEmpty()) {
64 return name;
65 }
66
67 // If both first and last name are set combine them to a full name
68 if (!m_addressee.givenName().isEmpty() && !m_addressee.familyName().isEmpty())
69 return i18ndc("kpeoplevcard", "given-name family-name", "%1 %2", m_addressee.givenName(), m_addressee.familyName());
70
71 // If only one of them is set just return what we know
72 if (!m_addressee.givenName().isEmpty())
73 return m_addressee.givenName();
74 if (!m_addressee.familyName().isEmpty())
75 return m_addressee.familyName();
76
77 // Fall back to other identifiers
78 if (!m_addressee.preferredEmail().isEmpty()) {
79 return m_addressee.preferredEmail();
80 }
81 if (!m_addressee.phoneNumbers().isEmpty()) {
82 return m_addressee.phoneNumbers().at(0).number();
83 }
84 return QVariant();
85 } else if (key == EmailProperty)
86 return m_addressee.preferredEmail();
87 else if (key == AllEmailsProperty)
88 return m_addressee.emails();
89 else if (key == PictureProperty)
90 return m_addressee.photo().data();
91 else if (key == AllPhoneNumbersProperty) {
92 const auto phoneNumbers = m_addressee.phoneNumbers();
93 QVariantList numbers;
94 for (const KContacts::PhoneNumber &phoneNumber : phoneNumbers) {
95 // convert from KContacts specific format to QString
96 numbers << phoneNumber.number();
97 }
98 return numbers;
99 } else if (key == PhoneNumberProperty) {
100 return m_addressee.phoneNumbers().isEmpty() ? QVariant() : m_addressee.phoneNumbers().at(0).number();
101 } else if (key == VCardProperty) {
103 return converter.createVCard(m_addressee);
104 }
105
106 return ret;
107 }
108
109 bool setCustomProperty(const QString &key, const QVariant &value) override
110 {
111 if (key == VCardProperty) {
112 QFile f(m_location.toLocalFile());
113 if (!f.open(QIODevice::WriteOnly))
114 return false;
115 f.write(value.toByteArray());
116 return true;
117 }
118 return false;
119 }
120
121 static QString createUri(const QString &path)
122 {
123 return QStringLiteral("vcard:/") + path;
124 }
125
126private:
127 KContacts::Addressee m_addressee;
128 QUrl m_location;
129};
130
131bool VCardDataSource::addContact(const QVariantMap &properties)
132{
133 if (!properties.contains("vcard"))
134 return false;
135
136 if (!QDir().mkpath(*vcardsWriteLocation))
137 return false;
138
139 QFile f(*vcardsWriteLocation + KFileUtils::suggestName(QUrl::fromLocalFile(*vcardsWriteLocation), QStringLiteral("contact.vcard")));
140 if (!f.open(QFile::WriteOnly)) {
141 qWarning() << "could not open file to write" << f.fileName();
142 return false;
143 }
144
145 f.write(properties.value("vcard").toByteArray());
146 return true;
147}
148
149bool VCardDataSource::deleteContact(const QString &uri)
150{
151 if (!uri.startsWith("vcard:/"))
152 return false;
153
154 QString path = uri;
155 path.remove("vcard:/");
156
157 if (!path.startsWith(*vcardsLocation))
158 return false;
159
160 return QFile::remove(path);
161}
162
163KPeopleVCard::KPeopleVCard()
164 : KPeople::AllContactsMonitor()
165 , m_fs(new KDirWatch(this))
166{
167 QDir().mkpath(*vcardsLocation);
168
169 processDirectory(QFileInfo(*vcardsLocation));
170
171 emitInitialFetchComplete(true);
172
173 connect(m_fs, &KDirWatch::dirty, this, [this](const QString &path) {
174 const QFileInfo fi(path);
175 if (fi.isFile())
176 processVCard(path);
177 else
178 processDirectory(fi);
179 });
180 connect(m_fs, &KDirWatch::created, this, [this](const QString &path) {
181 const QFileInfo fi(path);
182 if (fi.isFile())
183 processVCard(path);
184 else
185 processDirectory(fi);
186 });
187 connect(m_fs, &KDirWatch::deleted, this, &KPeopleVCard::deleteVCard);
188}
189
190KPeopleVCard::~KPeopleVCard()
191{
192}
193
194QMap<QString, AbstractContact::Ptr> KPeopleVCard::contacts()
195{
196 return m_contactForUri;
197}
198
199void KPeopleVCard::processDirectory(const QFileInfo &fi)
200{
201 const QDir dir(fi.absoluteFilePath());
202 {
203 const auto subdirs = dir.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot); // includes '.', ie. vcards from no subdir
204
205 for (const auto &subdir : subdirs) {
206 processDirectory(subdir);
207 }
208 }
209
210 {
211 const QFileInfoList subdirVcards = dir.entryInfoList({"*.vcard", "*.vcf"});
212 for (const QFileInfo &vcardFile : subdirVcards) {
213 processVCard(vcardFile.absoluteFilePath());
214 }
215 }
217}
218
219void KPeopleVCard::processVCard(const QString &path)
220{
221 m_fs->addFile(path);
222
223 QFile f(path);
224 bool b = f.open(QIODevice::ReadOnly);
225 if (!b) {
226 qWarning() << "error: couldn't open:" << path;
227 return;
228 }
229
231 const KContacts::Addressee addressee = conv.parseVCard(f.readAll());
232
233 QString uri = VCardContact::createUri(path);
234 auto it = m_contactForUri.find(uri);
235 if (it != m_contactForUri.end()) {
236 static_cast<VCardContact *>(it->data())->setAddressee(addressee);
237 static_cast<VCardContact *>(it->data())->setUrl(QUrl::fromLocalFile(path));
238 Q_EMIT contactChanged(uri, *it);
239 } else {
240 KPeople::AbstractContact::Ptr contact(new VCardContact(addressee, QUrl::fromLocalFile(path)));
241 m_contactForUri.insert(uri, contact);
242 Q_EMIT contactAdded(uri, contact);
243 }
244}
245
246void KPeopleVCard::deleteVCard(const QString &path)
247{
248 if (QFile::exists(path))
249 return;
250 QString uri = VCardContact::createUri(path);
251
252 const int r = m_contactForUri.remove(uri);
253 if (r)
255}
256
257QString KPeopleVCard::contactsVCardPath()
258{
259 return *vcardsLocation;
260}
261
262QString KPeopleVCard::contactsVCardWritePath()
263{
264 return *vcardsWriteLocation;
265}
266
267VCardDataSource::VCardDataSource(QObject *parent, const QVariantList &args)
268 : BasePersonsDataSourceV2(parent)
269{
270 Q_UNUSED(args);
271}
272
273VCardDataSource::~VCardDataSource()
274{
275}
276
277QString VCardDataSource::sourcePluginId() const
278{
279 return QStringLiteral("vcard");
280}
281
282AllContactsMonitor *VCardDataSource::createAllContactsMonitor()
283{
284 return new KPeopleVCard();
285}
286
287K_PLUGIN_CLASS_WITH_JSON(VCardDataSource, "kpeoplevcard.json")
288
289#include "kpeoplevcard.moc"
290
291#include "moc_kpeoplevcard.cpp"
Addressee parseVCard(const QByteArray &vcard) const
QByteArray createVCard(const Addressee &addr, Version version=v3_0) const
void addFile(const QString &file)
void deleted(const QString &path)
void addDir(const QString &path, WatchModes watchModes=WatchDirOnly)
void dirty(const QString &path)
void created(const QString &path)
virtual QVariant customProperty(const QString &key) const =0
Generic method to access a random contact property.
This class should be subclassed by each datasource and return a list of all contacts that the datasou...
void contactRemoved(const QString &contactUri)
DataSources should emit this whenever a contact is removed and they are no longer able to supply up-t...
void contactAdded(const QString &contactUri, const KPeople::AbstractContact::Ptr &contact)
DataSources should emit this whenever a contact is added.
void contactChanged(const QString &contactUri, const KPeople::AbstractContact::Ptr &contact)
DataSources should emit this whenever a known contact changes.
#define K_PLUGIN_CLASS_WITH_JSON(classname, jsonFile)
QString i18ndc(const char *domain, const char *context, const char *text, const TYPE &arg...)
KCOREADDONS_EXPORT QString suggestName(const QUrl &baseURL, const QString &oldName)
KIOCORE_EXPORT QString number(KIO::filesize_t size)
KIOCORE_EXPORT MkpathJob * mkpath(const QUrl &url, const QUrl &baseUrl=QUrl(), JobFlags flags=DefaultFlags)
QVariant location(const QVariant &res)
QString path(const QString &relativePath)
KIOCORE_EXPORT QString dir(const QString &fileClass)
KGuiItem properties()
QString name(StandardShortcut id)
bool mkpath(const QString &dirPath) const const
bool exists() const const
bool remove()
QString absoluteFilePath() const const
Q_EMITQ_EMIT
QString writableLocation(StandardLocation type)
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QUrl fromLocalFile(const QString &localFile)
QByteArray toByteArray() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri May 3 2024 11:48:33 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.