Continue to implement fullsync
[kdepim.git] / kleopatra / smartcard / readerstatus.cpp
blobabf8105ad51811f867b215ad6cc7c3a496ad66ff
1 /* -*- mode: c++; c-basic-offset:4 -*-
2 smartcard/readerstatus.cpp
4 This file is part of Kleopatra, the KDE keymanager
5 Copyright (c) 2009 Klarälvdalens Datakonsult AB
7 Kleopatra is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 Kleopatra is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 In addition, as a special exception, the copyright holders give
22 permission to link the code of this program with any edition of
23 the Qt library by Trolltech AS, Norway (or with modified versions
24 of Qt that use the same license as Qt), and distribute linked
25 combinations including the two. You must obey the GNU General
26 Public License in all respects for all of the code used other than
27 Qt. If you modify this file, you may extend this exception to
28 your version of the file, but you are not obligated to do so. If
29 you do not wish to do so, delete this exception statement from
30 your version.
33 #include <config-kleopatra.h>
35 #include "readerstatus.h"
37 #include <utils/gnupg-helper.h>
38 #include <utils/kdsignalblocker.h>
39 #include <utils/filesystemwatcher.h>
41 #include <Libkleo/Stl_Util>
43 #include <gpgme++/context.h>
44 #include <gpgme++/assuanresult.h>
45 #include <gpgme++/defaultassuantransaction.h>
46 #include <gpgme++/key.h>
47 #include <gpgme++/keylistresult.h>
49 #include <gpg-error.h>
51 #include "kleopatra_debug.h"
53 #include <QStringList>
54 #include <QDir>
55 #include <QFileInfo>
56 #include <QMutex>
57 #include <QWaitCondition>
58 #include <QThread>
59 #include <QPointer>
61 #include <boost/algorithm/string/split.hpp>
62 #include <boost/algorithm/string/classification.hpp>
63 #include <boost/algorithm/string/case_conv.hpp>
64 #include <boost/static_assert.hpp>
65 #include <boost/range.hpp>
66 #include <boost/bind.hpp>
68 #include <vector>
69 #include <set>
70 #include <list>
71 #include <algorithm>
72 #include <iterator>
73 #include <utility>
74 #include <cstdlib>
76 using namespace Kleo;
77 using namespace Kleo::SmartCard;
78 using namespace GpgME;
79 using namespace boost;
81 static const unsigned int CHECK_INTERVAL = 2000; // msecs
83 static ReaderStatus *self = 0;
85 struct CardInfo {
86 CardInfo()
87 : fileName(),
88 status(ReaderStatus::NoCard),
89 appType(ReaderStatus::UnknownApplication),
90 appVersion(-1)
94 CardInfo(const QString &fn, ReaderStatus::Status s)
95 : fileName(fn),
96 status(s),
97 appType(ReaderStatus::UnknownApplication),
98 appVersion(-1)
103 QString fileName;
104 ReaderStatus::Status status;
105 std::string serialNumber;
106 ReaderStatus::AppType appType;
107 int appVersion;
108 std::vector<ReaderStatus::PinState> pinStates;
111 static const char *flags[] = {
112 "NOCARD",
113 "PRESENT",
114 "ACTIVE",
115 "USABLE",
117 BOOST_STATIC_ASSERT((sizeof flags / sizeof * flags == ReaderStatus::_NumScdStates));
119 static const char *prettyFlags[] = {
120 "NoCard",
121 "CardPresent",
122 "CardActive",
123 "CardUsable",
124 "CardCanLearnKeys",
125 "CardHasNullPin",
126 "CardError",
128 BOOST_STATIC_ASSERT((sizeof prettyFlags / sizeof * prettyFlags == ReaderStatus::NumStates));
130 static QByteArray read_file(const QString &fileName)
132 QFile file(fileName);
133 if (!file.exists()) {
134 qCDebug(KLEOPATRA_LOG) << "read_file: file" << fileName << "does not exist";
135 return QByteArray();
137 if (!file.open(QIODevice::ReadOnly)) {
138 qCDebug(KLEOPATRA_LOG) << "read_file: failed to open" << fileName << ':' << file.errorString();
139 return QByteArray();
141 return file.readAll().trimmed();
144 static unsigned int parseFileName(const QString &fileName, bool *ok)
146 QRegExp rx(QStringLiteral("reader_(\\d+)\\.status"));
147 if (ok) {
148 *ok = false;
150 if (rx.exactMatch(QFileInfo(fileName).fileName())) {
151 return rx.cap(1).toUInt(ok, 10);
153 return 0;
156 namespace
158 template <typename T_Target, typename T_Source>
159 std::auto_ptr<T_Target> dynamic_pointer_cast(std::auto_ptr<T_Source> &in)
161 if (T_Target *const target = dynamic_cast<T_Target *>(in.get())) {
162 in.release();
163 return std::auto_ptr<T_Target>(target);
164 } else {
165 return std::auto_ptr<T_Target>();
169 template <typename T>
170 const T &_trace__impl(const T &t, const char *msg)
172 qCDebug(KLEOPATRA_LOG) << msg << t;
173 return t;
175 #define TRACE( x ) _trace__impl( x, #x )
178 static QDebug operator<<(QDebug s, const std::vector< std::pair<std::string, std::string> > &v)
180 typedef std::pair<std::string, std::string> pair;
181 s << '(';
182 Q_FOREACH (const pair &p, v) {
183 s << "status(" << QString::fromStdString(p.first) << ") =" << QString::fromStdString(p.second) << endl;
185 return s << ')';
188 static const char *app_types[] = {
189 "_", // will hopefully never be used as an app-type :)
190 "openpgp",
191 "nks",
192 "p15",
193 "dinsig",
194 "geldkarte",
196 BOOST_STATIC_ASSERT((sizeof app_types / sizeof * app_types == ReaderStatus::NumAppTypes));
198 static ReaderStatus::AppType parse_app_type(const std::string &s)
200 qCDebug(KLEOPATRA_LOG) << "parse_app_type(" << s.c_str() << ")";
201 const char **it = std::find(begin(app_types), end(app_types), to_lower_copy(s));
202 if (it == end(app_types)) {
203 return TRACE(ReaderStatus::UnknownApplication);
205 return TRACE(static_cast<ReaderStatus::AppType>(it - begin(app_types)));
209 static int parse_app_version(const std::string &s)
211 return std::atoi(s.c_str());
214 static ReaderStatus::PinState parse_pin_state(const std::string &s)
216 switch (int i = std::atoi(s.c_str())) {
217 case -4: return ReaderStatus::NullPin;
218 case -3: return ReaderStatus::PinBlocked;
219 case -2: return ReaderStatus::NoPin;
220 case -1: return ReaderStatus::UnknownPinState;
221 default:
222 if (i < 0) {
223 return ReaderStatus::UnknownPinState;
224 } else {
225 return ReaderStatus::PinOk;
230 static std::auto_ptr<DefaultAssuanTransaction> gpgagent_transact(shared_ptr<Context> &gpgAgent, const char *command, Error &err)
232 #ifdef DEBUG_SCREADER
233 qCDebug(KLEOPATRA_LOG) << "gpgagent_transact(" << command << ")";
234 #endif
235 const AssuanResult res = gpgAgent->assuanTransact(command);
236 err = res.error();
237 if (!err.code()) {
238 err = res.assuanError();
240 if (err.code()) {
241 #ifdef DEBUG_SCREADER
242 qCDebug(KLEOPATRA_LOG) << "gpgagent_transact(" << command << "):" << QString::fromLocal8Bit(err.asString());
243 #endif
244 if (err.code() >= GPG_ERR_ASS_GENERAL && err.code() <= GPG_ERR_ASS_UNKNOWN_INQUIRE) {
245 qCDebug(KLEOPATRA_LOG) << "Assuan problem, killing context";
246 gpgAgent.reset();
248 return std::auto_ptr<DefaultAssuanTransaction>();
250 std::auto_ptr<AssuanTransaction> t = gpgAgent->takeLastAssuanTransaction();
251 return dynamic_pointer_cast<DefaultAssuanTransaction>(t);
254 // returns const std::string so template deduction in boost::split works, and we don't need a temporary
255 static const std::string scd_getattr_status(shared_ptr<Context> &gpgAgent, const char *what, Error &err)
257 std::string cmd = "SCD GETATTR ";
258 cmd += what;
259 const std::auto_ptr<DefaultAssuanTransaction> t = gpgagent_transact(gpgAgent, cmd.c_str(), err);
260 if (t.get()) {
261 qCDebug(KLEOPATRA_LOG) << "scd_getattr_status(" << what << "): got" << t->statusLines();
262 return t->firstStatusLine(what);
263 } else {
264 qCDebug(KLEOPATRA_LOG) << "scd_getattr_status(" << what << "): t == NULL";
265 return std::string();
269 static unsigned int parse_event_counter(const std::string &str)
271 unsigned int result;
272 if (sscanf(str.c_str(), "%*u %*u %u ", &result) == 1) {
273 return result;
275 return -1;
278 static unsigned int get_event_counter(shared_ptr<Context> &gpgAgent)
280 Error err;
281 const std::auto_ptr<DefaultAssuanTransaction> t = gpgagent_transact(gpgAgent, "GETEVENTCOUNTER", err);
282 if (err.code()) {
283 qCDebug(KLEOPATRA_LOG) << "get_event_counter(): got error" << err.asString();
285 if (t.get()) {
286 #ifdef DEBUG_SCREADER
287 qCDebug(KLEOPATRA_LOG) << "get_event_counter(): got" << t->statusLines();
288 #endif
289 return parse_event_counter(t->firstStatusLine("EVENTCOUNTER"));
290 } else {
291 qCDebug(KLEOPATRA_LOG) << "scd_getattr_status(): t == NULL";
292 return -1;
296 // returns const std::string so template deduction in boost::split works, and we don't need a temporary
297 static const std::string gpgagent_data(shared_ptr<Context> &gpgAgent, const char *what, Error &err)
299 const std::auto_ptr<DefaultAssuanTransaction> t = gpgagent_transact(gpgAgent, what, err);
300 if (t.get()) {
301 return t->data();
302 } else {
303 return std::string();
307 static std::string parse_keypairinfo(const std::string &kpi)
309 static const char hexchars[] = "0123456789abcdefABCDEF";
310 return '&' + kpi.substr(0, kpi.find_first_not_of(hexchars));
313 static bool parse_keypairinfo_and_lookup_key(Context *ctx, const std::string &kpi)
315 if (!ctx) {
316 return false;
318 const std::string pattern = parse_keypairinfo(kpi);
319 qCDebug(KLEOPATRA_LOG) << "parse_keypairinfo_and_lookup_key: pattern=" << pattern.c_str();
320 if (const Error err = ctx->startKeyListing(pattern.c_str())) {
321 qCDebug(KLEOPATRA_LOG) << "parse_keypairinfo_and_lookup_key: startKeyListing failed:" << err.asString();
322 return false;
324 Error e;
325 const Key key = ctx->nextKey(e);
326 ctx->endKeyListing();
327 qCDebug(KLEOPATRA_LOG) << "parse_keypairinfo_and_lookup_key: e=" << e.code() << "; key.isNull()" << key.isNull();
328 return !e && !key.isNull();
331 static CardInfo get_card_status(const QString &fileName, unsigned int idx, shared_ptr<Context> &gpg_agent)
333 #ifdef DEBUG_SCREADER
334 qCDebug(KLEOPATRA_LOG) << "get_card_status(" << fileName << ',' << idx << ',' << gpg_agent.get() << ')';
335 #endif
336 CardInfo ci(fileName, ReaderStatus::CardUsable);
337 if (idx != 0 || !gpg_agent) {
338 return ci;
340 Error err;
341 ci.serialNumber = gpgagent_data(gpg_agent, "SCD SERIALNO", err);
342 if (err.code() == GPG_ERR_CARD_NOT_PRESENT || err.code() == GPG_ERR_CARD_REMOVED) {
343 ci.status = ReaderStatus::NoCard;
344 return ci;
346 if (err.code()) {
347 ci.status = ReaderStatus::CardError;
348 return ci;
350 ci.appType = parse_app_type(scd_getattr_status(gpg_agent, "APPTYPE", err));
351 if (err.code()) {
352 return ci;
354 if (ci.appType != ReaderStatus::NksApplication) {
355 qCDebug(KLEOPATRA_LOG) << "get_card_status: not a NetKey card, giving up";
356 return ci;
358 ci.appVersion = parse_app_version(scd_getattr_status(gpg_agent, "NKS-VERSION", err));
359 if (err.code()) {
360 return ci;
362 if (ci.appVersion != 3) {
363 qCDebug(KLEOPATRA_LOG) << "get_card_status: not a NetKey v3 card, giving up";
364 return ci;
367 // the following only works for NKS v3...
368 std::vector<std::string> chvStatus;
369 chvStatus.reserve(4); // expected number of fields
370 split(chvStatus, scd_getattr_status(gpg_agent, "CHV-STATUS", err), is_any_of(" \t"), token_compress_on);
371 if (err.code()) {
372 return ci;
374 std::transform(chvStatus.begin(), chvStatus.end(),
375 std::back_inserter(ci.pinStates),
376 parse_pin_state);
378 if (kdtools::contains(ci.pinStates, ReaderStatus::NullPin)) {
379 ci.status = ReaderStatus::CardHasNullPin;
380 return ci;
383 // check for keys to learn:
384 const std::auto_ptr<DefaultAssuanTransaction> result = gpgagent_transact(gpg_agent, "SCD LEARN --keypairinfo", err);
385 if (err.code() || !result.get()) {
386 return ci;
388 const std::vector<std::string> keyPairInfos = result->statusLine("KEYPAIRINFO");
389 if (keyPairInfos.empty()) {
390 return ci;
393 // check that any of the
394 const std::auto_ptr<Context> klc(Context::createForProtocol(CMS)); // what about OpenPGP?
395 if (!klc.get()) {
396 return ci;
398 klc->setKeyListMode(Ephemeral);
400 if (kdtools::any(keyPairInfos, !boost::bind(&parse_keypairinfo_and_lookup_key, klc.get(), _1))) {
401 ci.status = ReaderStatus::CardCanLearnKeys;
404 #ifdef DEBUG_SCREADER
405 qCDebug(KLEOPATRA_LOG) << "get_card_status: ci.status " << prettyFlags[ci.status];
406 #endif
408 return ci;
411 static std::vector<CardInfo> update_cardinfo(const QString &gnupgHomePath, shared_ptr<Context> &gpgAgent)
413 #ifdef DEBUG_SCREADER
414 qCDebug(KLEOPATRA_LOG) << "<update_cardinfo>";
415 #endif
416 const QDir gnupgHome(gnupgHomePath);
417 if (!gnupgHome.exists()) {
418 qCWarning(KLEOPATRA_LOG) << "gnupg home" << gnupgHomePath << "does not exist!";
421 const CardInfo ci = get_card_status(gnupgHome.absoluteFilePath(QStringLiteral("reader_0.status")), 0, gpgAgent);
422 #ifdef DEBUG_SCREADER
423 qCDebug(KLEOPATRA_LOG) << "</update_cardinfo>";
424 #endif
425 return std::vector<CardInfo>(1, ci);
428 static bool check_event_counter_changed(shared_ptr<Context> &gpg_agent, unsigned int &counter)
430 const unsigned int oldCounter = counter;
431 counter = get_event_counter(gpg_agent);
432 if (oldCounter != counter) {
433 #ifdef DEBUG_SCREADER
434 qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: events:" << oldCounter << "->" << counter;
435 #endif
436 return true;
437 } else {
438 return false;
442 struct Transaction {
443 QByteArray command;
444 QPointer<QObject> receiver;
445 const char *slot;
446 GpgME::Error error;
449 static const Transaction checkTransaction = { "__check__", 0, 0, Error() };
450 static const Transaction updateTransaction = { "__update__", 0, 0, Error() };
451 static const Transaction quitTransaction = { "__quit__", 0, 0, Error() };
453 namespace
455 class ReaderStatusThread : public QThread
457 Q_OBJECT
458 public:
459 explicit ReaderStatusThread(QObject *parent = Q_NULLPTR)
460 : QThread(parent),
461 m_gnupgHomePath(Kleo::gnupgHomeDirectory()),
462 m_transactions(1, updateTransaction) // force initial scan
464 connect(this, &ReaderStatusThread::oneTransactionFinished,
465 this, &ReaderStatusThread::slotOneTransactionFinished);
468 std::vector<CardInfo> cardInfos() const
470 const QMutexLocker locker(&m_mutex);
471 return m_cardInfos;
474 ReaderStatus::Status cardStatus(unsigned int slot) const
476 const QMutexLocker locker(&m_mutex);
477 if (slot < m_cardInfos.size()) {
478 return m_cardInfos[slot].status;
479 } else {
480 return ReaderStatus::NoCard;
484 void addTransaction(const Transaction &t)
486 const QMutexLocker locker(&m_mutex);
487 m_transactions.push_back(t);
488 m_waitForTransactions.wakeOne();
491 // make QThread::sleep public
492 using QThread::sleep;
494 Q_SIGNALS:
495 void anyCardHasNullPinChanged(bool);
496 void anyCardCanLearnKeysChanged(bool);
497 void cardStatusChanged(unsigned int, Kleo::SmartCard::ReaderStatus::Status);
498 void oneTransactionFinished();
500 public Q_SLOTS:
501 void ping()
503 qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[GUI]::ping()";
504 addTransaction(updateTransaction);
507 void stop()
509 const QMutexLocker locker(&m_mutex);
510 m_transactions.push_front(quitTransaction);
511 m_waitForTransactions.wakeOne();
514 void slotReaderStatusFileChanged()
516 const QDir gnupgHome(m_gnupgHomePath);
517 if (!gnupgHome.exists()) {
518 qCWarning(KLEOPATRA_LOG) << "gnupg home" << m_gnupgHomePath << "does not exist!";
519 return;
522 QStringList files = gnupgHome.entryList(QStringList(QStringLiteral("reader_*.status")), QDir::Files, QDir::Name);
523 bool *dummy = 0;
524 kdtools::sort(files, boost::bind(parseFileName, _1, dummy) < boost::bind(parseFileName, _2, dummy));
526 std::vector<QByteArray> contents;
528 Q_FOREACH (const QString &file, files) {
529 bool ok = false;
530 const unsigned int idx = parseFileName(file, &ok);
531 if (!ok) {
532 qCDebug(KLEOPATRA_LOG) << "filename" << file << ": cannot parse reader slot number";
533 continue;
535 assert(idx >= contents.size());
536 contents.resize(idx);
537 contents.push_back(read_file(gnupgHome.absoluteFilePath(file)));
540 // canonicalise by removing empty stuff from the end
541 while (!contents.empty() && contents.back().isEmpty()) {
542 contents.pop_back();
545 if (contents != readerStatusFileContents) {
546 ping();
549 readerStatusFileContents.swap(contents);
552 private Q_SLOTS:
553 void slotOneTransactionFinished()
555 std::list<Transaction> ft;
556 KDAB_SYNCHRONIZED(m_mutex)
557 ft.splice(ft.begin(), m_finishedTransactions);
558 Q_FOREACH (const Transaction &t, ft)
559 if (t.receiver && t.slot && *t.slot) {
560 QMetaObject::invokeMethod(t.receiver, t.slot, Qt::DirectConnection, Q_ARG(GpgME::Error, t.error));
564 private:
565 void run() Q_DECL_OVERRIDE {
567 shared_ptr<Context> gpgAgent;
568 unsigned int eventCounter = -1;
570 while (true)
573 QByteArray command;
574 bool nullSlot;
575 std::list<Transaction> item;
576 std::vector<CardInfo> oldCardInfos;
578 if (!gpgAgent) {
579 Error err;
580 std::auto_ptr<Context> c = Context::createForEngine(AssuanEngine, &err);
581 if (err.code() == GPG_ERR_NOT_SUPPORTED) {
582 return;
584 gpgAgent = c;
587 KDAB_SYNCHRONIZED(m_mutex) {
589 while (m_transactions.empty()) {
590 // go to sleep waiting for more work:
591 #ifdef DEBUG_SCREADER
592 qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: .zZZ";
593 #endif
594 if (!m_waitForTransactions.wait(&m_mutex, CHECK_INTERVAL)) {
595 m_transactions.push_front(checkTransaction);
597 #ifdef DEBUG_SCREADER
598 qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: .oOO";
599 #endif
602 // splice off the first transaction without
603 // copying, so we own it without really importing
604 // it into this thread (the QPointer isn't
605 // thread-safe):
606 item.splice(item.end(),
607 m_transactions, m_transactions.begin());
609 // make local copies of the interesting stuff so
610 // we can release the mutex again:
611 command = item.front().command;
612 nullSlot = !item.front().slot;
613 oldCardInfos = m_cardInfos;
616 #ifdef DEBUG_SCREADER
617 qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: new iteration command=" << command << " ; nullSlot=" << nullSlot;
618 #endif
619 // now, let's see what we got:
621 if (nullSlot && command == quitTransaction.command) {
622 return; // quit
625 if ((nullSlot && command == updateTransaction.command) ||
626 (nullSlot && command == checkTransaction.command)) {
628 if (nullSlot && command == checkTransaction.command && !check_event_counter_changed(gpgAgent, eventCounter)) {
629 continue; // early out
632 std::vector<CardInfo> newCardInfos
633 = update_cardinfo(m_gnupgHomePath, gpgAgent);
635 newCardInfos.resize(std::max(newCardInfos.size(), oldCardInfos.size()));
636 oldCardInfos.resize(std::max(newCardInfos.size(), oldCardInfos.size()));
638 KDAB_SYNCHRONIZED(m_mutex)
639 m_cardInfos = newCardInfos;
641 std::vector<CardInfo>::const_iterator
642 nit = newCardInfos.begin(), nend = newCardInfos.end(),
643 oit = oldCardInfos.begin(), oend = oldCardInfos.end();
645 unsigned int idx = 0;
646 bool anyLC = false;
647 bool anyNP = false;
648 bool anyError = false;
649 while (nit != nend && oit != oend) {
650 if (nit->status != oit->status) {
651 #ifdef DEBUG_SCREADER
652 qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: slot" << idx << ":" << prettyFlags[oit->status] << "->" << prettyFlags[nit->status];
653 #endif
654 Q_EMIT cardStatusChanged(idx, nit->status);
656 if (nit->status == ReaderStatus::CardCanLearnKeys) {
657 anyLC = true;
659 if (nit->status == ReaderStatus::CardHasNullPin) {
660 anyNP = true;
662 if (nit->status == ReaderStatus::CardError) {
663 anyError = true;
665 ++nit;
666 ++oit;
667 ++idx;
670 Q_EMIT anyCardHasNullPinChanged(anyNP);
671 Q_EMIT anyCardCanLearnKeysChanged(anyLC);
673 if (anyError) {
674 gpgAgent.reset();
677 } else {
679 (void)gpgagent_transact(gpgAgent, command.constData(), item.front().error);
681 KDAB_SYNCHRONIZED(m_mutex)
682 // splice 'item' into m_finishedTransactions:
683 m_finishedTransactions.splice(m_finishedTransactions.end(), item);
685 Q_EMIT oneTransactionFinished();
689 // update event counter in case anything above changed
690 // it:
691 if (gpgAgent) {
692 eventCounter = get_event_counter(gpgAgent);
693 } else {
694 eventCounter = -1;
696 #ifdef DEBUG_SCREADER
697 qCDebug(KLEOPATRA_LOG) << "eventCounter:" << eventCounter;
698 #endif
703 private:
704 mutable QMutex m_mutex;
705 QWaitCondition m_waitForTransactions;
706 const QString m_gnupgHomePath;
707 std::vector<QByteArray> readerStatusFileContents;
708 // protected by m_mutex:
709 std::vector<CardInfo> m_cardInfos;
710 std::list<Transaction> m_transactions, m_finishedTransactions;
715 class ReaderStatus::Private : ReaderStatusThread
717 friend class Kleo::SmartCard::ReaderStatus;
718 ReaderStatus *const q;
719 public:
720 explicit Private(ReaderStatus *qq)
721 : ReaderStatusThread(qq),
722 q(qq),
723 watcher()
725 KDAB_SET_OBJECT_NAME(watcher);
727 qRegisterMetaType<Status>("Kleo::SmartCard::ReaderStatus::Status");
729 watcher.whitelistFiles(QStringList(QStringLiteral("reader_*.status")));
730 watcher.addPath(Kleo::gnupgHomeDirectory());
731 watcher.setDelay(100);
733 connect(this, &::ReaderStatusThread::cardStatusChanged,
734 q, &ReaderStatus::cardStatusChanged);
735 connect(this, &::ReaderStatusThread::anyCardHasNullPinChanged,
736 q, &ReaderStatus::anyCardHasNullPinChanged);
737 connect(this, &::ReaderStatusThread::anyCardCanLearnKeysChanged,
738 q, &ReaderStatus::anyCardCanLearnKeysChanged);
740 connect(&watcher, &FileSystemWatcher::triggered, this, &::ReaderStatusThread::slotReaderStatusFileChanged);
743 ~Private()
745 stop();
746 if (!wait(100)) {
747 terminate();
748 wait();
752 private:
753 bool anyCardHasNullPinImpl() const
755 return kdtools::any(cardInfos(), boost::bind(&CardInfo::status, _1) == CardHasNullPin);
758 bool anyCardCanLearnKeysImpl() const
760 return kdtools::any(cardInfos(), boost::bind(&CardInfo::status, _1) == CardCanLearnKeys);
763 private:
764 FileSystemWatcher watcher;
767 ReaderStatus::ReaderStatus(QObject *parent)
768 : QObject(parent), d(new Private(this))
770 self = this;
773 ReaderStatus::~ReaderStatus()
775 self = 0;
778 // slot
779 void ReaderStatus::startMonitoring()
781 d->start();
784 // static
785 ReaderStatus *ReaderStatus::mutableInstance()
787 return self;
790 // static
791 const ReaderStatus *ReaderStatus::instance()
793 return self;
796 ReaderStatus::Status ReaderStatus::cardStatus(unsigned int slot) const
798 return d->cardStatus(slot);
801 bool ReaderStatus::anyCardHasNullPin() const
803 return d->anyCardHasNullPinImpl();
806 bool ReaderStatus::anyCardCanLearnKeys() const
808 return d->anyCardCanLearnKeysImpl();
811 std::vector<ReaderStatus::PinState> ReaderStatus::pinStates(unsigned int slot) const
813 const std::vector<CardInfo> ci = d->cardInfos();
814 if (slot < ci.size()) {
815 return ci[slot].pinStates;
816 } else {
817 return std::vector<PinState>();
821 void ReaderStatus::startSimpleTransaction(const QByteArray &command, QObject *receiver, const char *slot)
823 const Transaction t = { command, receiver, slot, Error() };
824 d->addTransaction(t);
827 void ReaderStatus::updateStatus()
829 d->ping();
832 #include "readerstatus.moc"