source: SMSSender/common/domain/stask.cpp @ 315:e6c8bb244ecd

separation-frontend-backend
Last change on this file since 315:e6c8bb244ecd was 315:e6c8bb244ecd, checked in by Sämy Zehnder <saemy.zehnder@…>, 6 years ago
  • Separates the frontend from the backend code.
  • At this point the schoolnet gateway is converted only.
  • Removes bc & da prefixes from the filenames.
File size: 19.7 KB
Line 
1/*
2 smssender - A frontend for fast and easy SMS sending over different gateways.
3 Copyright (C) 2007-2012, gorrión. See http://smssender.gorrion.ch
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program 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
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include "stask.h"
20#include "stask_p.h"
21
22#include <QCoreApplication>
23#include <QDebug>
24#include <QMetaType>
25#include <QPointer>
26#include <QTimer>
27
28#include "utils/smacros.h"
29#include "utils/staskmanager.h"
30
31STask::STask(uint UID, const QString& nmespace)
32    : QObject(NULL) // parent gets assigned when the task is started
33    , dataMutex_(QMutex::Recursive)
34    , uid_(UID)
35    , nmespace_(nmespace)
36    , progress_(0)
37    , status_(STask::sWaiting)
38    , taskResult_(STask::rSuccess)
39    , specialResult_(0)
40    , parent_(NULL)
41    , waitForMutex_(QMutex::NonRecursive)
42    , started_(false)
43    , cancelled_(false)
44    , finished_(false)
45    , autoHaltOnCancelRequested_(false)
46    , exception_(NULL)
47{
48    qRegisterMetaType<STask::Property>("STask::Property");
49    qRegisterMetaType<STask::Result>("STask::Result");
50
51    connect(this, SIGNAL(_sigDoStart()), this, SLOT(onSigStart()));
52
53    setStatus(STask::sWaiting);
54}
55STask::~STask() {
56    QMutexLocker locker(&dataMutex_);
57}
58
59QString STask::taskIdent() {
60    QString nmesp = "";
61    if (!nmespace().isEmpty()) {
62        nmesp = nmespace() + ".";
63    }
64    return nmesp + QString::number(uid());
65}
66QDebug STask::sDebug() {
67    QString nmesp = "";
68    if (nmespace() != "") {
69        nmesp = nmespace() + ".";
70    }
71    return (qDebug() << QString("[Task %1]: ").arg(taskIdent()));
72}
73
74/**
75 * Enqueues the task.
76 */
77void STask::start() {
78    sDebug() << "We are enqueued now.";
79
80    STaskManager::instance()->addTaskToQueue(this);
81}
82/**
83 * Dequeues the task and cancels it if it is already running.
84 */
85void STask::cancel() {
86    QMutexLocker locker(&dataMutex());
87
88    STaskManager::instance()->removeTaskFromQueue(this);
89
90    if (isStarted()) {
91        cancelled_ = true;
92        setTaskResult(STask::rCancelled);
93        emit cancelled(this);
94    }
95}
96
97void STask::startSubTask(STask* subTask, bool waitFor) {
98    QMutexLocker locker(&dataMutex());
99
100    subTasks_.append(subTask);
101    connect(subTask, SIGNAL(finished(STask*)), this, SLOT(onSubTaskFinished(STask*)), Qt::DirectConnection);
102    connect(subTask, SIGNAL(exceptionOccured(STask*)), this, SLOT(onSubTaskExceptionOccured(STask*)), Qt::DirectConnection);
103
104    subTask->setParentTask(this);
105    subTask->start();
106
107    if (waitFor) {
108        locker.unlock();
109        this->waitFor(subTask);
110    }
111}
112void STask::onSubTaskFinished(STask* subTask) {
113    QMutexLocker locker(&dataMutex());
114
115    subTasks_.removeAll(subTask);
116}
117void STask::onSubTaskExceptionOccured(STask* subTask) {
118    QSharedPointer<EException> ex(inheritSubtaskException(subTask->exception()));
119    if (!ex.isNull()) {
120        setException(ex);
121    }
122}
123/**
124 * The given exception occured on a subtask of us. If the exception should
125 * be passed on and the parent task (we) should therefore be marked as erroneous
126 * as well, then return an instance of an exception which will be set on the parent.
127 * If the exception should be ignored, just return NULL.
128 *
129 * @param exception The exception which occured in the subtask
130 * @return An exception (create a new instance) or NULL
131 */
132EException* STask::inheritSubtaskException(const EException& exception) {
133    return new ESubtaskException(exception);
134}
135
136/**
137 * Wait until the given task is finished.
138 * If the given task is NULL or already finished we return immediately.
139 *
140 * @param task The task we want to wait for
141 */
142void STask::waitFor(STask* task) {
143    QList<STask*> tasks;
144    tasks.append(task);
145    waitFor(tasks);
146}
147void STask::waitFor(QList<STask*> others) {
148    QList<QPointer<STask> > othersPtr;
149    foreach (STask* task, others) {
150        othersPtr.append(task);
151    }
152    waitFor(othersPtr);
153}
154void STask::waitFor(QList<QPointer<STask> > others) {
155    QMutexLocker locker(&waitForMutex_);
156    foreach (STask *task, others) {
157        if (!task || waitingFor_.contains(task) || task->isFinished()) {
158            others.removeAll(task);
159        } else {
160            connect(task, SIGNAL(finished(STask*)), this, SLOT(waitForTaskFinished(STask*)), Qt::DirectConnection); // Use DirectConnection, since this thread will be locked by the wait condition
161        }
162    }
163
164    if (!others.isEmpty()) {
165        sDebug() << QString("Waiting for %1 tasks to finish.").arg(others.count());
166
167        waitingFor_.append(others);
168        waitForWaitCondition_.wait(&waitForMutex_);
169
170        sDebug() << "All tasks which we were waiting for have finished.";
171    }
172}
173void STask::waitForTaskFinished(STask* task) {
174    QMutexLocker locker(&waitForMutex_);
175
176    disconnect(task, SIGNAL(finished(STask*)), this, SLOT(waitForTaskFinished(STask*)));
177    sDebug() << QString("Task %1 finished.").arg(task->taskIdent());
178
179    waitingFor_.removeAll(task);
180
181    if (waitingFor_.isEmpty()) {
182        waitForWaitCondition_.wakeAll();
183    }
184}
185
186/**
187 * Initiates the kick-off of the task.
188 * This method is called by the _sigDoStart signal which should be emitted in the task manager.
189 */
190void STask::onSigStart() {
191    bool wasStarted = false;
192
193    QMutexLocker locker(&dataMutex());
194    if (isStarted()) {
195        wasStarted = true;
196    } else {
197        setStarted();
198    }
199    locker.unlock();
200
201    if (wasStarted) {
202        return;
203    }
204
205    setTaskResult(STask::rSuccess);
206
207    setStatus(STask::sRunning);
208    emit started(this);
209
210    doStart();
211}
212void STask::doStart() {
213    safeDoTheWork();
214    setFinished();
215}
216void STask::safeDoTheWork() {
217    sDebug() << "We are starting!";
218
219    autoHaltOnCancelRequested_ = true;
220    try {
221        doTheWork();
222    } catch (const EAbortException &e) {
223        // Just ignore it
224    } catch (const EException *e) {
225        setException(*e); // Do not create a cloned instance, since this instance is probably not owned by anyone
226        qWarning() << exception().chainedWhat();
227    } catch (const EException &e) {
228        setException(e);
229        qWarning() << exception().chainedWhat();
230   } catch (...) {
231        /* If an exception lands here or in any catch below you either did not throw an instance of
232         * EException or
233         *   o) you have forgotten to add the "-shared-libgcc" compiler flag
234         *   o) the exception (or one of its parents) has implementation data in its header file
235         *   o) you have forgotten to add CORE_SHARED_EXPORT in the class definition of the exception
236         * (These points hold only, if the implementation of the task lies in a shared library)
237         */
238        setException(EException(tr("Unknown exception occured during executing the task.")));
239        qWarning() << exception().chainedWhat();
240    }
241    autoHaltOnCancelRequested_ = false;
242
243    // Wait until all subtasks are finished
244    waitFor(subTasks_);
245
246    sDebug() << "We have done our job!";
247}
248
249void STask::haltIfCancelRequested() {
250    if (isCancelled()) {
251        EAbortException().raise();
252    }
253}
254
255uint STask::uid() const {
256    return uid_;
257}
258QString STask::nmespace() const {
259    return nmespace_;
260}
261
262QList<SUIdNamespaceIdent> STask::blockingTaskIdents() const {
263    QMutexLocker locker(&dataMutex());
264
265    return blockingTaskIdents_;
266}
267QList<QPointer<STask> > STask::blockingTaskInstances() const {
268    QMutexLocker locker(&dataMutex());
269
270    return blockingTaskInstances_;
271}
272
273QString STask::title() const {
274    QMutexLocker locker(&dataMutex());
275
276    return title_;
277}
278QString STask::details() const {
279    QMutexLocker locker(&dataMutex());
280
281    return details_;
282}
283uint STask::progress() const {
284    QMutexLocker locker(&dataMutex());
285
286    return progress_;
287}
288uint STask::status() const {
289    QMutexLocker locker(&dataMutex());
290
291    return status_;
292}
293QString STask::statusStr() const {
294    QMutexLocker locker(&dataMutex());
295
296    switch (status()) {
297        case STask::sWaiting:
298            return tr("Waiting for other task to finish");
299
300        case STask::sRunning:
301            return tr("Running");
302
303        case STask::sFinished:
304            return tr("Finished");
305
306        default:
307            return tr("Unknown");
308    }
309}
310STask::Result STask::taskResult() const {
311    QMutexLocker locker(&dataMutex());
312
313    return taskResult_;
314}
315uint STask::specialResult() const {
316    QMutexLocker locker(&dataMutex());
317
318    return specialResult_;
319}
320
321STask* STask::parent() const {
322    QMutexLocker locker(&dataMutex());
323
324    return parent_;
325}
326bool STask::hasParent() const {
327    return parent();
328}
329bool STask::isSubtaskOf(STask* parent) const {
330    QMutexLocker locker(&dataMutex());
331
332    STask* currentParent = this->parent();
333
334    while (currentParent) {
335        if (currentParent == parent)
336            return true;
337
338        currentParent = currentParent->parent();
339    }
340    return false;
341}
342bool STask::isStarted() const {
343    QMutexLocker locker(&dataMutex());
344
345    return started_;
346}
347bool STask::isRunning() const {
348    QMutexLocker locker(&dataMutex());
349
350    return isStarted() && !isFinished();
351}
352bool STask::isCancelled() const {
353    QMutexLocker locker(&dataMutex());
354
355    return cancelled_;
356}
357bool STask::isFinished() const {
358    QMutexLocker locker(&dataMutex());
359
360    return finished_;
361}
362
363/**
364 * Locks the given ressource application wide. If an other task wants to
365 * access the same ressource it has to wait until the lock gets released.
366 * Exceptions are subtasks of tasks holding the lock. To avoid deadlocks
367 * these tasks are allowed to access the lock.
368 */
369void STask::lockRessource(int ressourceId, QString nmespace, QMutex::RecursionMode mode) {
370    SRessourceMutexHolder::instance()->lockRessource(this, ressourceId, nmespace, mode);
371}
372void STask::unlockRessource(int ressourceId, QString nmespace) {
373    SRessourceMutexHolder::instance()->unlockRessource(this, ressourceId, nmespace);
374}
375
376
377void STask::addBlockingTask(uint uid, QString nmespace) {
378    QMutexLocker locker(&dataMutex());
379
380    SUIdNamespaceIdent taskId;
381    taskId.uid      = uid;
382    taskId.nmespace = nmespace;
383
384    addBlockingTask(taskId);
385}
386void STask::addBlockingTask(const SUIdNamespaceIdent& taskIdent) {
387    QMutexLocker locker(&dataMutex());
388
389    if (!blockingTaskIdents_.contains(taskIdent)) {
390        blockingTaskIdents_.append(taskIdent);
391    }
392}
393void STask::setBlockingTasks(const QList<SUIdNamespaceIdent>& tasks) {
394    QMutexLocker locker(&dataMutex());
395
396    blockingTaskIdents_ = tasks;
397}
398void STask::addBlockingTask(STask* task) {
399    QMutexLocker locker(&dataMutex());
400
401    if (!blockingTaskInstances_.contains(task)) {
402        blockingTaskInstances_.append(task);
403    }
404}
405void STask::setSelfBlocking(bool selfBlocking) {
406    QMutexLocker locker(&dataMutex());
407
408    if (selfBlocking) {
409        addBlockingTask(uid(), nmespace());
410    } else {
411        removeBlockingTask(uid(), nmespace());
412    }
413}
414void STask::removeBlockingTask(uint uid, QString nmespace) {
415    QMutexLocker locker(&dataMutex());
416
417    SUIdNamespaceIdent taskIdent;
418    taskIdent.uid      = uid;
419    taskIdent.nmespace = nmespace;
420
421    removeBlockingTask(taskIdent);
422}
423void STask::removeBlockingTask(const SUIdNamespaceIdent& taskIdent) {
424    QMutexLocker locker(&dataMutex());
425
426    blockingTaskIdents_.removeAll(taskIdent);
427}
428void STask::removeBlockingTask(STask* task) {
429    QMutexLocker locker(&dataMutex());
430
431    blockingTaskInstances_.removeAll(task);
432}
433
434/**
435 * Enqueues this task after the given one.
436 *
437 * @param other The task which shall be finished first
438 * @param onlyIfSuccessful Run the given task only if the given one was successful
439 */
440void STask::enqueueAfter(STask* other, bool onlyIfSuccessful) {
441    if (onlyIfSuccessful) {
442        connect(other, SIGNAL(finished(STask*)), this, SLOT(startIfSuccessful(STask*)), Qt::DirectConnection);
443    } else {
444        addBlockingTask(other);
445        start();
446    }
447}
448void  STask::startIfSuccessful(STask* task) {
449    if (task->taskResult() == STask::rSuccess) {
450        start();
451    } else {
452        cancel();
453    }
454}
455
456void STask::setTitle(const QString& title) {
457    QMutexLocker locker(&dataMutex());
458
459    SET_IF_DIFFERENT(title_, title);
460    emit changed(this, STask::pTitle);
461}
462/**
463 * Sets the details string. Please provide a string
464 * without trailing "...".
465 *
466 * @param details The details string
467 */
468void STask::setDetails(const QString& details) {
469    QMutexLocker locker(&dataMutex());
470
471    SET_IF_DIFFERENT(details_, details);
472    emit changed(this, STask::pDetails);
473}
474void STask::setProgress(uint progress) {
475    QMutexLocker locker(&dataMutex());
476
477    if (autoHaltOnCancelRequested_)  haltIfCancelRequested();
478
479    SET_IF_DIFFERENT(progress_, progress);
480    emit changed(this, STask::pProgress);
481}
482void STask::setStatus(uint status) {
483    QMutexLocker locker(&dataMutex());
484
485    if (autoHaltOnCancelRequested_)  haltIfCancelRequested();
486
487    SET_IF_DIFFERENT(status_, status);
488    emit changed(this, STask::pStatus);
489}
490void STask::setTaskResult(STask::Result taskResult) {
491    QMutexLocker locker(&dataMutex());
492
493    taskResult_ = taskResult;
494}
495void STask::setSpecialResult(uint result) {
496    QMutexLocker locker(&dataMutex());
497
498    specialResult_ = result;
499}
500
501/**
502 * Sets the parent task. This task will be cancelled if an error occurs at the parent.
503 *
504 * @param parent Can be null if no parent shall be set
505 */
506void STask::setParentTask(STask* parent) {
507    QMutexLocker locker(&dataMutex());
508
509    if (parent_) {
510        parent_->disconnect(this);
511    }
512
513    parent_ = parent;
514
515    if (parent_) {
516        // If an exception occurs on the parent -> do not continue with the work
517        connect(parent, SIGNAL(exceptionOccured(STask*)), this, SLOT(cancel()));
518    }
519}
520
521void STask::setStarted() {
522    QMutexLocker locker(&dataMutex());
523
524    started_ = true;
525}
526void STask::setFinished() {
527    QMutexLocker locker(&dataMutex());
528
529    finished_ = true;
530
531    setStatus(STask::sFinished);
532    setProgress(100);
533
534    locker.unlock();
535    emit finished(this);
536}
537
538void STask::setException(const EException& e) {
539    setException(QSharedPointer<EException>(e.createClonedInstance()));
540}
541void STask::setException(const QSharedPointer<EException>& e) {
542    QMutexLocker locker(&dataMutex());
543
544    exception_ = e;
545    setTaskResult(STask::rError);
546
547    locker.unlock();
548    emit exceptionOccured(this);
549}
550
551bool STask::hadException() const {
552    QMutexLocker locker(&dataMutex());
553
554    return !exception_.isNull();
555}
556const EException& STask::exception() const {
557    QMutexLocker locker(&dataMutex());
558
559    return *exception_;
560}
561
562QMutex& STask::dataMutex() const {
563    return dataMutex_;
564}
565
566/********************************************************/
567
568SThreadedTask::SThreadedTask(unsigned int UID, const QString& nmespace)
569    : STask(UID, nmespace)
570{
571    thread_ = new QThread(this);
572
573    connect(thread_, SIGNAL(started()), this, SLOT(threadStarted()));
574}
575SThreadedTask::~SThreadedTask() {
576    thread_->quit(); // Stop the eventqueue of the thread
577    thread_->wait();
578    thread_->deleteLater();
579}
580
581void SThreadedTask::doStart() {
582    moveToThread(thread_);
583    Q_ASSERT(thread() == thread_);
584
585    thread_->start();
586}
587
588void SThreadedTask::threadStarted() {
589    Q_ASSERT(QThread::currentThread() == thread_);
590
591    safeDoTheWork();
592
593    /* Move us back to the main thread to allow termination of the thread and safe deletion of
594       us in STaskManager::onTaskFinished() */
595    moveToThread(qApp->thread());
596    Q_ASSERT(qApp->thread() == thread());
597
598    setFinished();
599}
600
601/********************************************************/
602
603SAsynchroneousTask::SAsynchroneousTask(uint UID, const QString& nmespace)
604    : STask(UID, nmespace)
605{
606}
607
608
609/**
610 * Make sure that you call setFinished and setException respectively
611 * in your Task-Implementation.
612 */
613void SAsynchroneousTask::doStart() {
614    safeDoTheWork();
615}
616
617void SAsynchroneousTask::doTheWork() {
618    startTheWork();
619}
620
621
622/*********************************************************/
623/*********************************************************/
624
625
626SRessourceMutex::SRessourceMutex(QObject* parent)
627    : QObject(parent)
628    , dataMutex_(QMutex::NonRecursive)
629    , lockingMutex_(QMutex::Recursive)
630{
631}
632
633void SRessourceMutex::lock(STask* task, QMutex::RecursionMode mode) {
634    QMutexLocker locker(&dataMutex_);
635
636    if (!lockingMutex_.tryLock()) {
637        // The mutex is locked
638
639        bool isSubtaskOfALockHolder = false;
640        if (mode == QMutex::Recursive) {
641            foreach (STask* parent, lockHolders_) {
642                if (task->isSubtaskOf(parent)) {
643                    isSubtaskOfALockHolder = true;
644                    break;
645                }
646            }
647        }
648
649        if (!isSubtaskOfALockHolder) {
650            // We are not a subtask (or mode is not recursive) -> Wait for the mutex to become unlocked
651            lockingMutex_.lock();
652        }
653    }
654
655    // We are holding the lock now
656    lockHolders_.append(task);
657}
658void SRessourceMutex::unlock(STask* task) {
659    QMutexLocker locker(&dataMutex_);
660
661    if (lockHolders_.contains(task)) {
662        lockHolders_.removeOne(task);
663
664        if (lockHolders_.count() == 0) {
665            // The mutex is now unlocked by all recursions (subtasks) -> really unlock the mutex
666            lockingMutex_.unlock();
667        }
668    }
669}
670
671/*********************************************************/
672
673S_SINGLETON_IMPL(SRessourceMutexHolder)
674
675SRessourceMutexHolder::SRessourceMutexHolder()
676    : QObject(NULL) // singleton
677    , ressourceMutex_(QMutex::Recursive)
678{
679}
680
681void SRessourceMutexHolder::lockRessource(STask* task, int ressourceId, QString nmespace, QMutex::RecursionMode mode) {
682    QMutexLocker locker(&ressourceMutex_);
683
684    SUIdNamespaceIdent ressourceIdent;
685    ressourceIdent.uid      = ressourceId;
686    ressourceIdent.nmespace = nmespace;
687
688    if (!ressourceMutexs_.contains(ressourceIdent)) {
689        ressourceMutexs_.insert(ressourceIdent, new SRessourceMutex(this));
690    }
691
692    SRessourceMutex* mtx = ressourceMutexs_[ressourceIdent];
693    mtx->lock(task, mode);
694}
695
696void SRessourceMutexHolder::unlockRessource(STask* task, int ressourceId, QString nmespace) {
697    QMutexLocker locker(&ressourceMutex_);
698
699    SUIdNamespaceIdent ressourceIdent;
700    ressourceIdent.uid      = ressourceId;
701    ressourceIdent.nmespace = nmespace;
702
703    if (ressourceMutexs_.contains(ressourceIdent)) {
704        SRessourceMutex* mtx = ressourceMutexs_[ressourceIdent];
705        mtx->unlock(task);
706    }
707}
708
709/*********************************************************/
710
711ESubtaskException::ESubtaskException(const EException& exception)
712    : EException("")
713{
714    originalException_ = exception.createClonedInstance();
715
716    setWhat(originalException_->what(false));
717    if (originalException_->hasChainedException()) {
718        chain(originalException_->chainedException());
719    }
720}
721ESubtaskException::ESubtaskException(const ESubtaskException& other)
722    : EException(other)
723{
724    originalException_ = other.originalException_->createClonedInstance();
725}
726ESubtaskException::~ESubtaskException() throw() {
727    delete originalException_;
728}
729
730EException* ESubtaskException::originalException() const throw() {
731    return originalException_;
732}
733
734void ESubtaskException::raise() {
735    throw *this;
736}
737ESubtaskException* ESubtaskException::createClonedInstance() const throw() {
738    return new ESubtaskException(*this);
739}
740
741QString ESubtaskException::toString() const throw() {
742    return originalException()->toString();
743}
744
745/*********************************************************/
746
747EAbortException::EAbortException()
748    : EException("")
749{
750}
751
752void EAbortException::raise() {
753    throw *this;
754}
755
756EAbortException* EAbortException::createClonedInstance() const throw() {
757    return new EAbortException(*this);
758}
Note: See TracBrowser for help on using the repository browser.