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