1/*
2 * qca_safetimer.cpp - Qt Cryptographic Architecture
3 * Copyright (C) 2014 Ivan Romanov <drizt@land.ru>
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
18 * 02110-1301 USA
19 *
20 */
21
22#include "qca_safetimer.h"
23#include <QElapsedTimer>
24#include <QTimerEvent>
25#include <qmath.h>
26
27// #define SAFETIMER_DEBUG
28
29#ifdef SAFETIMER_DEBUG
30#include <QDebug>
31#endif
32
33namespace QCA {
34
35class SafeTimer::Private : public QObject
36{
37 Q_OBJECT
38 friend class SafeTimer;
39
40public:
41 Private(QObject *parent = nullptr);
42
43 int timerId;
44 int fixerTimerId;
45 bool isSingleShot;
46 int interval;
47 bool isActive;
48 QElapsedTimer elapsedTimer;
49
50public Q_SLOTS:
51 void fixTimer();
52
53Q_SIGNALS:
54 void needFix();
55
56protected:
57 bool event(QEvent *event) override;
58 void timerEvent(QTimerEvent *event) override;
59};
60
61SafeTimer::Private::Private(QObject *parent)
62 : QObject(parent)
63 , timerId(0)
64 , fixerTimerId(0)
65 , isSingleShot(false)
66 , interval(0)
67 , isActive(false)
68 , elapsedTimer(QElapsedTimer())
69{
70 connect(sender: this, signal: &Private::needFix, context: this, slot: &Private::fixTimer, type: Qt::QueuedConnection);
71}
72
73void SafeTimer::Private::fixTimer()
74{
75 // Start special timer to align ressurected old timer
76 const int msec = qMax(a: 0, b: interval - static_cast<int>(elapsedTimer.elapsed()));
77
78 fixerTimerId = startTimer(interval: msec);
79#ifdef SAFETIMER_DEBUG
80 qDebug() << "START FIXTIMER: id =" << fixerTimerId << ", thread =" << thread() << ", interval =" << msec
81 << parent();
82#endif
83}
84
85bool SafeTimer::Private::event(QEvent *event)
86{
87 if (event->type() == QEvent::ThreadChange && fixerTimerId /* timer is actived */) {
88 // Timer dies when an object changes owner thread. This trick
89 // used to ressurect old timer in the new thread.
90 // Signal is emited in the old thread but will be gotten in the new one.
91#ifdef SAFETIMER_DEBUG
92 qDebug() << "STOP FIXTIMER ON CHANGE THREAD: id =" << fixerTimerId << ", thread =" << thread() << parent();
93#endif
94 killTimer(id: fixerTimerId);
95 fixerTimerId = 0;
96 emit needFix();
97 }
98
99 return QObject::event(event);
100}
101
102void SafeTimer::Private::timerEvent(QTimerEvent *event)
103{
104 if (event->timerId() == fixerTimerId) {
105#ifdef SAFETIMER_DEBUG
106 qDebug() << "STOP FIXTIMER ON TIMEOUT: id =" << fixerTimerId << ", thread =" << thread() << parent();
107#endif
108 killTimer(id: fixerTimerId);
109 fixerTimerId = 0;
110
111 SafeTimer *safeTimer = qobject_cast<SafeTimer *>(object: parent());
112 // Emulate timeout signal of not yet ressurected timer
113 emit safeTimer->timeout();
114 // Ressurect timer here if not a singleshot
115 if (!isSingleShot)
116 safeTimer->start();
117 else
118 isActive = false;
119 } else {
120#ifdef SAFETIMER_DEBUG
121 qDebug() << "BAD PRIVATE TIME EVENT: id =" << timerId << ", thread =" << thread() << this
122 << ", badId =" << event->timerId() << parent();
123#endif
124 }
125}
126
127SafeTimer::SafeTimer(QObject *parent)
128 : QObject()
129 , d(new Private())
130{
131 // It must be done here. Initialization list can't be used.
132 // Need to have proper class name. Look at TimerFixer::hook.
133 setParent(parent);
134 d->setParent(this);
135}
136
137SafeTimer::~SafeTimer()
138{
139}
140
141int SafeTimer::interval() const
142{
143 return d->interval;
144}
145
146bool SafeTimer::isActive() const
147{
148 return d->isActive;
149}
150
151bool SafeTimer::isSingleShot() const
152{
153 return d->isSingleShot;
154}
155
156void SafeTimer::setInterval(int msec)
157{
158 d->interval = msec;
159}
160
161void SafeTimer::setSingleShot(bool singleShot)
162{
163 d->isSingleShot = singleShot;
164}
165
166void SafeTimer::start(int msec)
167{
168 d->interval = msec;
169 start();
170}
171
172void SafeTimer::start()
173{
174 stop();
175
176 d->elapsedTimer.start();
177 d->timerId = QObject::startTimer(interval: d->interval);
178 d->isActive = d->timerId > 0;
179
180#ifdef SAFETIMER_DEBUG
181 qDebug() << "START TIMER: id =" << d->timerId << ", thread =" << thread() << ", interval =" << d->interval << this;
182#endif
183}
184
185void SafeTimer::stop()
186{
187 if (d->timerId) {
188 QObject::killTimer(id: d->timerId);
189#ifdef SAFETIMER_DEBUG
190 qDebug() << "STOP TIMER: id =" << d->timerId << ", thread =" << thread() << this;
191#endif
192 d->timerId = 0;
193 }
194
195 if (d->fixerTimerId) {
196#ifdef SAFETIMER_DEBUG
197 qDebug() << "STOP FIXER TIMER: id =" << d->fixerTimerId << ", thread =" << thread() << this;
198#endif
199 d->killTimer(id: d->fixerTimerId);
200 d->fixerTimerId = 0;
201 }
202 d->isActive = false;
203}
204
205bool SafeTimer::event(QEvent *event)
206{
207 if (event->type() == QEvent::ThreadChange && d->timerId /* timer is actived */) {
208 // Timer dies when an object changes owner thread. This trick
209 // used to ressurect old timer in the new thread.
210 // Signal is emited in the old thread but will be gotten in the new one.
211#ifdef SAFETIMER_DEBUG
212 qDebug() << "CHANGE THREAD: id =" << d->timerId << ", thread =" << thread() << this;
213#endif
214 killTimer(d->timerId);
215 d->timerId = 0;
216 emit d->needFix();
217 }
218
219 return QObject::event(event);
220}
221
222void SafeTimer::timerEvent(QTimerEvent *event)
223{
224 if (event->timerId() == d->timerId) {
225 if (d->isSingleShot)
226 stop();
227 emit timeout();
228 } else {
229#ifdef SAFETIMER_DEBUG
230 qDebug() << "BAD TIME EVENT: id =" << d->timerId << ", thread =" << thread() << this
231 << ", badId =" << event->timerId() << this;
232#endif
233 }
234}
235
236} // end namespace QCA
237
238#include "qca_safetimer.moc"
239

source code of qca/src/qca_safetimer.cpp