1 /*
2     threadedjobmixin.h
3 
4     This file is part of qgpgme, the Qt API binding for gpgme
5     Copyright (c) 2008 Klarälvdalens Datakonsult AB
6     Copyright (c) 2016 by Bundesamt für Sicherheit in der Informationstechnik
7     Software engineering by Intevation GmbH
8 
9     QGpgME 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 the
12     License, or (at your option) any later version.
13 
14     QGpgME is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17     General Public License for more details.
18 
19     You should have received a copy of the GNU General Public License
20     along with this program; if not, write to the Free Software
21     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 
23     In addition, as a special exception, the copyright holders give
24     permission to link the code of this program with any edition of
25     the Qt library by Trolltech AS, Norway (or with modified versions
26     of Qt that use the same license as Qt), and distribute linked
27     combinations including the two.  You must obey the GNU General
28     Public License in all respects for all of the code used other than
29     Qt.  If you modify this file, you may extend this exception to
30     your version of the file, but you are not obligated to do so.  If
31     you do not wish to do so, delete this exception statement from
32     your version.
33 */
34 
35 #ifndef __QGPGME_THREADEDJOBMIXING_H__
36 #define __QGPGME_THREADEDJOBMIXING_H__
37 
38 #include <QMutex>
39 #include <QMutexLocker>
40 #include <QThread>
41 #include <QString>
42 #include <QIODevice>
43 
44 #ifdef BUILDING_QGPGME
45 # include "context.h"
46 # include "interfaces/progressprovider.h"
47 #else
48 # include <gpgme++/context.h>
49 # include <gpgme++/interfaces/progressprovider.h>
50 #endif
51 
52 #include "job.h"
53 
54 #include <cassert>
55 #include <functional>
56 
57 namespace QGpgME
58 {
59 namespace _detail
60 {
61 
62 QString audit_log_as_html(GpgME::Context *ctx, GpgME::Error &err);
63 
64 class PatternConverter
65 {
66     const QList<QByteArray> m_list;
67     mutable const char **m_patterns;
68 public:
69     explicit PatternConverter(const QByteArray &ba);
70     explicit PatternConverter(const QString &s);
71     explicit PatternConverter(const QList<QByteArray> &lba);
72     explicit PatternConverter(const QStringList &sl);
73     ~PatternConverter();
74 
75     const char **patterns() const;
76 };
77 
78 class ToThreadMover
79 {
80     QObject *const m_object;
81     QThread *const m_thread;
82 public:
ToThreadMover(QObject * o,QThread * t)83     ToThreadMover(QObject *o, QThread *t) : m_object(o), m_thread(t) {}
ToThreadMover(QObject & o,QThread * t)84     ToThreadMover(QObject &o, QThread *t) : m_object(&o), m_thread(t) {}
ToThreadMover(const std::shared_ptr<QObject> & o,QThread * t)85     ToThreadMover(const std::shared_ptr<QObject> &o, QThread *t) : m_object(o.get()), m_thread(t) {}
~ToThreadMover()86     ~ToThreadMover()
87     {
88         if (m_object && m_thread) {
89             m_object->moveToThread(m_thread);
90         }
91     }
92 };
93 
94 template <typename T_result>
95 class Thread : public QThread
96 {
97 public:
QThread(parent)98     explicit Thread(QObject *parent = Q_NULLPTR) : QThread(parent) {}
99 
setFunction(const std::function<T_result ()> & function)100     void setFunction(const std::function<T_result()> &function)
101     {
102         const QMutexLocker locker(&m_mutex);
103         m_function = function;
104     }
105 
result()106     T_result result() const
107     {
108         const QMutexLocker locker(&m_mutex);
109         return m_result;
110     }
111 
112 private:
run()113     void run() Q_DECL_OVERRIDE {
114         const QMutexLocker locker(&m_mutex);
115         m_result = m_function();
116     }
117 private:
118     mutable QMutex m_mutex;
119     std::function<T_result()> m_function;
120     T_result m_result;
121 };
122 
123 template <typename T_base, typename T_result = std::tuple<GpgME::Error, QString, GpgME::Error> >
124 class ThreadedJobMixin : public T_base, public GpgME::ProgressProvider
125 {
126 public:
127     typedef ThreadedJobMixin<T_base, T_result> mixin_type;
128     typedef T_result result_type;
129 
130 protected:
131     static_assert(std::tuple_size<T_result>::value > 2,
132                   "Result tuple too small");
133     static_assert(std::is_same <
134                   typename std::tuple_element <
135                   std::tuple_size<T_result>::value - 2,
136                   T_result
137                   >::type,
138                   QString
139                   >::value,
140                   "Second to last result type not a QString");
141     static_assert(std::is_same <
142                   typename std::tuple_element <
143                   std::tuple_size<T_result>::value - 1,
144                   T_result
145                   >::type,
146                   GpgME::Error
147                   >::value,
148                   "Last result type not a GpgME::Error");
149 
ThreadedJobMixin(GpgME::Context * ctx)150     explicit ThreadedJobMixin(GpgME::Context *ctx)
151         : T_base(nullptr), m_ctx(ctx), m_thread(), m_auditLog(), m_auditLogError()
152     {
153     }
154 
lateInitialization()155     void lateInitialization()
156     {
157         assert(m_ctx);
158         QObject::connect(&m_thread, &QThread::finished, this,
159                          &mixin_type::slotFinished);
160         m_ctx->setProgressProvider(this);
161         QGpgME::g_context_map.insert(this, m_ctx.get());
162     }
163 
~ThreadedJobMixin()164     ~ThreadedJobMixin()
165     {
166         QGpgME::g_context_map.remove(this);
167     }
168 
169     template <typename T_binder>
run(const T_binder & func)170     void run(const T_binder &func)
171     {
172         m_thread.setFunction(std::bind(func, this->context()));
173         m_thread.start();
174     }
175     template <typename T_binder>
run(const T_binder & func,const std::shared_ptr<QIODevice> & io)176     void run(const T_binder &func, const std::shared_ptr<QIODevice> &io)
177     {
178         if (io) {
179             io->moveToThread(&m_thread);
180         }
181         // the arguments passed here to the functor are stored in a QThread, and are not
182         // necessarily destroyed (living outside the UI thread) at the time the result signal
183         // is emitted and the signal receiver wants to clean up IO devices.
184         // To avoid such races, we pass std::weak_ptr's to the functor.
185         m_thread.setFunction(std::bind(func, this->context(), this->thread(), std::weak_ptr<QIODevice>(io)));
186         m_thread.start();
187     }
188     template <typename T_binder>
run(const T_binder & func,const std::shared_ptr<QIODevice> & io1,const std::shared_ptr<QIODevice> & io2)189     void run(const T_binder &func, const std::shared_ptr<QIODevice> &io1, const std::shared_ptr<QIODevice> &io2)
190     {
191         if (io1) {
192             io1->moveToThread(&m_thread);
193         }
194         if (io2) {
195             io2->moveToThread(&m_thread);
196         }
197         // the arguments passed here to the functor are stored in a QThread, and are not
198         // necessarily destroyed (living outside the UI thread) at the time the result signal
199         // is emitted and the signal receiver wants to clean up IO devices.
200         // To avoid such races, we pass std::weak_ptr's to the functor.
201         m_thread.setFunction(std::bind(func, this->context(), this->thread(), std::weak_ptr<QIODevice>(io1), std::weak_ptr<QIODevice>(io2)));
202         m_thread.start();
203     }
context()204     GpgME::Context *context() const
205     {
206         return m_ctx.get();
207     }
208 
resultHook(const result_type &)209     virtual void resultHook(const result_type &) {}
210 
slotFinished()211     void slotFinished()
212     {
213         const T_result r = m_thread.result();
214         m_auditLog = std::get < std::tuple_size<T_result>::value - 2 > (r);
215         m_auditLogError = std::get < std::tuple_size<T_result>::value - 1 > (r);
216         resultHook(r);
217         Q_EMIT this->done();
218         doEmitResult(r);
219         this->deleteLater();
220     }
slotCancel()221     void slotCancel() Q_DECL_OVERRIDE {
222         if (m_ctx)
223         {
224             m_ctx->cancelPendingOperation();
225         }
226     }
auditLogAsHtml()227     QString auditLogAsHtml() const Q_DECL_OVERRIDE
228     {
229         return m_auditLog;
230     }
auditLogError()231     GpgME::Error auditLogError() const Q_DECL_OVERRIDE
232     {
233         return m_auditLogError;
234     }
showProgress(const char *,int,int current,int total)235     void showProgress(const char * /*what*/,
236                       int /*type*/, int current, int total) Q_DECL_OVERRIDE {
237         // will be called from the thread exec'ing the operation, so
238         // just bounce everything to the owning thread:
239         // ### hope this is thread-safe (meta obj is const, and
240         // ### portEvent is thread-safe, so should be ok)
241         QMetaObject::invokeMethod(this, "progress", Qt::QueuedConnection,
242         // TODO port
243         Q_ARG(QString, QString()),
244         Q_ARG(int, current),
245         Q_ARG(int, total));
246     }
247 private:
248     template <typename T1, typename T2>
doEmitResult(const std::tuple<T1,T2> & tuple)249     void doEmitResult(const std::tuple<T1, T2> &tuple)
250     {
251         Q_EMIT this->result(std::get<0>(tuple), std::get<1>(tuple));
252     }
253 
254     template <typename T1, typename T2, typename T3>
doEmitResult(const std::tuple<T1,T2,T3> & tuple)255     void doEmitResult(const std::tuple<T1, T2, T3> &tuple)
256     {
257         Q_EMIT this->result(std::get<0>(tuple), std::get<1>(tuple), std::get<2>(tuple));
258     }
259 
260     template <typename T1, typename T2, typename T3, typename T4>
doEmitResult(const std::tuple<T1,T2,T3,T4> & tuple)261     void doEmitResult(const std::tuple<T1, T2, T3, T4> &tuple)
262     {
263         Q_EMIT this->result(std::get<0>(tuple), std::get<1>(tuple), std::get<2>(tuple), std::get<3>(tuple));
264     }
265 
266     template <typename T1, typename T2, typename T3, typename T4, typename T5>
doEmitResult(const std::tuple<T1,T2,T3,T4,T5> & tuple)267     void doEmitResult(const std::tuple<T1, T2, T3, T4, T5> &tuple)
268     {
269         Q_EMIT this->result(std::get<0>(tuple), std::get<1>(tuple), std::get<2>(tuple), std::get<3>(tuple), std::get<4>(tuple));
270     }
271 
272 private:
273     std::shared_ptr<GpgME::Context> m_ctx;
274     Thread<T_result> m_thread;
275     QString m_auditLog;
276     GpgME::Error m_auditLogError;
277 };
278 
279 }
280 }
281 
282 #endif /* __QGPGME_THREADEDJOBMIXING_H__ */
283