1 /*
2 Copyright (C) 2015 Aleix Pol Gonzalez <aleixpol@kde.org>
3 Copyright (C) 2015 Martin Klapetek <mklapetek@kde.org>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 #include "kpeoplevcard.h"
21 #include <QDebug>
22 #include <QImage>
23 #include <QDir>
24 #include <QStandardPaths>
25 #include <KContacts/VCardConverter>
26 #include <KContacts/Picture>
27 #include <KLocalizedString>
28
29 #include <KPluginFactory>
30 #include <KPluginLoader>
31 #include <KFileUtils>
32
33 using namespace KPeople;
34
35 Q_GLOBAL_STATIC_WITH_ARGS(QString, vcardsLocation, (QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + ("/kpeoplevcard")))
36 Q_GLOBAL_STATIC_WITH_ARGS(QString, vcardsWriteLocation, (QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + ("/kpeoplevcard/own/")))
37
38 class VCardContact : public AbstractEditableContact
39 {
40 public:
VCardContact()41 VCardContact() {}
VCardContact(const KContacts::Addressee & addr,const QUrl & location)42 VCardContact(const KContacts::Addressee& addr, const QUrl &location) : m_addressee(addr), m_location(location) {}
setAddressee(const KContacts::Addressee & addr)43 void setAddressee(const KContacts::Addressee& addr) { m_addressee = addr; }
setUrl(const QUrl & url)44 void setUrl(const QUrl &url) { m_location = url; }
45
customProperty(const QString & key) const46 QVariant customProperty(const QString & key) const override
47 {
48 QVariant ret;
49 if (key == NameProperty) {
50 const QString name = m_addressee.realName();
51 if (!name.isEmpty()) {
52 return name;
53 }
54
55 // If both first and last name are set combine them to a full name
56 if (!m_addressee.givenName().isEmpty() && !m_addressee.familyName().isEmpty())
57 return i18nc("given-name family-name", "%1 %2", m_addressee.givenName(), m_addressee.familyName());
58
59 // If only one of them is set just return what we know
60 if (!m_addressee.givenName().isEmpty())
61 return m_addressee.givenName();
62 if (!m_addressee.familyName().isEmpty())
63 return m_addressee.familyName();
64
65 // Fall back to other identifiers
66 if (!m_addressee.preferredEmail().isEmpty()) {
67 return m_addressee.preferredEmail();
68 }
69 if (!m_addressee.phoneNumbers().isEmpty()) {
70 return m_addressee.phoneNumbers().at(0).number();
71 }
72 return QVariant();
73 } else if (key == EmailProperty)
74 return m_addressee.preferredEmail();
75 else if (key == AllEmailsProperty)
76 return m_addressee.emails();
77 else if (key == PictureProperty)
78 return m_addressee.photo().data();
79 else if (key == AllPhoneNumbersProperty) {
80 const auto phoneNumbers = m_addressee.phoneNumbers();
81 QVariantList numbers;
82 for (const KContacts::PhoneNumber &phoneNumber : phoneNumbers) {
83 // convert from KContacts specific format to QString
84 numbers << phoneNumber.number();
85 }
86 return numbers;
87 } else if (key == PhoneNumberProperty) {
88 return m_addressee.phoneNumbers().isEmpty() ? QVariant() : m_addressee.phoneNumbers().at(0).number();
89 } else if (key == VCardProperty) {
90 KContacts::VCardConverter converter;
91 return converter.createVCard(m_addressee);
92 }
93
94 return ret;
95 }
96
setCustomProperty(const QString & key,const QVariant & value)97 bool setCustomProperty(const QString & key, const QVariant & value) override {
98 if (key == VCardProperty) {
99 QFile f(m_location.toLocalFile());
100 if (!f.open(QIODevice::WriteOnly))
101 return false;
102 f.write(value.toByteArray());
103 return true;
104 }
105 return false;
106 }
107
createUri(const QString & path)108 static QString createUri(const QString& path) {
109 return QStringLiteral("vcard:/") + path;
110 }
111 private:
112 KContacts::Addressee m_addressee;
113 QUrl m_location;
114 };
115
addContact(const QVariantMap & properties)116 bool VCardDataSource::addContact(const QVariantMap & properties)
117 {
118 if (!properties.contains("vcard"))
119 return false;
120
121 if (!QDir().mkpath(*vcardsWriteLocation))
122 return false;
123
124 QFile f(*vcardsWriteLocation + KFileUtils::suggestName(QUrl::fromLocalFile(*vcardsWriteLocation), QStringLiteral("contact.vcard")));
125 if (!f.open(QFile::WriteOnly)) {
126 qWarning() << "could not open file to write" << f.fileName();
127 return false;
128 }
129
130 f.write(properties.value("vcard").toByteArray());
131 return true;
132 }
133
deleteContact(const QString & uri)134 bool VCardDataSource::deleteContact(const QString &uri)
135 {
136 if (!uri.startsWith("vcard:/"))
137 return false;
138
139 QString path = uri;
140 path.remove("vcard:/");
141
142 if (!path.startsWith(*vcardsLocation))
143 return false;
144
145 return QFile::remove(path);
146 }
147
KPeopleVCard()148 KPeopleVCard::KPeopleVCard()
149 : KPeople::AllContactsMonitor()
150 , m_fs(new KDirWatch(this))
151 {
152 QDir().mkpath(*vcardsLocation);
153
154 processDirectory(QFileInfo(*vcardsLocation));
155
156 connect(m_fs, &KDirWatch::dirty, this, [this](const QString& path) {
157 const QFileInfo fi(path);
158 if (fi.isFile())
159 processVCard(path);
160 else
161 processDirectory(fi);
162 });
163 connect(m_fs, &KDirWatch::created, this, [this] (const QString &path) {
164 const QFileInfo fi(path);
165 if (fi.isFile())
166 processVCard(path);
167 else
168 processDirectory(fi);
169 });
170 connect(m_fs, &KDirWatch::deleted, this, &KPeopleVCard::deleteVCard);
171 }
172
~KPeopleVCard()173 KPeopleVCard::~KPeopleVCard()
174 {}
175
contacts()176 QMap<QString, AbstractContact::Ptr> KPeopleVCard::contacts()
177 {
178 return m_contactForUri;
179 }
180
processDirectory(const QFileInfo & fi)181 void KPeopleVCard::processDirectory(const QFileInfo& fi)
182 {
183 const QDir dir(fi.absoluteFilePath());
184 {
185 const auto subdirs = dir.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot); // includes '.', ie. vcards from no subdir
186
187 for (const auto &subdir : subdirs) {
188 processDirectory(subdir);
189 }
190 }
191
192 {
193 const QFileInfoList subdirVcards = dir.entryInfoList({"*.vcard", "*.vcf"});
194 for (const QFileInfo &vcardFile : subdirVcards) {
195 processVCard(vcardFile.absoluteFilePath());
196 }
197 }
198 m_fs->addDir(dir.absolutePath(), KDirWatch::WatchDirOnly | KDirWatch::WatchSubDirs | KDirWatch::WatchFiles);
199 }
200
processVCard(const QString & path)201 void KPeopleVCard::processVCard(const QString &path)
202 {
203 m_fs->addFile(path);
204
205 QFile f(path);
206 bool b = f.open(QIODevice::ReadOnly);
207 if (!b) {
208 qWarning() << "error: couldn't open:" << path;
209 return;
210 }
211
212 KContacts::VCardConverter conv;
213 const KContacts::Addressee addressee = conv.parseVCard(f.readAll());
214
215 QString uri = VCardContact::createUri(path);
216 auto it = m_contactForUri.find(uri);
217 if (it != m_contactForUri.end()) {
218 static_cast<VCardContact*>(it->data())->setAddressee(addressee);
219 static_cast<VCardContact*>(it->data())->setUrl(QUrl::fromLocalFile(path));
220 Q_EMIT contactChanged(uri, *it);
221 } else {
222 KPeople::AbstractContact::Ptr contact(new VCardContact(addressee, QUrl::fromLocalFile(path)));
223 m_contactForUri.insert(uri, contact);
224 Q_EMIT contactAdded(uri, contact);
225 }
226 }
227
deleteVCard(const QString & path)228 void KPeopleVCard::deleteVCard(const QString &path)
229 {
230 if (QFile::exists(path))
231 return;
232 QString uri = VCardContact::createUri(path);
233
234 const int r = m_contactForUri.remove(uri);
235 if (r)
236 Q_EMIT contactRemoved(uri);
237 }
238
contactsVCardPath()239 QString KPeopleVCard::contactsVCardPath()
240 {
241 return *vcardsLocation;
242 }
243
contactsVCardWritePath()244 QString KPeopleVCard::contactsVCardWritePath()
245 {
246 return *vcardsWriteLocation;
247 }
248
VCardDataSource(QObject * parent,const QVariantList & args)249 VCardDataSource::VCardDataSource(QObject *parent, const QVariantList &args)
250 : BasePersonsDataSourceV2(parent)
251 {
252 Q_UNUSED(args);
253 }
254
~VCardDataSource()255 VCardDataSource::~VCardDataSource()
256 {
257 }
258
sourcePluginId() const259 QString VCardDataSource::sourcePluginId() const
260 {
261 return QStringLiteral("vcard");
262 }
263
createAllContactsMonitor()264 AllContactsMonitor* VCardDataSource::createAllContactsMonitor()
265 {
266 return new KPeopleVCard();
267 }
268
269 K_PLUGIN_FACTORY_WITH_JSON( VCardDataSourceFactory, "kpeoplevcard.json", registerPlugin<VCardDataSource>(); )
270 K_EXPORT_PLUGIN( VCardDataSourceFactory("kpeoplevcard") )
271
272 #include "kpeoplevcard.moc"
273