-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathEmailerThread.cpp
More file actions
282 lines (237 loc) · 9.99 KB
/
EmailerThread.cpp
File metadata and controls
282 lines (237 loc) · 9.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
/*
* EmailerThread.cpp
*
* Created on: 10 March 2012
* Author: Max Foster
*/
using namespace std;
#include <QMutex>
#include <QTimer>
#include <QTcpSocket>
#include <QMessageBox>
#include "EmailerThread.h"
#include "EmailDetails.h"
#include "Emailer.h"
#include "Utils.h"
#include "dialogs/utils/ProcessDialog.h"
const int EmailerThread::mutexLockTimeout = 1000, EmailerThread::queueCheckTimePeriod = 5000;
EmailerThread *EmailerThread::emailerThread = NULL;
queue<EmailDetails> *EmailerThread::emailQueue = NULL;
QMutex *EmailerThread::emailQueueMutex = NULL, *EmailerThread::emailsInQueueMutex = NULL;
QMessageBox *EmailerThread::messageBox = NULL;
ProcessDialog *EmailerThread::emailProcessDialog = NULL;
bool EmailerThread::connectionAvailable_ = false;
void EmailerThread::init(QObject *parent)
{
if (emailerThread == NULL) emailerThread = new EmailerThread(parent);
if (emailQueue == NULL) emailQueue = new queue<EmailDetails>;
if (emailQueueMutex == NULL) emailQueueMutex = new QMutex;
if (emailsInQueueMutex == NULL) emailsInQueueMutex = new QMutex;
if (messageBox == NULL) messageBox = new QMessageBox;
emailerThread->start(LowestPriority);
}
static int emailCountBeforeClose = 0; // Used to calculate a percentage in EmailerThread::checkEmailQueuePercentDone()
bool EmailerThread::finalise()
{
if (emailerThread == NULL) return true; // Emailer thread already deleted, so finish
// Check if there are any emails in the queue by attempting to lock the emailsInQueueMutex. If it can be locked,
// unlock it immediately so there is no risk of mutex deadlocks
bool canLockEmailsInQueueMutex = false;
if (emailsInQueueMutex->tryLock())
{
emailsInQueueMutex->unlock();
canLockEmailsInQueueMutex = true;
}
if (!canLockEmailsInQueueMutex || !checkEmailQueueEmpty()) // If the queue isn't empty
{
// Ask the user if they want to cancel the emails (and double check)
bool cancelEmails = showYesNoDialog("There are still emails waiting to be sent.\n"
"Would you like to cancel the sending of these emails?");
if (cancelEmails)
{
cancelEmails = showYesNoDialog("Are you sure you want to cancel the sending of these emails?\n"
"They will not be able to be recovered after cancellation");
if (cancelEmails)
{ // The user is completely sure they want to cancel, so empty the queue
while (!emailQueueMutex->tryLock(mutexLockTimeout));
while (emailQueue->size() > 0) emailQueue->pop();
emailQueueMutex->unlock();
showInfoDialog("Emails canceled");
}
else return false; // The user doesn't want to cancel the emails, so end the function
}
else
{
// Ask the user if they want the program to close itself after all of the emails have been sent, and if
// they do, then show a pending dialog until the emails have been sent, then continue with the rest of the
// function as normal
bool closeAutomatically =
showYesNoDialog("Would you like the program to close after all the emails have been sent?");
if (closeAutomatically)
{
while (!emailQueueMutex->tryLock(mutexLockTimeout));
emailCountBeforeClose = emailQueue->size();
emailQueueMutex->unlock();
showPendingDialog("Waiting for emails to be sent", checkEmailQueuePercentDone);
}
else return false; // The user doesn't want the program to close, so end the function
}
}
// Get the emailerThread to stop and delete it, along with the queue, mutexes, etc.
emailsInQueueMutex->unlock();
emailerThread->exit();
while (emailerThread->isRunning()) emailerThread->wait();
delete emailerThread;
emailerThread = NULL;
if (emailProcessDialog != NULL) emailProcessDialog->accept();
if (emailQueue != NULL)
{
delete emailQueue;
emailQueue = NULL;
}
if (emailQueueMutex != NULL)
{
delete emailQueueMutex;
emailQueueMutex = NULL;
}
if (emailsInQueueMutex != NULL)
{
delete emailsInQueueMutex;
emailsInQueueMutex = NULL;
}
if (messageBox != NULL)
{
delete messageBox;
messageBox = NULL;
}
return true;
}
int EmailerThread::checkEmailQueuePercentDone()
{
// Lock the email queue mutex before calculating the percentage of completion
while (!emailQueueMutex->tryLock(mutexLockTimeout));
int percentage;
// There might be an email still being sent even though the queue is empty, so check by attempting to lock
// emailsInQueueMutex
if (emailCountBeforeClose == 0)
{
bool canLock = emailsInQueueMutex->tryLock();
percentage = canLock ? 100 : 0;
if (canLock) emailsInQueueMutex->unlock();
}
else percentage = ((emailCountBeforeClose - emailQueue->size()) * 100) / emailCountBeforeClose;
emailQueueMutex->unlock();
return percentage;
}
bool EmailerThread::checkEmailQueueEmpty()
{
// If emailsInQueueMutex can be locked, then the queue is empty
bool canLock = emailsInQueueMutex->tryLock();
if (canLock) emailsInQueueMutex->unlock();
return canLock;
}
void EmailerThread::enqueueEmail(const EmailDetails &email)
{
// Lock the emailQueueMutex before safely putting the email details onto the end of the queue
while (!emailQueueMutex->tryLock(mutexLockTimeout));
if (emailQueue != NULL) emailQueue->push(email);
emailQueueMutex->unlock();
}
bool EmailerThread::connectionAvailable()
{
return connectionAvailable_;
}
void EmailerThread::checkEmailQueue()
{
// Check for an internet connection by creating a socket and connecting to a reliable website (i.e. Google).
// If socket could not connect then end the function (no point sending emails if there is no internet connection)
QTcpSocket *connectionChecker = new QTcpSocket;
connectionChecker->connectToHost("www.google.co.uk", 80);
connectionAvailable_ = connectionChecker->waitForConnected(1000);
connectionChecker->disconnectFromHost();
delete connectionChecker;
if (!connectionAvailable_) return;
// If there is already an Emailer sending an email or the email queue is being used then end the function because
// the rest of the function uses the email queue as well as creating a new Emailer object
if (emailer != NULL) return;
if (!emailQueueMutex->tryLock()) return;
// The email queue is empty and there are no emails being sent, so unlock the emailsInQueue mutex if there were
// previously emails in the queue to indicate that email sending has finished
if ((emailQueue == NULL) || emailQueue->empty())
{
emailQueueMutex->unlock();
if (emailsInQueue)
{
emailsInQueueMutex->unlock();
emailsInQueue = false;
}
emit emailQueueEmpty();
return;
}
// There are emails in the queue, so if there were no emails in the queue before, lock the emailsInQueueMutex and
// create a dialog indicating to the user that there are emails being sent
if (!emailsInQueue)
{
while (!emailsInQueueMutex->tryLock(mutexLockTimeout));
emailsInQueue = true;
if (emailProcessDialog == NULL)
{
emailProcessDialog = new ProcessDialog("Sending emails", checkEmailQueueEmpty, &emailProcessDialog);
emailProcessDialog->show();
}
}
emit emailQueueNotEmpty();
// Create a new Emailer to send the email at the front of the queue, and tell it to send the email
emailer = new Emailer(emailQueue->front(), this);
emailQueue->pop();
emailQueueMutex->unlock(); // We're done with the email queue for now, so unlock it
connect(this, SIGNAL(sendEmail()), emailer, SLOT(send()), Qt::QueuedConnection);
connect(emailer, SIGNAL(mailSent()), this, SLOT(mailSent()), Qt::QueuedConnection);
connect(emailer, SIGNAL(mailFailed()), this, SLOT(mailFailed()), Qt::QueuedConnection);
emit sendEmail();
}
void EmailerThread::mailSent()
{
if (emailer == NULL) return; // The emailer has already been destroyed, that's fine, end
// Tell the emailer that it can delete itself when it has done whatever cleanup it needs to do. We can't just
// delete it straight away here because the Emailer's socket may still be connected and if we delete it, it may
// cause a crash!
emailer->kill();
emailer = NULL;
}
void EmailerThread::mailFailed()
{
if (emailer == NULL) return;
while (!emailQueueMutex->tryLock(mutexLockTimeout));
// If the email has not exceeded the maximum number of tries, then put it back onto the queue, else display an error
if (emailer->emailDetails().tries < EmailDetails::maxEmailTries) emailQueue->push(emailer->emailDetails());
else
{
QString message
= QString("Email to ") + emailer->emailDetails().recipient.c_str() + " could not be sent\n"
+ "(attempted to send " + toString(emailer->emailDetails().tries).c_str() + " times)";
messageBox->setIcon(QMessageBox::Warning);
messageBox->setWindowTitle("Error");
messageBox->setText(message);
messageBox->show();
cout << message.toStdString() << endl; // Also put the message into the logfile
}
emailer->kill();
emailer = NULL;
emailQueueMutex->unlock();
}
EmailerThread::EmailerThread(QObject *parent) : QThread(parent), emailer(NULL), timer(NULL), emailsInQueue(false) {}
EmailerThread::~EmailerThread()
{
if (emailer != NULL) delete emailer;
if (timer != NULL) delete timer;
}
void EmailerThread::run()
{
// Start the timer to make the EmailerThread check the email queue every so often
timer = new QTimer;
connect(timer, SIGNAL(timeout()), this, SLOT(checkEmailQueue()), Qt::QueuedConnection);
timer->start(queueCheckTimePeriod);
exec();
timer->stop();
}