clazy: fix QString(QLatin1String(...))
[trojita.git] / src / Plugins / AbookAddressbook / AbookAddressbook.cpp
blobea6c38437713a3a251a806c3abc27ceb1e77513a
1 /* Copyright (C) 2012 Thomas Lübking <thomas.luebking@gmail.com>
2 Copyright (C) 2013 Caspar Schutijser <caspar@schutijser.com>
3 Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
4 Copyright (C) 2013 - 2014 Pali Rohár <pali.rohar@gmail.com>
6 This file is part of the Trojita Qt IMAP e-mail client,
7 http://trojita.flaska.net/
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License as
11 published by the Free Software Foundation; either version 2 of
12 the License or (at your option) version 3 or any later version
13 accepted by the membership of KDE e.V. (or its successor approved
14 by the membership of KDE e.V.), which shall act as a proxy
15 defined in Section 14 of version 3 of the license.
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with this program. If not, see <http://www.gnu.org/licenses/>.
26 #include "AbookAddressbook.h"
27 #include "be-contacts.h"
29 #include <QDir>
30 #include <QFileSystemWatcher>
31 #include <QSettings>
32 #include <QStandardItemModel>
33 #include <QStringBuilder>
34 #include <QTimer>
35 #include "Common/SettingsCategoryGuard.h"
37 class AbookAddressbookCompletionJob : public AddressbookCompletionJob
39 public:
40 AbookAddressbookCompletionJob(const QString &input, const QStringList &ignores, int max, AbookAddressbook *parent) :
41 AddressbookCompletionJob(parent), m_input(input), m_ignores(ignores), m_max(max), m_parent(parent) {}
43 public slots:
44 virtual void doStart()
46 NameEmailList completion = m_parent->complete(m_input, m_ignores, m_max);
47 emit completionAvailable(completion);
48 finished();
51 virtual void doStop()
53 emit error(AddressbookJob::Stopped);
54 finished();
57 private:
58 QString m_input;
59 QStringList m_ignores;
60 int m_max;
61 AbookAddressbook *m_parent;
65 class AbookAddressbookNamesJob : public AddressbookNamesJob
67 public:
68 AbookAddressbookNamesJob(const QString &email, AbookAddressbook *parent) :
69 AddressbookNamesJob(parent), m_email(email), m_parent(parent) {}
71 public slots:
72 virtual void doStart()
74 QStringList displayNames = m_parent->prettyNamesForAddress(m_email);
75 emit prettyNamesForAddressAvailable(displayNames);
76 finished();
79 virtual void doStop()
81 emit error(AddressbookJob::Stopped);
82 finished();
85 private:
86 QString m_email;
87 AbookAddressbook *m_parent;
91 AbookAddressbook::AbookAddressbook(QObject *parent): AddressbookPlugin(parent), m_updateTimer(0)
93 #define ADD(TYPE, KEY) \
94 m_fields << qMakePair<Type,QString>(TYPE, QLatin1String(KEY))
95 ADD(Name, "name");
96 ADD(Mail, "email");
97 ADD(Address, "address");
98 ADD(City, "city");
99 ADD(State, "state");
100 ADD(ZIP, "zip");
101 ADD(Country, "country");
102 ADD(Phone, "phone");
103 ADD(Workphone, "workphone");
104 ADD(Fax, "fax");
105 ADD(Mobile, "mobile");
106 ADD(Nick, "nick");
107 ADD(URL, "url");
108 ADD(Notes, "notes");
109 ADD(Anniversary, "anniversary");
110 ADD(Photo, "photo");
111 #undef ADD
113 m_contacts = new QStandardItemModel(this);
115 ensureAbookPath();
117 // read abook
118 readAbook(false);
120 m_filesystemWatcher = new QFileSystemWatcher(this);
121 m_filesystemWatcher->addPath(QDir::homePath() + QLatin1String("/.abook/addressbook"));
122 connect (m_filesystemWatcher, &QFileSystemWatcher::fileChanged, this, &AbookAddressbook::scheduleAbookUpdate);
125 AbookAddressbook::~AbookAddressbook()
129 AddressbookPlugin::Features AbookAddressbook::features() const
131 return FeatureAddressbookWindow | FeatureContactWindow | FeatureAddContact | FeatureEditContact | FeatureCompletion | FeaturePrettyNames;
134 AddressbookCompletionJob *AbookAddressbook::requestCompletion(const QString &input, const QStringList &ignores, int max)
136 return new AbookAddressbookCompletionJob(input, ignores, max, this);
139 AddressbookNamesJob *AbookAddressbook::requestPrettyNamesForAddress(const QString &email)
141 return new AbookAddressbookNamesJob(email, this);
144 void AbookAddressbook::openAddressbookWindow()
146 BE::Contacts *window = new BE::Contacts(this);
147 window->setAttribute(Qt::WA_DeleteOnClose, true);
148 //: Translators: BE::Contacts is the name of a stand-alone address book application.
149 //: BE refers to Bose/Einstein (condensate).
150 window->setWindowTitle(BE::Contacts::tr("BE::Contacts"));
151 window->show();
154 void AbookAddressbook::openContactWindow(const QString &email, const QString &displayName)
156 BE::Contacts *window = new BE::Contacts(this);
157 window->setAttribute(Qt::WA_DeleteOnClose, true);
158 window->manageContact(email, displayName);
159 window->show();
162 QStandardItemModel *AbookAddressbook::model() const
164 return m_contacts;
167 void AbookAddressbook::remonitorAdressbook()
169 m_filesystemWatcher->addPath(QDir::homePath() + QLatin1String("/.abook/addressbook"));
172 void AbookAddressbook::ensureAbookPath()
174 if (!QDir::home().exists(QStringLiteral(".abook"))) {
175 QDir::home().mkdir(QStringLiteral(".abook"));
177 QDir abook(QDir::homePath() + QLatin1String("/.abook/"));
178 QStringList abookrc;
179 QFile file(QDir::homePath() + QLatin1String("/.abook/abookrc"));
180 if (file.exists() && file.open(QIODevice::ReadWrite|QIODevice::Text)) {
181 abookrc = QString::fromLocal8Bit(file.readAll()).split(QStringLiteral("\n"));
182 bool havePhoto = false;
183 for (QStringList::iterator it = abookrc.begin(), end = abookrc.end(); it != end; ++it) {
184 if (it->contains(QLatin1String("preserve_fields")))
185 *it = QStringLiteral("set preserve_fields=all");
186 else if (it->contains(QLatin1String("photo")) && it->contains(QLatin1String("field")))
187 havePhoto = true;
189 if (!havePhoto)
190 abookrc << QStringLiteral("field photo = Photo");
191 } else {
192 abookrc << QStringLiteral("field photo = Photo") << QStringLiteral("set preserve_fields=all");
193 file.open(QIODevice::WriteOnly|QIODevice::Text);
195 if (file.isOpen()) {
196 if (file.isWritable()) {
197 file.seek(0);
198 file.write(abookrc.join(QStringLiteral("\n")).toLocal8Bit());
200 file.close();
202 QFile abookFile(abook.filePath(QStringLiteral("addressbook")));
203 if (!abookFile.exists()) {
204 abookFile.open(QIODevice::WriteOnly);
208 void AbookAddressbook::scheduleAbookUpdate()
210 // we need to schedule this because the filesystemwatcher usually fires while the file is re/written
211 if (!m_updateTimer) {
212 m_updateTimer = new QTimer(this);
213 m_updateTimer->setSingleShot(true);
214 connect(m_updateTimer, &QTimer::timeout, this, &AbookAddressbook::updateAbook);
216 m_updateTimer->start(500);
219 void AbookAddressbook::updateAbook()
221 readAbook(true);
222 // QFileSystemWatcher will usually unhook from the file when it's re/written - the entire watcher ain't so great :-(
223 m_filesystemWatcher->addPath(QDir::homePath() + QLatin1String("/.abook/addressbook"));
226 void AbookAddressbook::readAbook(bool update)
228 // QElapsedTimer profile;
229 // profile.start();
230 QSettings abook(QDir::homePath() + QLatin1String("/.abook/addressbook"), QSettings::IniFormat);
231 abook.setIniCodec("UTF-8");
232 QStringList contacts = abook.childGroups();
233 foreach (const QString &contact, contacts) {
234 Common::SettingsCategoryGuard guard(&abook, contact);
235 QStandardItem *item = 0;
236 QStringList mails;
237 if (update) {
238 QList<QStandardItem*> list = m_contacts->findItems(abook.value(QStringLiteral("name")).toString());
239 if (list.count() == 1)
240 item = list.at(0);
241 else if (list.count() > 1) {
242 mails = abook.value(QStringLiteral("email"), QString()).toStringList();
243 const QString mailString = mails.join(QStringLiteral("\n"));
244 foreach (QStandardItem *it, list) {
245 if (it->data(Mail).toString() == mailString) {
246 item = it;
247 break;
251 if (item && item->data(Dirty).toBool()) {
252 continue;
255 bool add = !item;
256 if (add)
257 item = new QStandardItem;
259 QMap<QString,QVariant> unknownKeys;
261 foreach (const QString &key, abook.allKeys()) {
262 QList<QPair<Type,QString> >::const_iterator field = m_fields.constBegin();
263 while (field != m_fields.constEnd()) {
264 if (field->second == key)
265 break;
266 ++field;
268 if (field == m_fields.constEnd())
269 unknownKeys.insert(key, abook.value(key));
270 else if (field->first == Mail) {
271 if (mails.isEmpty())
272 mails = abook.value(field->second, QString()).toStringList(); // to fix the name field
273 item->setData( mails.join(QStringLiteral("\n")), Mail );
275 else
276 item->setData( abook.value(field->second, QString()), field->first );
279 // attempt to fix the name field
280 if (item->data(Name).toString().isEmpty()) {
281 if (!mails.isEmpty())
282 item->setData( mails.at(0), Name );
284 if (item->data(Name).toString().isEmpty()) {
285 delete item;
286 continue; // junk or format spec entry
289 item->setData( unknownKeys, UnknownKeys );
291 if (add)
292 m_contacts->appendRow( item );
294 // const qint64 elapsed = profile.elapsed();
295 // qDebug() << "reading too" << elapsed << "ms";
298 void AbookAddressbook::saveContacts()
300 m_filesystemWatcher->blockSignals(true);
301 QSettings abook(QDir::homePath() + QLatin1String("/.abook/addressbook"), QSettings::IniFormat);
302 abook.setIniCodec("UTF-8");
303 abook.clear();
304 for (int i = 0; i < m_contacts->rowCount(); ++i) {
305 Common::SettingsCategoryGuard guard(&abook, QString::number(i));
306 QStandardItem *item = m_contacts->item(i);
307 for (QList<QPair<Type,QString> >::const_iterator it = m_fields.constBegin(),
308 end = m_fields.constEnd(); it != end; ++it) {
309 if (it->first == Mail)
310 abook.setValue(QStringLiteral("email"), item->data(Mail).toString().split(QStringLiteral("\n")));
311 else {
312 const QVariant v = item->data(it->first);
313 if (!v.toString().isEmpty())
314 abook.setValue(it->second, v);
317 QMap<QString,QVariant> unknownKeys = item->data( UnknownKeys ).toMap();
318 for (QMap<QString,QVariant>::const_iterator it = unknownKeys.constBegin(),
319 end = unknownKeys.constEnd(); it != end; ++it) {
320 abook.setValue(it.key(), it.value());
323 abook.sync();
324 m_filesystemWatcher->blockSignals(false);
327 static inline bool ignore(const QString &string, const QStringList &ignores)
329 Q_FOREACH (const QString &ignore, ignores) {
330 if (ignore.contains(string, Qt::CaseInsensitive))
331 return true;
333 return false;
336 NameEmailList AbookAddressbook::complete(const QString &string, const QStringList &ignores, int max) const
338 NameEmailList list;
339 if (string.isEmpty())
340 return list;
341 // In e-mail addresses, dot, dash, _ and @ shall be treated as delimiters
342 QRegExp mailMatch = QRegExp(QString::fromUtf8("[\\.\\-_@]%1").arg(QRegExp::escape(string)), Qt::CaseInsensitive);
343 // In human readable names, match on word boundaries
344 QRegExp nameMatch = QRegExp(QString::fromUtf8("\\b%1").arg(QRegExp::escape(string)), Qt::CaseInsensitive);
345 // These REs are still not perfect, they won't match on e.g. ".net" or "-project", but screw these I say
346 for (int i = 0; i < m_contacts->rowCount(); ++i) {
347 QStandardItem *item = m_contacts->item(i);
348 QString contactName = item->data(Name).toString();
349 // several mail addresses per contact are stored newline delimited
350 QStringList contactMails(item->data(Mail).toString().split(QLatin1Char('\n'), QString::SkipEmptyParts));
351 if (contactName.contains(nameMatch)) {
352 Q_FOREACH (const QString &mail, contactMails) {
353 if (ignore(mail, ignores))
354 continue;
355 list << NameEmail(contactName, mail);
356 if (list.count() == max)
357 return list;
359 continue;
361 Q_FOREACH (const QString &mail, contactMails) {
362 if (mail.startsWith(string, Qt::CaseInsensitive) ||
363 // don't match on the TLD
364 mail.section(QLatin1Char('.'), 0, -2).contains(mailMatch)) {
365 if (ignore(mail, ignores))
366 continue;
367 list << NameEmail(contactName, mail);
368 if (list.count() == max)
369 return list;
373 return list;
376 QStringList AbookAddressbook::prettyNamesForAddress(const QString &mail) const
378 QStringList res;
379 for (int i = 0; i < m_contacts->rowCount(); ++i) {
380 QStandardItem *item = m_contacts->item(i);
381 if (QString::compare(item->data(Mail).toString(), mail, Qt::CaseInsensitive) == 0)
382 res << item->data(Name).toString();
384 return res;
388 QString trojita_plugin_AbookAddressbookPlugin::name() const
390 return QStringLiteral("abookaddressbook");
393 QString trojita_plugin_AbookAddressbookPlugin::description() const
395 return tr("Addressbook in ~/.abook/");
398 AddressbookPlugin *trojita_plugin_AbookAddressbookPlugin::create(QObject *parent, QSettings *)
400 return new AbookAddressbook(parent);