messages.cpp 15.6 KB
Newer Older
1
/*
2
 * Copyright (C) 2014-2017 CZ.NIC
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations including
 * the two.
 */

#include <QDebug>
25 26
#include <QFile>
#include <QPair>
27
#include <QPushButton>
28
#include <QQmlEngine> /* qmlRegisterType */
29
#include <QStorageInfo>
30

31
#include "src/common.h"
32
#include "src/dialogues/dialogues.h"
33
#include "src/messages.h"
34
#include "src/settings.h"
35
#include "src/models/accountmodel.h"
36
#include "src/models/messagemodel.h"
37
#include "src/sqlite/message_db_container.h"
38 39
#include "src/sqlite/file_db_container.h"

40 41 42
void Messages::declareQML(void)
{
	qmlRegisterType<Messages>("cz.nic.mobileDatovka.messages", 1, 0, "MessageType");
43
	qRegisterMetaType<Messages::MessageType>();
44
}
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59

Messages::Messages(QObject *parent) : QObject(parent)
{
}

/* ========================================================================= */
/*
 * Slot: Clear message model in QML.
 */
void Messages::clearMessagesModel(void)
/* ========================================================================= */
{
	globMessagesModel.clearAll();
}

60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
/*!
 * @brief Translate message type enumeration type into database representation.
 */
static
enum MessageDb::MessageType enumToDbRepr(enum Messages::MessageType msgType)
{
	switch (msgType) {
	case Messages::TYPE_RECEIVED:
		return MessageDb::TYPE_RECEIVED;
		break;
	case Messages::TYPE_SENT:
		return MessageDb::TYPE_SENT;
		break;
	default:
		/*
		 * Any other translates to sent, but forces the application to
		 * crash in debugging mode.
		 */
		Q_ASSERT(0);
Martin Straka's avatar
Martin Straka committed
79
		return MessageDb::TYPE_SENT;
80 81 82
		break;
	}
}
83 84 85 86 87

/* ========================================================================= */
/*
 * Slot: Fill message list in QML.
 */
88 89
void Messages::fillMessageList(const QString &userName,
    enum MessageType msgType)
90 91 92 93 94
/* ========================================================================= */
{
	qDebug("%s()", __func__);

	MessageDb *msgDb = NULL;
95 96
	msgDb = globMessageDbsPtr->accessMessageDb(globSet.dbsLocation, userName,
	    AccountListModel::globAccounts[userName].storeToDisk());
97 98 99 100 101 102
	if (msgDb == NULL) {
		qDebug() << "ERROR: Message database cannot open!";
		return;
	}

	globMessagesModel.clearAll();
103
	/* Translate into database enum type. */
Martin Straka's avatar
Martin Straka committed
104
	msgDb->getMessageListDataFromDb(userName, enumToDbRepr(msgType));
105 106 107
}


108 109
QString Messages::getMessageDetail(const QString &userName,
    const QString &msgIdStr)
110 111 112
{
	qDebug("%s()", __func__);

113 114 115 116 117 118
	bool ok = false;
	qint64 msgId = msgIdStr.toLongLong(&ok);
	if (!ok || (msgId < 0)) {
		return QString();
	}

119
	MessageDb *msgDb = NULL;
120 121
	msgDb = globMessageDbsPtr->accessMessageDb(globSet.dbsLocation, userName,
	    AccountListModel::globAccounts[userName].storeToDisk());
122 123
	if (msgDb == NULL) {
		qDebug() << "ERROR: Message database cannot open!";
124
		return QString();
125
	}
126
	return msgDb->getMessageDetailDataFromDb(msgId);
127 128 129 130 131 132 133
}


/* ========================================================================= */
/*
 * Slot: Set message in database as locally read.
*/
134 135
void Messages::markMessageAsLocallyRead(const QString &userName, qint64 msgId,
    bool isRead)
136 137 138 139 140
/* ========================================================================= */
{
	qDebug("%s()", __func__);

	MessageDb *msgDb = NULL;
141 142
	msgDb = globMessageDbsPtr->accessMessageDb(globSet.dbsLocation, userName,
	    AccountListModel::globAccounts[userName].storeToDisk());
143 144 145 146
	if (msgDb == NULL) {
		qDebug() << "ERROR: Message database cannot open!";
		return;
	}
147 148
	msgDb->markMessageLocallyRead(msgId, isRead);
	globMessagesModel.overrideRead(msgId, isRead);
149 150 151 152 153 154 155
}


/* ========================================================================= */
/*
 * Slot: Set all messages in database as locally read/unread.
*/
156 157
void Messages::markMessagesAsLocallyRead(const QString &userName,
    enum MessageType msgType, bool isRead)
158 159 160 161 162
/* ========================================================================= */
{
	qDebug("%s()", __func__);

	MessageDb *msgDb = NULL;
163 164
	msgDb = globMessageDbsPtr->accessMessageDb(globSet.dbsLocation, userName,
	    AccountListModel::globAccounts[userName].storeToDisk());
165 166 167 168
	if (msgDb == NULL) {
		qDebug() << "ERROR: Message database cannot open!";
		return;
	}
169 170
	msgDb->markMessagesLocallyRead(enumToDbRepr(msgType), isRead);
	/* The current model should correspond with supplied type. */
171 172
	globMessagesModel.overrideReadAll(isRead);
}
173 174


175 176 177 178
/* ========================================================================= */
/*
 * Slot: Delete selected message from databases.
 */
Karel Slaný's avatar
Karel Slaný committed
179
void Messages::deleteMessageFromDbs(const QString &userName, qint64 msgId)
180 181 182 183 184 185 186 187
/* ========================================================================= */
{
	qDebug("%s()", __func__);

	if (userName.isEmpty()) {
		return;
	}

188
	int msgResponse = Dialogues::message(Dialogues::QUESTION,
189 190 191
	    tr("Delete message: %1").arg(msgId),
	    tr("Do you want to delete the message '%1'?").arg(msgId),
	    tr("Note: It will delete all attachments and message information from the local database."),
192 193
	    Dialogues::NO | Dialogues::YES, Dialogues::NO);
	if (msgResponse == Dialogues::NO) {
194 195 196 197 198 199 200 201
		return;
	}

	if (!AccountListModel::globAccounts.contains(userName)) {
		return;
	}

	MessageDb *msgDb = NULL;
202 203
	msgDb = globMessageDbsPtr->accessMessageDb(globSet.dbsLocation, userName,
	    AccountListModel::globAccounts[userName].storeToDisk());
204 205 206 207 208 209
	if (msgDb == NULL) {
		qDebug() << "ERROR: Message database cannot open!" << userName;
		return;
	}

	FileDb *fDb = NULL;
210 211
	fDb = globFileDbsPtr->accessFileDb(globSet.dbsLocation, userName,
	    AccountListModel::globAccounts[userName].storeToDisk());
212 213 214 215 216 217 218
	if (fDb == NULL) {
		qDebug() << "ERROR: File database cannot open!" << userName;
		return;
	}

	if (fDb->deleteFilesFromDb(msgId)) {
		if (msgDb->deleteMsgFromDb(msgId)) {
219 220
			/* Remove row from model, don't regenerate data. */
			globMessagesModel.removeMessage(msgId);
221
			globAccountsModelPtr->updateCounters(userName,
222 223 224 225
			    msgDb->getCntOfNewMsgs(MessageDb::TYPE_RECEIVED),
			    msgDb->getMessageCount(MessageDb::TYPE_RECEIVED),
			    msgDb->getCntOfNewMsgs(MessageDb::TYPE_SENT),
			    msgDb->getMessageCount(MessageDb::TYPE_SENT));
226 227 228 229
		}
	}
}

230
bool Messages::moveOrCreateNewDbsToNewLocation(const QString &newLocation)
231 232 233
{
	qDebug("%s()", __func__);

234
	int allDbSize = 0;
235

236 237 238 239 240
	/* Select action what to do with the currently used databases. */
	enum ReloactionAction action = askAction();
	switch (action) {
	case RA_NONE:
		/* Do nothing and exit. */
241
		return false;
242 243 244 245 246 247 248 249 250 251 252 253
		break;
	case RA_RELOCATE:
		allDbSize = getAllDbSize();
		break;
	case RA_CREATE_NEW:
		allDbSize = estimateAllDbSize();
		break;
	default:
		/* Unsupported action. */
		Q_ASSERT(0);
		return false;
		break;
254 255
	}

256 257
	if (allDbSize <= 0) {
		/* Expecting a positive value. */
258 259 260
		return false;
	}

261 262 263 264
	/* Check whether new location has required size for database storing. */
	{
		QStorageInfo si(newLocation);
		if (si.bytesAvailable() < allDbSize) {
265
			Dialogues::errorMessage(Dialogues::CRITICAL,
266 267
			    tr("New location error"),
			    tr("It is not possible to store databases in the new location because there is not enough free space left."),
268
			    tr("Databases size is %1 MB.").arg(allDbSize / (1024 * 1024)));
269
			return false;
270 271 272
		}
	}

273
	return relocateDatabases(newLocation, action);
274 275 276
}


277 278 279 280 281 282 283 284 285 286
/* ========================================================================= */
/*
 * Func: Delete messages from all message databases together with files
 *       where lifetime expired.
 */
void Messages::deleteExpiredMessagesFromDbs(int days)
/* ========================================================================= */
{
	qDebug("%s()", __func__);

287
	QList<qint64> msgIDList;
288 289 290 291
	QStringList userNameList = AccountListModel::globAccounts.keys();
	foreach (const QString &userName, userNameList) {

		MessageDb *msgDb = NULL;
292 293
		msgDb = globMessageDbsPtr->accessMessageDb(globSet.dbsLocation, userName,
		    AccountListModel::globAccounts[userName].storeToDisk());
294 295 296 297 298 299 300 301 302 303 304
		if (msgDb == NULL) {
			qDebug() << "ERROR: Message database cannot open!" << userName;
			return;
		}
		msgIDList = msgDb->getExpireMsgListFromDb(days);

		if (msgIDList.isEmpty()) {
			continue;
		}

		FileDb *fDb = NULL;
305 306
		fDb = globFileDbsPtr->accessFileDb(globSet.dbsLocation, userName,
		    AccountListModel::globAccounts[userName].storeToDisk());
307 308 309 310 311 312 313
		if (fDb == NULL) {
			qDebug() << "ERROR: File database cannot open!" << userName;
			return;
		}

		fDb->beginTransaction();
		msgDb->beginTransaction();
314 315 316
		foreach (qint64 msgId, msgIDList) {
			if (fDb->deleteFilesFromDb(msgId)) {
				msgDb->deleteMsgFromDb(msgId);
317 318 319 320 321 322
			}
		}
		fDb->commitTransaction();
		msgDb->commitTransaction();
	}
}
323 324


325 326
enum Messages::ReloactionAction Messages::askAction(void)
{
327 328 329 330 331
	QList< QPair<QString, int> > customButtons;
	customButtons.append(QPair<QString, int>(tr("Move"), RA_RELOCATE));
	customButtons.append(QPair<QString, int>(tr("Create new"), RA_CREATE_NEW));
	int customVal = -1;

332
	Dialogues::message(Dialogues::QUESTION,
333 334 335
	    tr("Change database location"),
	    tr("What do you want to do with the currently used database files?"),
	    tr("You have the option to move the current database files to a new location or you can delete them and create new empty databases."),
336
	    Dialogues::CANCEL, Dialogues::CANCEL,
337 338 339
	    customButtons, &customVal);

	if (customVal == RA_RELOCATE) {
340
		return RA_RELOCATE;
341
	} else if (customVal == RA_CREATE_NEW) {
342 343 344 345 346 347 348
		return RA_CREATE_NEW;
	} else {
		return RA_NONE;
	}
}

int Messages::getAllDbSize(void)
349 350 351
{
	int size = 0;

352
	QStringList userNameList(AccountListModel::globAccounts.keys());
353
	foreach (const QString &userName, userNameList) {
354
		MessageDb *msgDb = globMessageDbsPtr->accessMessageDb(
355 356
		    globSet.dbsLocation, userName,
		    AccountListModel::globAccounts[userName].storeToDisk());
357
		if (msgDb == NULL) {
358 359
			qDebug("ERROR: Cannot open message database for user name '%s'.", userName.toUtf8().constData());
			return -1;
360 361 362
		}
		size += msgDb->getDbSizeInBytes();

363
		FileDb *fDb = globFileDbsPtr->accessFileDb(globSet.dbsLocation,
364 365
		    userName,
		    AccountListModel::globAccounts[userName].storeToDisk());
366
		if (fDb == NULL) {
367 368
			qDebug("ERROR: Cannot open file database for user name '%s'.", userName.toUtf8().constData());
			return -1;
369 370 371 372 373 374
		}
		size += fDb->getDbSizeInBytes();
	}

	return size;
}
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404

int Messages::estimateAllDbSize(void)
{
	return
	    2 * AccountListModel::globAccounts.size() * INITIAL_DB_SIZE_BYTES;
}

bool Messages::relocateDatabases(const QString &newLocation,
    enum ReloactionAction action)
{
	if ((action != RA_RELOCATE) && (action != RA_CREATE_NEW)) {
		return false;
	}

	/* List of message databases and old locations. */
	typedef QPair<MessageDb *, QString> MsgDbListEntry;
	QList< MsgDbListEntry > relocatedMsgDbs;
	typedef QPair<FileDb *, QString> FileDbListEntry;
	QList< FileDbListEntry > relocatedFileDbs;

	bool relocationSucceeded = true;

	QStringList userNameList(AccountListModel::globAccounts.keys());
	foreach (const QString &userName, userNameList) {
		/* Ignore accounts with data stored in memory. */
		if (!AccountListModel::globAccounts[userName].storeToDisk()) {
			continue;
		}

		MessageDb *mDb = globMessageDbsPtr->accessMessageDb(
405 406
		    globSet.dbsLocation, userName,
		    AccountListModel::globAccounts[userName].storeToDisk());
407 408 409 410 411 412 413
		relocationSucceeded = (mDb != NULL);
		if (!relocationSucceeded) {
			qWarning("Cannot access message database for user name '%s'.",
			    userName.toUtf8().constData());
			break; /* Break the for cycle. */
		}
		FileDb *fDb = globFileDbsPtr->accessFileDb(
414 415
		    globSet.dbsLocation, userName,
		    AccountListModel::globAccounts[userName].storeToDisk());
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
		relocationSucceeded = (fDb != NULL);
		if (!relocationSucceeded) {
			qWarning("Cannot access file database for user name '%s'.",
			    userName.toUtf8().constData());
			break; /* Break the for cycle. */
		}

		QString oldLocation;

		/* Message database file. */
		oldLocation = mDb->fileName();
		switch (action) {
		case RA_RELOCATE:
			relocationSucceeded = mDb->copyDb(newLocation);
			break;
		case RA_CREATE_NEW:
			relocationSucceeded = mDb->createDb(newLocation);
			break;
		default:
			Q_ASSERT(0);
			relocationSucceeded = false;
			break;
		}
		if (relocationSucceeded) {
			/*
			 * Remember original file name as the file still
			 * exists.
			 */
			relocatedMsgDbs.append(
			    MsgDbListEntry(mDb, oldLocation));
		} else {
			break; /* Break the for cycle. */
		}

		/* File database file. */
		oldLocation = fDb->fileName();
		switch (action) {
		case RA_RELOCATE:
			relocationSucceeded = fDb->copyDb(newLocation);
			break;
		case RA_CREATE_NEW:
			relocationSucceeded = fDb->createDb(newLocation);
			break;
		default:
			Q_ASSERT(0);
			relocationSucceeded = false;
			break;
		}
		if (relocationSucceeded) {
			/*
			 * Remember original file name as the file still
			 * exists.
			 */
			relocatedFileDbs.append(
			    FileDbListEntry(fDb, oldLocation));
		} else {
			break; /* Break the for cycle. */
		}
	}

	if (relocationSucceeded) {
		/* Delete all original files. */
		foreach (const MsgDbListEntry &entry, relocatedMsgDbs) {
			QFile::remove(entry.second);
		}
		foreach (const FileDbListEntry &entry, relocatedFileDbs) {
			QFile::remove(entry.second);
		}
	} else {
		/*
		 * Revert all databases to original locations and delete the
		 * new locations.
		 */
		QString newLocation;
		foreach (const MsgDbListEntry &entry, relocatedMsgDbs) {
			newLocation = entry.first->fileName();
			if (entry.first->openDb(entry.second, true)) {
				QFile::remove(newLocation);
			} else {
				qCritical(
				    "Cannot revert to original message database file '%s'.",
				    entry.second.toUtf8().constData());
				/* TODO -- Critical. Cannot revert. */
			}
		}
		foreach (const FileDbListEntry &entry, relocatedFileDbs) {
			newLocation = entry.first->fileName();
			if (entry.first->openDb(entry.second, true)) {
				QFile::remove(newLocation);
			} else {
				qCritical(
				    "Cannot revert to original file database file '%s'.",
				    entry.second.toUtf8().constData());
				/* TODO -- Critical. Cannot revert. */
			}
		}
	}

	return relocationSucceeded;
}
Martin Straka's avatar
Martin Straka committed
516

517 518
int Messages::searchMsg(const QVariant &msgModelVariant, const QString &phrase,
    MessageTypes msgTypes)
Martin Straka's avatar
Martin Straka committed
519 520 521 522 523
{
	qDebug("%s()", __func__);

	int msgs = 0;

524 525 526 527 528 529 530 531 532
	MessageListModel *messageModel =
	    MessageListModel::fromVariant(msgModelVariant);
	if (messageModel == Q_NULLPTR) {
		Q_ASSERT(0);
		qCritical("%s", "Cannot access message model.");
		return 0;
	}

	messageModel->clearAll();
Martin Straka's avatar
Martin Straka committed
533 534 535 536

	QStringList userNameList = AccountListModel::globAccounts.keys();
	foreach (const QString &userName, userNameList) {

537 538
		MessageDb *msgDb = globMessageDbsPtr->accessMessageDb(
		    globSet.dbsLocation, userName,
Martin Straka's avatar
Martin Straka committed
539 540 541 542 543 544
		    AccountListModel::globAccounts[userName].storeToDisk());
		if (msgDb == Q_NULLPTR) {
			qDebug() << "ERROR: Message database cannot open!" << userName;
			continue;
		}

545
		if (msgTypes & TYPE_RECEIVED) {
546 547
			msgs += msgDb->searchAndAppendMsgs(messageModel,
			    userName, phrase, MessageDb::TYPE_RECEIVED);
548 549
		}
		if (msgTypes & TYPE_SENT) {
550 551
			msgs += msgDb->searchAndAppendMsgs(messageModel,
			    userName, phrase, MessageDb::TYPE_SENT);
552
		}
Martin Straka's avatar
Martin Straka committed
553 554 555 556
	}

	return msgs;
}