File indexing completed on 2024-12-01 10:31:52

0001 /*
0002  * qca_safetimer.cpp - Qt Cryptographic Architecture
0003  * Copyright (C) 2014  Ivan Romanov <drizt@land.ru>
0004  *
0005  * This library is free software; you can redistribute it and/or
0006  * modify it under the terms of the GNU Lesser General Public
0007  * License as published by the Free Software Foundation; either
0008  * version 2.1 of the License, or (at your option) any later version.
0009  *
0010  * This library is distributed in the hope that it will be useful,
0011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013  * Lesser General Public License for more details.
0014  *
0015  * You should have received a copy of the GNU Lesser General Public
0016  * License along with this library; if not, write to the Free Software
0017  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
0018  * 02110-1301  USA
0019  *
0020  */
0021 
0022 #include "qca_safetimer.h"
0023 #include <QElapsedTimer>
0024 #include <QTimerEvent>
0025 #include <qmath.h>
0026 
0027 // #define SAFETIMER_DEBUG
0028 
0029 #ifdef SAFETIMER_DEBUG
0030 #include <QDebug>
0031 #endif
0032 
0033 namespace QCA {
0034 
0035 class SafeTimer::Private : public QObject
0036 {
0037     Q_OBJECT
0038     friend class SafeTimer;
0039 
0040 public:
0041     Private(QObject *parent = nullptr);
0042 
0043     int           timerId;
0044     int           fixerTimerId;
0045     bool          isSingleShot;
0046     int           interval;
0047     bool          isActive;
0048     QElapsedTimer elapsedTimer;
0049 
0050 public Q_SLOTS:
0051     void fixTimer();
0052 
0053 Q_SIGNALS:
0054     void needFix();
0055 
0056 protected:
0057     bool event(QEvent *event) override;
0058     void timerEvent(QTimerEvent *event) override;
0059 };
0060 
0061 SafeTimer::Private::Private(QObject *parent)
0062     : QObject(parent)
0063     , timerId(0)
0064     , fixerTimerId(0)
0065     , isSingleShot(false)
0066     , interval(0)
0067     , isActive(false)
0068     , elapsedTimer(QElapsedTimer())
0069 {
0070     connect(this, &Private::needFix, this, &Private::fixTimer, Qt::QueuedConnection);
0071 }
0072 
0073 void SafeTimer::Private::fixTimer()
0074 {
0075     // Start special timer to align ressurected old timer
0076     const int msec = qMax(0, interval - static_cast<int>(elapsedTimer.elapsed()));
0077 
0078     fixerTimerId = startTimer(msec);
0079 #ifdef SAFETIMER_DEBUG
0080     qDebug() << "START FIXTIMER: id =" << fixerTimerId << ", thread =" << thread() << ", interval =" << msec
0081              << parent();
0082 #endif
0083 }
0084 
0085 bool SafeTimer::Private::event(QEvent *event)
0086 {
0087     if (event->type() == QEvent::ThreadChange && fixerTimerId /* timer is actived */) {
0088         // Timer dies when an object changes owner thread. This trick
0089         // used to ressurect old timer in the new thread.
0090         // Signal is emited in the old thread but will be gotten in the new one.
0091 #ifdef SAFETIMER_DEBUG
0092         qDebug() << "STOP FIXTIMER ON CHANGE THREAD: id =" << fixerTimerId << ", thread =" << thread() << parent();
0093 #endif
0094         killTimer(fixerTimerId);
0095         fixerTimerId = 0;
0096         emit needFix();
0097     }
0098 
0099     return QObject::event(event);
0100 }
0101 
0102 void SafeTimer::Private::timerEvent(QTimerEvent *event)
0103 {
0104     if (event->timerId() == fixerTimerId) {
0105 #ifdef SAFETIMER_DEBUG
0106         qDebug() << "STOP FIXTIMER ON TIMEOUT: id =" << fixerTimerId << ", thread =" << thread() << parent();
0107 #endif
0108         killTimer(fixerTimerId);
0109         fixerTimerId = 0;
0110 
0111         SafeTimer *safeTimer = qobject_cast<SafeTimer *>(parent());
0112         // Emulate timeout signal of not yet ressurected timer
0113         emit safeTimer->timeout();
0114         // Ressurect timer here if not a singleshot
0115         if (!isSingleShot)
0116             safeTimer->start();
0117         else
0118             isActive = false;
0119     } else {
0120 #ifdef SAFETIMER_DEBUG
0121         qDebug() << "BAD PRIVATE TIME EVENT: id =" << timerId << ", thread =" << thread() << this
0122                  << ", badId =" << event->timerId() << parent();
0123 #endif
0124     }
0125 }
0126 
0127 SafeTimer::SafeTimer(QObject *parent)
0128     : QObject()
0129     , d(new Private())
0130 {
0131     // It must be done here. Initialization list can't be used.
0132     // Need to have proper class name. Look at TimerFixer::hook.
0133     setParent(parent);
0134     d->setParent(this);
0135 }
0136 
0137 SafeTimer::~SafeTimer()
0138 {
0139 }
0140 
0141 int SafeTimer::interval() const
0142 {
0143     return d->interval;
0144 }
0145 
0146 bool SafeTimer::isActive() const
0147 {
0148     return d->isActive;
0149 }
0150 
0151 bool SafeTimer::isSingleShot() const
0152 {
0153     return d->isSingleShot;
0154 }
0155 
0156 void SafeTimer::setInterval(int msec)
0157 {
0158     d->interval = msec;
0159 }
0160 
0161 void SafeTimer::setSingleShot(bool singleShot)
0162 {
0163     d->isSingleShot = singleShot;
0164 }
0165 
0166 void SafeTimer::start(int msec)
0167 {
0168     d->interval = msec;
0169     start();
0170 }
0171 
0172 void SafeTimer::start()
0173 {
0174     stop();
0175 
0176     d->elapsedTimer.start();
0177     d->timerId  = QObject::startTimer(d->interval);
0178     d->isActive = d->timerId > 0;
0179 
0180 #ifdef SAFETIMER_DEBUG
0181     qDebug() << "START TIMER: id =" << d->timerId << ", thread =" << thread() << ", interval =" << d->interval << this;
0182 #endif
0183 }
0184 
0185 void SafeTimer::stop()
0186 {
0187     if (d->timerId) {
0188         QObject::killTimer(d->timerId);
0189 #ifdef SAFETIMER_DEBUG
0190         qDebug() << "STOP TIMER: id =" << d->timerId << ", thread =" << thread() << this;
0191 #endif
0192         d->timerId = 0;
0193     }
0194 
0195     if (d->fixerTimerId) {
0196 #ifdef SAFETIMER_DEBUG
0197         qDebug() << "STOP FIXER TIMER: id =" << d->fixerTimerId << ", thread =" << thread() << this;
0198 #endif
0199         d->killTimer(d->fixerTimerId);
0200         d->fixerTimerId = 0;
0201     }
0202     d->isActive = false;
0203 }
0204 
0205 bool SafeTimer::event(QEvent *event)
0206 {
0207     if (event->type() == QEvent::ThreadChange && d->timerId /* timer is actived */) {
0208         // Timer dies when an object changes owner thread. This trick
0209         // used to ressurect old timer in the new thread.
0210         // Signal is emited in the old thread but will be gotten in the new one.
0211 #ifdef SAFETIMER_DEBUG
0212         qDebug() << "CHANGE THREAD: id =" << d->timerId << ", thread =" << thread() << this;
0213 #endif
0214         killTimer(d->timerId);
0215         d->timerId = 0;
0216         emit d->needFix();
0217     }
0218 
0219     return QObject::event(event);
0220 }
0221 
0222 void SafeTimer::timerEvent(QTimerEvent *event)
0223 {
0224     if (event->timerId() == d->timerId) {
0225         if (d->isSingleShot)
0226             stop();
0227         emit timeout();
0228     } else {
0229 #ifdef SAFETIMER_DEBUG
0230         qDebug() << "BAD TIME EVENT: id =" << d->timerId << ", thread =" << thread() << this
0231                  << ", badId =" << event->timerId() << this;
0232 #endif
0233     }
0234 }
0235 
0236 } // end namespace QCA
0237 
0238 #include "qca_safetimer.moc"