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 | |
33 | namespace QCA { |
34 | |
35 | class SafeTimer::Private : public QObject |
36 | { |
37 | Q_OBJECT |
38 | friend class SafeTimer; |
39 | |
40 | public: |
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 | |
50 | public Q_SLOTS: |
51 | void fixTimer(); |
52 | |
53 | Q_SIGNALS: |
54 | void needFix(); |
55 | |
56 | protected: |
57 | bool event(QEvent *event) override; |
58 | void timerEvent(QTimerEvent *event) override; |
59 | }; |
60 | |
61 | SafeTimer::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 | |
73 | void 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 | |
85 | bool 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 | |
102 | void 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 | |
127 | SafeTimer::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 | |
137 | SafeTimer::~SafeTimer() |
138 | { |
139 | } |
140 | |
141 | int SafeTimer::interval() const |
142 | { |
143 | return d->interval; |
144 | } |
145 | |
146 | bool SafeTimer::isActive() const |
147 | { |
148 | return d->isActive; |
149 | } |
150 | |
151 | bool SafeTimer::isSingleShot() const |
152 | { |
153 | return d->isSingleShot; |
154 | } |
155 | |
156 | void SafeTimer::setInterval(int msec) |
157 | { |
158 | d->interval = msec; |
159 | } |
160 | |
161 | void SafeTimer::setSingleShot(bool singleShot) |
162 | { |
163 | d->isSingleShot = singleShot; |
164 | } |
165 | |
166 | void SafeTimer::start(int msec) |
167 | { |
168 | d->interval = msec; |
169 | start(); |
170 | } |
171 | |
172 | void 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 | |
185 | void 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 | |
205 | bool 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 | |
222 | void 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 | |