files.cpp 28.1 KB
Newer Older
1
/*
2
 * Copyright (C) 2014-2018 CZ.NIC
3 4 5 6 7 8 9 10
 *
 * 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
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 13 14 15 16 17 18 19 20 21 22 23 24
 * 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 <QDesktopServices>
25
#include <QFileInfo>
Martin Straka's avatar
Martin Straka committed
26
#include <QQmlEngine>
27
#include <QTextStream>
28

29
#include "ios/src/url_opener.h"
30
#include "src/auxiliaries/attachment_helper.h"
31
#include "src/auxiliaries/email_helper.h"
32
#include "src/common.h"
33
#include "src/crypto/crypto.h"
34
#include "src/datovka_shared/log/global.h"
35
#include "src/datovka_shared/log/log.h"
36
#include "src/datovka_shared/log/memory_log.h"
37
#include "src/dialogues/dialogues.h"
38
#include "src/files.h"
39
#include "src/global.h"
40 41
#include "src/io/filesystem.h"
#include "src/models/accountmodel.h"
42
#include "src/settings.h"
43
#include "src/sqlite/dbs.h"
44 45
#include "src/sqlite/file_db_container.h"
#include "src/sqlite/message_db_container.h"
46
#include "src/sqlite/zfo_db.h"
47 48 49
#include "src/xml/xml_base.h"
#include "src/xml/xml_download_delivery_info.h"
#include "src/xml/xml_download_message.h"
50

Martin Straka's avatar
Martin Straka committed
51 52 53
void Files::declareQML(void)
{
	qmlRegisterType<Files>("cz.nic.mobileDatovka.files", 1, 0, "FileIdType");
54
	qmlRegisterType<Files>("cz.nic.mobileDatovka.files", 1, 0, "MsgAttachFlag");
Martin Straka's avatar
Martin Straka committed
55
	qRegisterMetaType<Files::FileIdType>();
56
	qRegisterMetaType<Files::MsgAttachFlag>();
Martin Straka's avatar
Martin Straka committed
57 58
}

59 60
Files::Files(QObject *parent)
    : QObject(parent)
61 62 63
{
}

64
void Files::attachmentSavingNotification(const QString &destPath)
65
{
66 67 68 69
	QFileInfo fi(destPath);

	Dialogues::errorMessage(
	    !destPath.isEmpty() ? Dialogues::INFORMATION : Dialogues::CRITICAL,
70 71 72 73 74
	    tr("Attachment saving"),
	    !destPath.isEmpty() ? tr("Attachments have been saved.") :
	        tr("Attachments have not been saved!"),
	    !destPath.isEmpty() ?
	        tr("Path: '%1'").arg(fi.absolutePath()) : QString());
75 76 77 78
}

void Files::deleteExpiredFilesFromDbs(int days)
{
79
	debugFuncCall();
80

81 82
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
	        (GlobInstcs::messageDbsPtr == Q_NULLPTR) ||
83 84
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
85 86 87 88
		Q_ASSERT(0);
		return;
	}

89
	const QStringList userNameList(GlobInstcs::acntMapPtr->keys());
90
	foreach (const QString &userName, userNameList) {
91 92
		FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
		    GlobInstcs::setPtr->dbsLocation, userName,
93
		    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
94
		if (fDb == Q_NULLPTR) {
95 96 97
			logErrorNL(
			    "Cannot access file database for username '%s'.",
			    userName.toUtf8().constData());
98 99
			return;
		}
100
		const QList<qint64> msgIDList(fDb->cleanFilesInDb(days));
101 102 103 104
		if (msgIDList.isEmpty()) {
			continue;
		}

105 106
		MessageDb *msgDb = GlobInstcs::messageDbsPtr->accessMessageDb(
		    GlobInstcs::setPtr->dbsLocation, userName,
107
		    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
108
		if (msgDb == Q_NULLPTR) {
109 110 111
			logErrorNL(
			    "Cannot access message database for username '%s'.",
			    userName.toUtf8().constData());
112 113
			return;
		}
114
		msgDb->beginTransaction();
115 116
		foreach (qint64 msgId, msgIDList) {
			msgDb->setAttachmentDownloaded(msgId, false);
117
		}
118
		msgDb->commitTransaction();
119 120 121
	}
}

122 123 124 125 126 127 128 129 130 131 132
QString Files::getAttachmentFileIcon(const QString &fileName)
{
	return getAttachmentFileIconFromFileExtension(fileName);
}

qint64 Files::getAttachmentSizeInBytes(const QString &filePath)
{
	QFileInfo fileInfo(filePath);
	return fileInfo.size();
}

133
QByteArray Files::getFileRawContentFromDb(const QString &userName, int fileId)
134
{
135
	debugFuncCall();
136

137
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
138 139
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
140 141 142 143
		Q_ASSERT(0);
		return QByteArray();
	}

144 145
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
146
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
147

148
	if (fDb == Q_NULLPTR) {
149 150
		logErrorNL("Cannot access file database for username '%s'.",
		    userName.toUtf8().constData());
151 152 153
		return QByteArray();
	}

154
	return fDb->getFileFromDb(fileId).binaryContent();
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
QString Files::loadLogContent(const QString &filePath)
{
	debugFuncCall();

	QString log;
	log.append("<ul>");

	if (filePath.isEmpty()) {
		MemoryLog *mLog = GlobInstcs::logPtr->memoryLog();

		if (mLog == Q_NULLPTR) {
			return QString();
		}

		const QList<quint64> keys(mLog->keys());
		foreach (quint64 key, keys) {
			log.append("<li>");
			log.append(mLog->message(key));
		}
	} else {

		QFile file(filePath);
		if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
			Q_ASSERT(0);
			logErrorNL("Cannot open file '%s'.",
			    filePath.toUtf8().constData());
			return QString();
		}

		QTextStream in(&file);
		while (!in.atEnd()) {
			QString line = in.readLine();
			log.append("<li>");
			log.append(line);
		}
		file.close();
	}

	log.append("</ul>");
	return log;
}

199
void Files::openAttachmentFromDb(const QString &userName, int fileId)
200
{
201
	debugFuncCall();
202

203
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
204 205
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
206 207 208 209
		Q_ASSERT(0);
		return;
	}

210 211
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
212
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
213

214
	if (fDb == Q_NULLPTR) {
215 216
		logErrorNL("Cannot access file database for username '%s'.",
		    userName.toUtf8().constData());
217 218 219
		return;
	}

220
	Isds::Document document(fDb->getFileFromDb(fileId));
221

222
	openAttachment(document.fileDescr(), document.base64Content().toUtf8());
223 224
}

225 226
void Files::openAttachment(const QString &fileName,
    const QByteArray &base64Data)
227 228
{
	Q_ASSERT(!fileName.isEmpty());
229
	Q_ASSERT(!base64Data.isEmpty());
230

231
	if (fileName.isEmpty() || base64Data.isEmpty()) {
232
		logErrorNL("%s", "File name or its content is empty.");
233 234 235
		return;
	}

236 237
	if (isZfoFile(fileName)) {
		/* Don't open zfo files from here. */
238
		logErrorNL("%s", "The application should open ZFO files by itself.");
239
		Q_ASSERT(0);
240 241 242
		return;
	}

243
	QString filePath(writeFile(appTmpDirPath(), fileName,
244
	    base64ToRaw(base64Data)));
245 246

	if (!filePath.isEmpty()) {
247 248
		logInfoNL("Creating temporary file '%s'.",
		    filePath.toUtf8().constData());
249 250
		openAttachmentFromPath(filePath);
	} else {
251 252
		logErrorNL("Cannot create temporary file for '%s'.",
		    fileName.toUtf8().constData());
253 254 255 256 257 258 259 260 261 262
		Dialogues::errorMessage(Dialogues::CRITICAL,
		    tr("Open attachment error"),
		    tr("Cannot save selected file to disk for opening."),
		    QString());
	}
}

void Files::openAttachmentFromPath(const QString &filePath)
{
	if (filePath.isEmpty()) {
263
		logErrorNL("%s", "File path is empty.");
264
		Q_ASSERT(0);
265 266 267 268 269
		return;
	}

	if (isZfoFile(filePath)) {
		/* Don't open zfo files from here. */
270
		logErrorNL("%s", "The application should open ZFO files by itself.");
271
		Q_ASSERT(0);
272 273
		return;
	}
274

275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
#ifdef Q_OS_IOS

	UrlOpener urlOpener;
	urlOpener.openFile(filePath);

#else

	if (!QDesktopServices::openUrl(QUrl::fromLocalFile(filePath))) {
		Dialogues::errorMessage(Dialogues::CRITICAL,
		    tr("Open attachment error"),
		    tr("There is no application to open this file format."),
		    tr("File: '%1'").arg(filePath));
	}

#endif
290 291
}

292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
bool Files::sendLogFileViaEmail(const QString &filePath)
{
	QFile file(filePath);
	if (!file.open(QIODevice::ReadOnly)) {
		Q_ASSERT(0);
		logErrorNL("Cannot open file '%s'.",
		    filePath.toUtf8().constData());
		return false;
	}

	/* TODO */
	QString log(file.readAll());
	QStringList attachmentList;
	attachmentList.append(filePath);
	QString subject = tr("Log file");
	QString body = tr("Log file");
	removeDirFromDocLoc(DATOVKA_MAIL_DIR_NAME);
	const QString boundary = generateBoundaryString();
	QString emailMessage = createEmailMessage(body, subject,  boundary);
	addAttachmentToEmailMessage(emailMessage, "mdatovka.log",
	    log.toUtf8().toBase64(), boundary);
	finishEmailMessage(emailMessage, boundary);
	sendEmail(emailMessage, attachmentList, subject, body, 0);

	return true;
}

319
void Files::sendMsgFilesWithEmail(const QString &userName, qint64 msgId,
320
    MsgAttachFlags attachFlags)
321
{
322
	debugFuncCall();
323

324 325
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
	        (GlobInstcs::messageDbsPtr == Q_NULLPTR) ||
326
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
327
	        (GlobInstcs::zfoDbPtr == Q_NULLPTR) ||
328
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
329 330 331 332
		Q_ASSERT(0);
		return;
	}

333 334 335 336 337 338 339
	if (userName.isEmpty() || msgId <= 0) {
		return;
	}

	QString body;
	QString subject;

340
	/* Fill email subject and email body */
341 342
	MessageDb *msgDb = GlobInstcs::messageDbsPtr->accessMessageDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
343
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
344
	if (msgDb == Q_NULLPTR) {
345
		logErrorNL("Cannot access message database for '%s'.",
Karel Slaný's avatar
Karel Slaný committed
346
		    userName.toUtf8().constData());
347 348
		return;
	}
349
	if (!msgDb->getMessageEmailDataFromDb(msgId, body, subject)) {
350
		logErrorNL("Missing data of message '%s'.",
Karel Slaný's avatar
Karel Slaný committed
351
		    QString::number(msgId).toUtf8().constData());
352 353 354
		return;
	}

355
	QList<Isds::Document> documents;
356 357

	/* Get attachment files from database if needed */
358
	if (attachFlags & MSG_ATTACHS) {
359 360 361 362
		FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
		    GlobInstcs::setPtr->dbsLocation, userName,
		    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
		if (fDb == Q_NULLPTR) {
363
			logErrorNL("Cannot access file database for '%s'.",
Karel Slaný's avatar
Karel Slaný committed
364
			    userName.toUtf8().constData());
365 366 367
			return;
		}

368 369
		documents = fDb->getFilesFromDb(msgId);
		if (documents.isEmpty()) {
370
			logErrorNL("Missing attachments for message '%s'.",
Karel Slaný's avatar
Karel Slaný committed
371
			    QString::number(msgId).toUtf8().constData());
372 373
			return;
		}
374 375
	}

376
	/* Get zfo file from database if needed */
377
	if (attachFlags & MSG_ZFO) {
378 379 380
		Isds::Document document;
		document.setBase64Content(GlobInstcs::zfoDbPtr->getZfoContentFromDb(
		    msgId, (*GlobInstcs::acntMapPtr)[userName].isTestAccount()));
381
		if (document.binaryContent().isEmpty()) {
382
			logErrorNL("Missing zfo data for message '%s'.",
Karel Slaný's avatar
Karel Slaný committed
383
			    QString::number(msgId).toUtf8().constData());
384 385 386 387
			Dialogues::errorMessage(Dialogues::CRITICAL,
			    tr("ZFO missing"),
			    tr("ZFO message is not present in local database."),
			    tr("Download complete message again to obtain it."));
388 389 390
			Q_ASSERT(0);
			return;
		}
391 392
		document.setFileDescr(QString("DZ_%1.zfo").arg(msgId));
		documents.append(document);
393
	}
394

395
	/* Create email content, email attachment path, email eml content */
396 397
	removeDirFromDocLoc(DATOVKA_MAIL_DIR_NAME);
	QString targetPath(appEmailDirPath(QString::number(msgId)));
398 399
	const QString boundary = generateBoundaryString();
	QString emailMessage = createEmailMessage(body, subject,  boundary);
400
	QStringList filePathList;
401

402
	/* Write attachment files to email directory */
403 404
	foreach (const Isds::Document &document, documents) {
		QString fileName = document.fileDescr();
405
		if (fileName.isEmpty()) {
406
			logErrorNL("%s", "File name is empty.");
407 408
			return;
		}
409
		fileName = writeFile(targetPath, fileName,
410
		    document.binaryContent());
411
		filePathList.append(fileName);
412 413
		addAttachmentToEmailMessage(emailMessage, document.fileDescr(),
		    document.base64Content().toUtf8(), boundary);
414 415 416 417
	}

	finishEmailMessage(emailMessage, boundary);

418 419
	/* Send email */
	sendEmail(emailMessage, filePathList, subject, body, msgId);
420
}
421 422 423

void Files::deleteFileDb(const QString &userName)
{
424
	debugFuncCall();
425

426 427
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
	        (GlobInstcs::messageDbsPtr == Q_NULLPTR) ||
428 429
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
430 431 432 433
		Q_ASSERT(0);
		return;
	}

434
	int msgResponse = Dialogues::message(Dialogues::QUESTION,
435 436 437
	    tr("Delete files: %1").arg(userName),
	    tr("Do you want to clean up the file database of account '%1'?").arg(userName),
	    tr("Note: All attachment files of messages will be removed from the database."),
438 439
	    Dialogues::NO | Dialogues::YES, Dialogues::NO);
	if (msgResponse == Dialogues::NO) {
440 441 442
		return;
	}

443 444
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
445
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
446
	if (fDb == Q_NULLPTR) {
447
		logErrorNL("%s", "Cannot access file database.");
448 449
		return;
	}
450
	if (!GlobInstcs::fileDbsPtr->deleteDb(fDb)) {
451
		logErrorNL("%s", "Cannot delete file database.");
452 453 454
		return;
	}

455 456
	MessageDb *msgDb = GlobInstcs::messageDbsPtr->accessMessageDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
457
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
458
	if (msgDb == Q_NULLPTR) {
459
		logErrorNL("%s", "Cannot access message database.");
460 461 462
		return;
	}
	if (!msgDb->setAttachmentsDownloaded(false)) {
463
		logErrorNL("%s", "Message data missing.");
464 465 466
		return;
	}
}
467

Karel Slaný's avatar
Karel Slaný committed
468
void Files::vacuumFileDbs(void)
469
{
470
	debugFuncCall();
471

472
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
473 474
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
475 476 477 478
		Q_ASSERT(0);
		return;
	}

Karel Slaný's avatar
Karel Slaný committed
479
	emit statusBarTextChanged(tr("Vacuum databases"), true);
480

481
	QStringList userNameList(GlobInstcs::acntMapPtr->keys());
482
	foreach (const QString &userName, userNameList) {
483 484
		FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
		    GlobInstcs::setPtr->dbsLocation, userName,
485
		    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
486
		if (fDb == Q_NULLPTR) {
487 488 489
			logErrorNL(
			    "Cannot access file database for username '%s'.",
			    userName.toUtf8().constData());
490 491
			return;
		}
Karel Slaný's avatar
Karel Slaný committed
492
		fDb->vacuumFileDb();
493 494
	}

Karel Slaný's avatar
Karel Slaný committed
495
	emit statusBarTextChanged(tr("Operation Vacuum has finished"), false);
496
}
497 498 499

bool Files::deleteAttachmentsFromDb(const QString &userName, qint64 msgId)
{
500
	debugFuncCall();
501

502 503
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
	        (GlobInstcs::messageDbsPtr == Q_NULLPTR) ||
504 505
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
506 507 508 509
		Q_ASSERT(0);
		return false;
	}

510 511
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
512
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
513
	if (fDb == Q_NULLPTR) {
514 515
		logErrorNL("Cannot access file database for username '%s'.",
		    userName.toUtf8().constData());
516 517 518 519
		return false;
	}

	if (fDb->deleteFilesFromDb(msgId)) {
520 521
		MessageDb *msgDb = GlobInstcs::messageDbsPtr->accessMessageDb(
		    GlobInstcs::setPtr->dbsLocation, userName,
522
		    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
523
		if (msgDb == Q_NULLPTR) {
524
			logErrorNL("%s", "Cannot access message database.");
525 526 527 528 529 530 531
			return false;
		}
		return msgDb->setAttachmentDownloaded(msgId, false);
	}

	return false;
}
532

533 534 535 536
bool Files::fileReadable(const QString &filePath)
{
	if (filePath.isEmpty()) {
		Q_ASSERT(0);
537
		logErrorNL("%s", "Target ZFO path is empty.");
538 539 540 541 542 543
		return false;
	}

	{
		QFileInfo fileInfo(filePath);
		if (!fileInfo.isFile() || !fileInfo.isReadable()) {
544 545
			logErrorNL("Cannot open ZFO file '%s'.",
			    filePath.toUtf8().constData());
546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
			return false;
		}
	}

	return true;
}

bool Files::isZfoFile(const QString &fileName)
{
	return QFileInfo(fileName).suffix().toLower() == QStringLiteral("zfo");
}

QByteArray Files::rawFileContent(const QString &filePath)
{
	if (!fileReadable(filePath)) {
		return QByteArray();
	}

	QFile file(filePath);
	if (!file.open(QIODevice::ReadOnly)) {
		Q_ASSERT(0);
567 568
		logErrorNL("Cannot open file '%s'.",
		    filePath.toUtf8().constData());
569 570 571 572 573 574 575 576 577 578 579 580 581
		return QByteArray();
	}

	QByteArray rawData(file.readAll());
	file.close();
	return rawData;
}

QByteArray Files::base64ToRaw(const QByteArray &base64Data)
{
	return QByteArray::fromBase64(base64Data);
}

582 583
MsgInfo *Files::zfoData(const QVariant &attachModelVariant,
    const QByteArray &rawZfoData)
584
{
585
	enum MsgInfo::ZfoType type = MsgInfo::TYPE_UNKNOWN;
586
	QString idStr, annot, htmlDescr, emailBody;
587

588
	bool ret = parseXmlData(&type, &idStr, &annot, &htmlDescr,
589
	    FileListModel::fromVariant(attachModelVariant),
590
	    &emailBody, Xml::getXmlFromCms(rawZfoData));
591

592
	return ret ?
593
	    new (std::nothrow) MsgInfo(type, idStr, annot, htmlDescr,
594
	        emailBody) :
595
	    new (std::nothrow) MsgInfo();
596 597 598 599 600
}

bool Files::setAttachmentModel(FileListModel &attachModel,
    const QString &userName, qint64 msgId)
{
601
	debugFuncCall();
602

603
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
604 605
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
606 607 608 609
		Q_ASSERT(0);
		return false;
	}

610 611
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
612
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
613 614

	if (fDb == Q_NULLPTR) {
615
		logErrorNL("%s", "Cannot access file database.");
616 617 618
		return false;
	}
	attachModel.clearAll();
619
	fDb->setFileModelFromDb(attachModel, msgId);
620 621 622 623 624 625
	return true;
}

void Files::sendAttachmentEmailZfo(const QVariant &attachModelVariant,
    const QString &msgIdStr, QString subject, QString body)
{
626
	debugFuncCall();
627 628

	/* Obtain pointer to attachment model. */
629
	const FileListModel *attachModel =
630
	    FileListModel::fromVariant(attachModelVariant);
631 632 633 634 635 636 637 638 639 640 641 642 643 644
	if (attachModel == Q_NULLPTR) {
		Q_ASSERT(0);
		return;
	}

	/* Obtain message identifier. */
	bool ok = false;
	qint64 msgId = msgIdStr.toLongLong(&ok);
	if (!ok || (msgId < 0)) {
		return;
	}

	QStringList fileList;

645 646
	const QString boundary = generateBoundaryString();
	QString emailMessage = createEmailMessage(body, subject,  boundary);
647

648 649
	removeDirFromDocLoc(DATOVKA_MAIL_DIR_NAME);
	QString targetPath(appEmailDirPath(msgIdStr));
650 651 652 653 654 655 656 657 658 659 660

	for (int row = 0; row < attachModel->rowCount(); ++row) {
		QModelIndex idx(attachModel->index(row));
		/*
		 * On Android the attachment must be saved and the explicitly
		 * add into the email message.
		 */
		QByteArray base64Data(attachModel->data(idx,
		    FileListModel::ROLE_FILE_DATA).toByteArray());
		QString attachName(attachModel->data(idx,
		    FileListModel::ROLE_FILE_NAME).toString());
661 662
		QString filePath(writeFile(targetPath, attachName,
		    base64ToRaw(base64Data)));
663 664 665 666 667 668 669 670 671 672
		fileList.append(filePath);
		addAttachmentToEmailMessage(emailMessage, attachName,
		    base64Data, boundary);
	}

	finishEmailMessage(emailMessage, boundary);

	sendEmail(emailMessage, fileList, subject, body, msgId);
}

673 674
void Files::saveMsgFilesToDisk(const QString &userName,
    const QString &msgIdStr, MsgAttachFlags attachFlags)
675
{
676
	debugFuncCall();
677

678
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
679
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
680
	        (GlobInstcs::zfoDbPtr == Q_NULLPTR) ||
681
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
682 683 684 685
		Q_ASSERT(0);
		return;
	}

686 687 688 689 690 691 692 693 694 695 696 697 698
	/* User name must be supplied. */
	if (userName.isEmpty()) {
		Q_ASSERT(0);
		return;
	}

	/* Obtain message identifier. */
	bool ok = false;
	qint64 msgId = msgIdStr.toLongLong(&ok);
	if (!ok || (msgId < 0)) {
		return;
	}

699
	QList<Isds::Document> documents;
700

701 702 703 704 705 706
	/* Get attachment files from database if needed */
	if (attachFlags & MSG_ATTACHS) {
		FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
		    GlobInstcs::setPtr->dbsLocation, userName,
		    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
		if (fDb == Q_NULLPTR) {
707
			logErrorNL("Cannot access file database for '%s'.",
708 709 710 711
			    userName.toUtf8().constData());
			return;
		}

712 713
		documents = fDb->getFilesFromDb(msgId);
		if (documents.isEmpty()) {
714
			logErrorNL("Missing attachments for message '%s'.",
715 716 717
			    QString::number(msgId).toUtf8().constData());
			return;
		}
Martin Straka's avatar
Martin Straka committed
718
	}
719

720 721
	/* Get zfo file from database if needed */
	if (attachFlags & MSG_ZFO) {
722 723 724
		Isds::Document document;
		document.setBase64Content(GlobInstcs::zfoDbPtr->getZfoContentFromDb(
		    msgId, (*GlobInstcs::acntMapPtr)[userName].isTestAccount()));
725
		if (document.binaryContent().isEmpty()) {
726
			logErrorNL("Missing zfo data for message '%s'.",
727 728 729 730 731 732 733 734
			    QString::number(msgId).toUtf8().constData());
			Dialogues::errorMessage(Dialogues::CRITICAL,
			    tr("ZFO missing"),
			    tr("ZFO message is not present in local database."),
			    tr("Download complete message again to obtain it."));
			Q_ASSERT(0);
			return;
		}
735 736
		document.setFileDescr(QString("DZ_%1.zfo").arg(msgId));
		documents.append(document);
737 738
	}

739
	QString filePath(appMsgAttachDirPath(msgIdStr));
740

741
	QString destPath;
742 743
	foreach (const Isds::Document &document, documents) {
		destPath = writeFile(filePath, document.fileDescr(),
744
		    document.binaryContent());
745 746 747 748 749 750 751 752
	}

	attachmentSavingNotification(destPath);
}

void Files::saveAttachmentsToDiskZfo(const QVariant &attachModelVariant,
    const QString &msgIdStr)
{
753
	debugFuncCall();
754 755 756

	/* Obtain pointer to attachment model. */
	const FileListModel *attachModel =
757
	    FileListModel::fromVariant(attachModelVariant);
758 759 760 761 762
	if (attachModel == Q_NULLPTR) {
		Q_ASSERT(0);
		return;
	}

763
	QString targetPath(appMsgAttachDirPath(msgIdStr));
764 765 766 767

	QString destPath;
	for (int row = 0; row < attachModel->rowCount(); ++row) {
		QModelIndex idx(attachModel->index(row));
768 769
		destPath = writeFile(targetPath, attachModel->data(idx,
		    FileListModel::ROLE_FILE_NAME).toString(),
770
		    base64ToRaw(attachModel->data(idx,
771
		    FileListModel::ROLE_FILE_DATA).toByteArray()));
772 773 774 775 776
	}

	attachmentSavingNotification(destPath);
}

777 778 779 780 781 782 783
void Files::deleteTmpFileFromStorage(const QString &filePath)
{
#if defined Q_OS_IOS
	QFile file(filePath);
	file.remove();
#else
	Q_UNUSED(filePath);
784
#endif
785 786
}

787 788 789
void Files::sendEmail(const QString &emailMessage, const QStringList &fileList,
    const QString &subject, const QString &body, qint64 msgId)
{
790 791 792 793
	Q_UNUSED(subject);
	Q_UNUSED(body);
	Q_UNUSED(emailMessage);
	Q_UNUSED(msgId);
794
	Q_UNUSED(fileList);
795 796

#if defined Q_OS_IOS
797 798 799 800

	UrlOpener urlOpener;
	urlOpener.createEmail(body, subject, fileList);

801
#elif defined Q_OS_ANDROID
802 803 804 805

	QDesktopServices::openUrl(QUrl("mailto:?subject=" + subject +
	    "&body=" + body));

806 807
#else

808
	QString tmpEmailFile = writeFile(
809
	    appEmailDirPath(QString::number(msgId)),
810 811 812 813
	    QString::number(msgId) + "_mail.eml", emailMessage.toUtf8());
	QDesktopServices::openUrl(QUrl::fromLocalFile(tmpEmailFile));

#endif
814 815
}

816
bool Files::parseXmlData(enum MsgInfo::ZfoType *type, QString *idStr,
817 818
    QString *annotation, QString *msgDescrHtml, FileListModel *attachModel,
    QString *emailBody, QByteArray xmlData)
819
{
820
	debugFuncCall();
821

822
	if (xmlData.isEmpty()) {
823
		logErrorNL("%s", "XML content is empty.");
824
		return false;
825 826
	}

827
	/* Test if zfo is message, delivery info or unknown format */
828
	if (xmlData.contains(QByteArray("MessageDownloadResponse"))) {
829 830
		if (type != Q_NULLPTR) {
			*type = MsgInfo::TYPE_MESSAGE;
831
		}
832 833
		return parseAndShowXmlData(MsgInfo::TYPE_MESSAGE, idStr,
		    annotation, msgDescrHtml, attachModel, emailBody, xmlData);
834
	} else if (xmlData.contains(QByteArray("GetDeliveryInfoResponse"))) {
835 836
		if (type != Q_NULLPTR) {
			*type = MsgInfo::TYPE_DELIVERY_INFO;
837
		}
838 839
		return parseAndShowXmlData(MsgInfo::TYPE_DELIVERY_INFO, idStr,
		    annotation, msgDescrHtml, attachModel, emailBody, xmlData);
840
	} else {
841 842 843
		if (type != Q_NULLPTR) {
			*type = MsgInfo::TYPE_UNKNOWN;
		}
844
		logErrorNL("%s", "Unknown ZFO format.");
845 846
	}

847
	return false;
848 849
}

850
bool Files::parseAndShowXmlData(enum MsgInfo::ZfoType type, QString *idStr,
851 852
    QString *annotation, QString *msgDescrHtml, FileListModel *attachModel,
    QString *emailBody, QByteArray &xmlData)
853
{
854
	debugFuncCall();
855

856 857 858 859 860
	if (type == MsgInfo::TYPE_UNKNOWN) {
		Q_ASSERT(0);
		return false;
	}

861 862
	Isds::Message message = Xml::parseCompleteMessage(xmlData);
	QList<Isds::Event> events;
863

864
	if (type == MsgInfo::TYPE_DELIVERY_INFO) {
865
		events = Xml::parseDeliveryInfo(xmlData);
866 867 868 869
	}

	QString html = divStart;

870
	html += "<h3>" + tr("General") + "</h3>";
871 872
	html += strongInfoLine(tr("Subject"), message.envelope().dmAnnotation());
	QString size = QString::number(message.envelope().dmAttachmentSize());
873
	html += strongInfoLine(tr("Attachment size"),
874
	    (size == "0") ? "&lt;1 kB" : "~" + size + " kB");
875
	html += strongInfoLine(tr("Personal delivery"),
876
	    (message.envelope().dmPersonalDelivery()) ? tr("Yes") : tr("No"));
877
	html += strongInfoLine(tr("Delivery by fiction"),
878
	    (message.envelope().dmAllowSubstDelivery()) ? tr("Yes") : tr("No"));
879 880

	html += "<h3>" + tr("Sender") + "</h3>";
881 882 883 884 885 886
	html += strongInfoLine(tr("Databox ID"),
	    message.envelope().dbIDSender());
	html += strongInfoLine(tr("Name"),
	    message.envelope().dmSender());
	html += strongInfoLine(tr("Address"),
	    message.envelope().dmSenderAddress());
887 888

	html += "<h3>" + tr("Recipient") + "</h3>";
889 890 891 892 893 894 895 896
	html += strongInfoLine(tr("Databox ID"),
	    message.envelope().dbIDRecipient());
	html += strongInfoLine(tr("Name"), message.envelope().dmRecipient());
	html += strongInfoLine(tr("Address"),
	    message.envelope().dmRecipientAddress());
	if (!message.envelope().dmToHands().isEmpty()) {
		html += strongInfoLine(tr("To hands"),
		    message.envelope().dmToHands());
897
	}
898

899
	QString tmpHtml;
900
	if (!message.envelope().dmSenderIdent().isEmpty()) {
901
		tmpHtml += strongInfoLine(tr("Our file mark"),
902
		    message.envelope().dmSenderIdent());
903
	}
904
	if (!message.envelope().dmSenderRefNumber().isEmpty()) {
905
		tmpHtml += strongInfoLine(tr("Our reference number"),
906
		    message.envelope().dmSenderRefNumber());
907
	}
908
	if (!message.envelope().dmRecipientIdent().isEmpty()) {
909
		tmpHtml += strongInfoLine(tr("Your file mark"),
910
		    message.envelope().dmRecipientIdent());
911
	}
912
	if (!message.envelope().dmRecipientRefNumber().isEmpty()) {
913
		tmpHtml += strongInfoLine(tr("Your reference number"),
914
		    message.envelope().dmRecipientRefNumber());
915
	}
916 917 918
	if (!message.envelope().dmLegalTitleLawStr().isEmpty()) {
		tmpHtml += strongInfoLine(tr("Law"),
		    message.envelope().dmLegalTitleLawStr());
919
	}
920 921 922
	if (!message.envelope().dmLegalTitleYearStr().isEmpty()) {
		tmpHtml += strongInfoLine(tr("Year"),
		    message.envelope().dmLegalTitleYearStr());
923
	}
924 925 926
	if (!message.envelope().dmLegalTitleSect().isEmpty()) {
		tmpHtml += strongInfoLine(tr("Section"),
		    message.envelope().dmLegalTitleSect());
927
	}
928
	if (!message.envelope().dmLegalTitlePar().isEmpty()) {
929
		tmpHtml += strongInfoLine(tr("Paragraph"),
930
		    message.envelope().dmLegalTitlePar());
931
	}
932 933 934
	if (!message.envelope().dmLegalTitlePoint().isEmpty()) {
		tmpHtml += strongInfoLine(tr("Letter"),
		    message.envelope().dmLegalTitlePoint());
935 936
	}
	if (!tmpHtml.isEmpty()) {
937
		html += "<h3>" + tr("Additional info") + "</h3>";
938 939
		html += tmpHtml;
	}
940

941 942
	html += "<h3>" + tr("Message state") + "</h3>";
	html += strongInfoLine(tr("Delivery time"),
943
	    dateTimeStrFromDbFormat(
944
	        utcDateTimeToDbFormatStr(message.envelope().dmDeliveryTime()),
945
	        DATETIME_QML_FORMAT));
946
	html += strongInfoLine(tr("Accetance time"),
947
	    dateTimeStrFromDbFormat(
948
	        utcDateTimeToDbFormatStr(message.envelope().dmAcceptanceTime()),
949
	        DATETIME_QML_FORMAT));
950
	html += strongInfoLine(tr("Status"),
951
	    QString::number(message.envelope().dmMessageStatus()));
952

953
	if (type == MsgInfo::TYPE_DELIVERY_INFO) {
954
		html += "<h3>" + tr("Events") + "</h3>";
955
		foreach (const Isds::Event &event, events) {
956 957
			html += divStart +
			    strongInfoLine(dateTimeStrFromDbFormat(
958 959
			        utcDateTimeToDbFormatStr(event.time()),
			        DATETIME_QML_FORMAT), event.descr())
960 961 962 963
			    + divEnd;
		}
	}

964 965
	html += divEnd;

966
	// Create body for email
967 968 969 970
	QString body = generateEmailBodyText(message.envelope().dmId(),
	    message.envelope().dmSender(), message.envelope().dmRecipient(),
	    dateTimeStrFromDbFormat(
	    utcDateTimeToDbFormatStr(message.envelope().dmAcceptanceTime()),
971
	    DATETIME_QML_FORMAT));
972

973
	if (idStr != Q_NULLPTR) {
974
		*idStr = QString::number(message.envelope().dmId());
975
	}
976
	if (annotation != Q_NULLPTR) {
977
		*annotation = message.envelope().dmAnnotation();
978 979 980 981 982
	}
	if (msgDescrHtml != Q_NULLPTR) {
		*msgDescrHtml = html;
	}
	if (attachModel != Q_NULLPTR) {
983
		attachModel->clearAll();
984
		foreach (const Isds::Document &document, message.documents()) {
Martin Straka's avatar
Martin Straka committed
985
			attachModel->appendFileEntry(
986
			    FileListModel::Entry(-1, document.fileDescr(),
987
			    approximateDataSize(document.binaryContent().size()),
988 989 990
			    getAttachmentFileIconFromFileExtension(
			        document.fileDescr()),
			    document.base64Content(), QString()));
991
		}
992 993 994 995
	}
	if (emailBody != Q_NULLPTR) {
		*emailBody = body;
	}
996

997 998
	return true;
}