files.cpp 28.5 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>
Martin Straka's avatar
Martin Straka committed
27
#include <QSysInfo>
28
#include <QTextStream>
29

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

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

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

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

	Dialogues::errorMessage(
	    !destPath.isEmpty() ? Dialogues::INFORMATION : Dialogues::CRITICAL,
71 72 73 74 75
	    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());
76 77 78 79
}

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

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

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

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

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

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

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

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

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

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

155
	return fDb->getFileFromDb(fileId).binaryContent();
156 157
}

158 159 160 161 162
QString Files::getLocFileLocation(void)
{
	return appLogDirPath();
}

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
QString Files::loadLogContent(const QString &filePath)
{
	debugFuncCall();

	QString log;

	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(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(line);
		}
		file.close();
	}

	return log;
}

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

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

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

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

222
	Isds::Document document(fDb->getFileFromDb(fileId));
223

224
	openAttachment(document.fileDescr(), document.base64Content().toUtf8());
225 226
}

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

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

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

245
	QString filePath(writeFile(appTmpDirPath(), fileName,
246
	    base64ToRaw(base64Data)));
247 248

	if (!filePath.isEmpty()) {
249 250
		logInfoNL("Creating temporary file '%s'.",
		    filePath.toUtf8().constData());
251 252
		openAttachmentFromPath(filePath);
	} else {
253 254
		logErrorNL("Cannot create temporary file for '%s'.",
		    fileName.toUtf8().constData());
255 256 257 258 259 260 261 262 263 264
		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()) {
265
		logErrorNL("%s", "File path is empty.");
266
		Q_ASSERT(0);
267 268 269 270 271
		return;
	}

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

277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
#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
292 293
}

Martin Straka's avatar
Martin Straka committed
294
void Files::sendLogViaEmail(const QString &logContent)
295
{
Martin Straka's avatar
Martin Straka committed
296 297 298 299 300 301 302 303
	debugFuncCall();

	int msgResponse = Dialogues::message(Dialogues::QUESTION,
	    tr("Send log via email"),
	    tr("Do you want to send the log information to developers?"),
	    QString(), Dialogues::NO | Dialogues::YES, Dialogues::NO);
	if (msgResponse == Dialogues::NO) {
		return;
304
	}
Martin Straka's avatar
Martin Straka committed
305 306 307 308 309
	QString subject = "Mobile Datovka: Log File";
	QString body = "Mobile Datovka";
	body.append("\nVersion: " + QStringLiteral(VERSION));
	body.append("\nOS: " + QSysInfo::productType() + " " + QSysInfo::productVersion());
	body.append("\nArch: " + QSysInfo::currentCpuArchitecture());
310

Martin Straka's avatar
Martin Straka committed
311 312 313 314 315
	removeDirFromDocLoc(DATOVKA_MAIL_DIR_NAME);

	QString targetPath(appEmailDirPath("Log"));
	QString filePath(writeFile(targetPath, "mobile-datovka.log",
	    base64ToRaw(logContent.toUtf8())));
316 317
	QStringList attachmentList;
	attachmentList.append(filePath);
Martin Straka's avatar
Martin Straka committed
318

319 320
	const QString boundary = generateBoundaryString();
	QString emailMessage = createEmailMessage(body, subject,  boundary);
Martin Straka's avatar
Martin Straka committed
321 322
	addAttachmentToEmailMessage(emailMessage, "mobile-datovka.log",
	    logContent.toUtf8().toBase64(), boundary);
323 324 325 326
	finishEmailMessage(emailMessage, boundary);
	sendEmail(emailMessage, attachmentList, subject, body, 0);
}

327
void Files::sendMsgFilesWithEmail(const QString &userName, qint64 msgId,
328
    MsgAttachFlags attachFlags)
329
{
330
	debugFuncCall();
331

332 333
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
	        (GlobInstcs::messageDbsPtr == Q_NULLPTR) ||
334
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
335
	        (GlobInstcs::zfoDbPtr == Q_NULLPTR) ||
336
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
337 338 339 340
		Q_ASSERT(0);
		return;
	}

341 342 343 344 345 346 347
	if (userName.isEmpty() || msgId <= 0) {
		return;
	}

	QString body;
	QString subject;

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

363
	QList<Isds::Document> documents;
364 365

	/* Get attachment files from database if needed */
366
	if (attachFlags & MSG_ATTACHS) {
367 368 369 370
		FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
		    GlobInstcs::setPtr->dbsLocation, userName,
		    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
		if (fDb == Q_NULLPTR) {
371
			logErrorNL("Cannot access file database for '%s'.",
Karel Slaný's avatar
Karel Slaný committed
372
			    userName.toUtf8().constData());
373 374 375
			return;
		}

376 377
		documents = fDb->getFilesFromDb(msgId);
		if (documents.isEmpty()) {
378
			logErrorNL("Missing attachments for message '%s'.",
Karel Slaný's avatar
Karel Slaný committed
379
			    QString::number(msgId).toUtf8().constData());
380 381
			return;
		}
382 383
	}

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

403
	/* Create email content, email attachment path, email eml content */
404 405
	removeDirFromDocLoc(DATOVKA_MAIL_DIR_NAME);
	QString targetPath(appEmailDirPath(QString::number(msgId)));
406 407
	const QString boundary = generateBoundaryString();
	QString emailMessage = createEmailMessage(body, subject,  boundary);
408
	QStringList filePathList;
409

410
	/* Write attachment files to email directory */
411 412
	foreach (const Isds::Document &document, documents) {
		QString fileName = document.fileDescr();
413
		if (fileName.isEmpty()) {
414
			logErrorNL("%s", "File name is empty.");
415 416
			return;
		}
417
		fileName = writeFile(targetPath, fileName,
418
		    document.binaryContent());
419
		filePathList.append(fileName);
420 421
		addAttachmentToEmailMessage(emailMessage, document.fileDescr(),
		    document.base64Content().toUtf8(), boundary);
422 423 424 425
	}

	finishEmailMessage(emailMessage, boundary);

426 427
	/* Send email */
	sendEmail(emailMessage, filePathList, subject, body, msgId);
428
}
429 430 431

void Files::deleteFileDb(const QString &userName)
{
432
	debugFuncCall();
433

434 435
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
	        (GlobInstcs::messageDbsPtr == Q_NULLPTR) ||
436 437
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
438 439 440 441
		Q_ASSERT(0);
		return;
	}

442
	int msgResponse = Dialogues::message(Dialogues::QUESTION,
443 444 445
	    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."),
446 447
	    Dialogues::NO | Dialogues::YES, Dialogues::NO);
	if (msgResponse == Dialogues::NO) {
448 449 450
		return;
	}

451 452
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
453
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
454
	if (fDb == Q_NULLPTR) {
455
		logErrorNL("%s", "Cannot access file database.");
456 457
		return;
	}
458
	if (!GlobInstcs::fileDbsPtr->deleteDb(fDb)) {
459
		logErrorNL("%s", "Cannot delete file database.");
460 461 462
		return;
	}

463 464
	MessageDb *msgDb = GlobInstcs::messageDbsPtr->accessMessageDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
465
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
466
	if (msgDb == Q_NULLPTR) {
467
		logErrorNL("%s", "Cannot access message database.");
468 469 470
		return;
	}
	if (!msgDb->setAttachmentsDownloaded(false)) {
471
		logErrorNL("%s", "Message data missing.");
472 473 474
		return;
	}
}
475

Karel Slaný's avatar
Karel Slaný committed
476
void Files::vacuumFileDbs(void)
477
{
478
	debugFuncCall();
479

480
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
481 482
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
483 484 485 486
		Q_ASSERT(0);
		return;
	}

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

489
	QStringList userNameList(GlobInstcs::acntMapPtr->keys());
490
	foreach (const QString &userName, userNameList) {
491 492
		FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
		    GlobInstcs::setPtr->dbsLocation, userName,
493
		    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
494
		if (fDb == Q_NULLPTR) {
495 496 497
			logErrorNL(
			    "Cannot access file database for username '%s'.",
			    userName.toUtf8().constData());
498 499
			return;
		}
Karel Slaný's avatar
Karel Slaný committed
500
		fDb->vacuumFileDb();
501 502
	}

Karel Slaný's avatar
Karel Slaný committed
503
	emit statusBarTextChanged(tr("Operation Vacuum has finished"), false);
504
}
505 506 507

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

510 511
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
	        (GlobInstcs::messageDbsPtr == Q_NULLPTR) ||
512 513
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
514 515 516 517
		Q_ASSERT(0);
		return false;
	}

518 519
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
520
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
521
	if (fDb == Q_NULLPTR) {
522 523
		logErrorNL("Cannot access file database for username '%s'.",
		    userName.toUtf8().constData());
524 525 526 527
		return false;
	}

	if (fDb->deleteFilesFromDb(msgId)) {
528 529
		MessageDb *msgDb = GlobInstcs::messageDbsPtr->accessMessageDb(
		    GlobInstcs::setPtr->dbsLocation, userName,
530
		    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
531
		if (msgDb == Q_NULLPTR) {
532
			logErrorNL("%s", "Cannot access message database.");
533 534 535 536 537 538 539
			return false;
		}
		return msgDb->setAttachmentDownloaded(msgId, false);
	}

	return false;
}
540

541 542 543 544
bool Files::fileReadable(const QString &filePath)
{
	if (filePath.isEmpty()) {
		Q_ASSERT(0);
545
		logErrorNL("%s", "Target ZFO path is empty.");
546 547 548 549 550 551
		return false;
	}

	{
		QFileInfo fileInfo(filePath);
		if (!fileInfo.isFile() || !fileInfo.isReadable()) {
552 553
			logErrorNL("Cannot open ZFO file '%s'.",
			    filePath.toUtf8().constData());
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
			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);
575 576
		logErrorNL("Cannot open file '%s'.",
		    filePath.toUtf8().constData());
577 578 579 580 581 582 583 584 585 586 587 588 589
		return QByteArray();
	}

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

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

590 591
MsgInfo *Files::zfoData(const QVariant &attachModelVariant,
    const QByteArray &rawZfoData)
592
{
593
	enum MsgInfo::ZfoType type = MsgInfo::TYPE_UNKNOWN;
594
	QString idStr, annot, htmlDescr, emailBody;
595

596
	bool ret = parseXmlData(&type, &idStr, &annot, &htmlDescr,
597
	    FileListModel::fromVariant(attachModelVariant),
598
	    &emailBody, Xml::getXmlFromCms(rawZfoData));
599

600
	return ret ?
601
	    new (std::nothrow) MsgInfo(type, idStr, annot, htmlDescr,
602
	        emailBody) :
603
	    new (std::nothrow) MsgInfo();
604 605 606 607 608
}

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

611
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
612 613
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
614 615 616 617
		Q_ASSERT(0);
		return false;
	}

618 619
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
620
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
621 622

	if (fDb == Q_NULLPTR) {
623
		logErrorNL("%s", "Cannot access file database.");
624 625 626
		return false;
	}
	attachModel.clearAll();
627
	fDb->setFileModelFromDb(attachModel, msgId);
628 629 630 631 632 633
	return true;
}

void Files::sendAttachmentEmailZfo(const QVariant &attachModelVariant,
    const QString &msgIdStr, QString subject, QString body)
{
634
	debugFuncCall();
635 636

	/* Obtain pointer to attachment model. */
637
	const FileListModel *attachModel =
638
	    FileListModel::fromVariant(attachModelVariant);
639 640 641 642 643 644 645 646 647 648 649 650 651 652
	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;

653 654
	const QString boundary = generateBoundaryString();
	QString emailMessage = createEmailMessage(body, subject,  boundary);
655

656 657
	removeDirFromDocLoc(DATOVKA_MAIL_DIR_NAME);
	QString targetPath(appEmailDirPath(msgIdStr));
658 659 660 661 662 663 664 665 666 667 668

	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());
669 670
		QString filePath(writeFile(targetPath, attachName,
		    base64ToRaw(base64Data)));
671 672 673 674 675 676 677 678 679 680
		fileList.append(filePath);
		addAttachmentToEmailMessage(emailMessage, attachName,
		    base64Data, boundary);
	}

	finishEmailMessage(emailMessage, boundary);

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

681 682
void Files::saveMsgFilesToDisk(const QString &userName,
    const QString &msgIdStr, MsgAttachFlags attachFlags)
683
{
684
	debugFuncCall();
685

686
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
687
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
688
	        (GlobInstcs::zfoDbPtr == Q_NULLPTR) ||
689
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
690 691 692 693
		Q_ASSERT(0);
		return;
	}

694 695 696 697 698 699 700 701 702 703 704 705 706
	/* 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;
	}

707
	QList<Isds::Document> documents;
708

709 710 711 712 713 714
	/* 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) {
715
			logErrorNL("Cannot access file database for '%s'.",
716 717 718 719
			    userName.toUtf8().constData());
			return;
		}

720 721
		documents = fDb->getFilesFromDb(msgId);
		if (documents.isEmpty()) {
722
			logErrorNL("Missing attachments for message '%s'.",
723 724 725
			    QString::number(msgId).toUtf8().constData());
			return;
		}
Martin Straka's avatar
Martin Straka committed
726
	}
727

728 729
	/* Get zfo file from database if needed */
	if (attachFlags & MSG_ZFO) {
730 731 732
		Isds::Document document;
		document.setBase64Content(GlobInstcs::zfoDbPtr->getZfoContentFromDb(
		    msgId, (*GlobInstcs::acntMapPtr)[userName].isTestAccount()));
733
		if (document.binaryContent().isEmpty()) {
734
			logErrorNL("Missing zfo data for message '%s'.",
735 736 737 738 739 740 741 742
			    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;
		}
743 744
		document.setFileDescr(QString("DZ_%1.zfo").arg(msgId));
		documents.append(document);
745 746
	}

747
	QString filePath(appMsgAttachDirPath(msgIdStr));
748

749
	QString destPath;
750 751
	foreach (const Isds::Document &document, documents) {
		destPath = writeFile(filePath, document.fileDescr(),
752
		    document.binaryContent());
753 754 755 756 757 758 759 760
	}

	attachmentSavingNotification(destPath);
}

void Files::saveAttachmentsToDiskZfo(const QVariant &attachModelVariant,
    const QString &msgIdStr)
{
761
	debugFuncCall();
762 763 764

	/* Obtain pointer to attachment model. */
	const FileListModel *attachModel =
765
	    FileListModel::fromVariant(attachModelVariant);
766 767 768 769 770
	if (attachModel == Q_NULLPTR) {
		Q_ASSERT(0);
		return;
	}

771
	QString targetPath(appMsgAttachDirPath(msgIdStr));
772 773 774 775

	QString destPath;
	for (int row = 0; row < attachModel->rowCount(); ++row) {
		QModelIndex idx(attachModel->index(row));
776 777
		destPath = writeFile(targetPath, attachModel->data(idx,
		    FileListModel::ROLE_FILE_NAME).toString(),
778
		    base64ToRaw(attachModel->data(idx,
779
		    FileListModel::ROLE_FILE_DATA).toByteArray()));
780 781 782 783 784
	}

	attachmentSavingNotification(destPath);
}

785 786 787 788 789 790 791
void Files::deleteTmpFileFromStorage(const QString &filePath)
{
#if defined Q_OS_IOS
	QFile file(filePath);
	file.remove();
#else
	Q_UNUSED(filePath);
792
#endif
793 794
}

795 796 797
void Files::sendEmail(const QString &emailMessage, const QStringList &fileList,
    const QString &subject, const QString &body, qint64 msgId)
{
798 799 800 801
	Q_UNUSED(subject);
	Q_UNUSED(body);
	Q_UNUSED(emailMessage);
	Q_UNUSED(msgId);
802
	Q_UNUSED(fileList);
803 804

#if defined Q_OS_IOS
805 806 807 808

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

809
#elif defined Q_OS_ANDROID
810 811 812 813

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

814 815
#else

816
	QString tmpEmailFile = writeFile(
817
	    appEmailDirPath(QString::number(msgId)),
818 819 820 821
	    QString::number(msgId) + "_mail.eml", emailMessage.toUtf8());
	QDesktopServices::openUrl(QUrl::fromLocalFile(tmpEmailFile));

#endif
822 823
}

824
bool Files::parseXmlData(enum MsgInfo::ZfoType *type, QString *idStr,
825 826
    QString *annotation, QString *msgDescrHtml, FileListModel *attachModel,
    QString *emailBody, QByteArray xmlData)
827
{
828
	debugFuncCall();
829

830
	if (xmlData.isEmpty()) {
831
		logErrorNL("%s", "XML content is empty.");
832
		return false;
833 834
	}

835
	/* Test if zfo is message, delivery info or unknown format */
836
	if (xmlData.contains(QByteArray("MessageDownloadResponse"))) {
837 838
		if (type != Q_NULLPTR) {
			*type = MsgInfo::TYPE_MESSAGE;
839
		}
840 841
		return parseAndShowXmlData(MsgInfo::TYPE_MESSAGE, idStr,
		    annotation, msgDescrHtml, attachModel, emailBody, xmlData);
842
	} else if (xmlData.contains(QByteArray("GetDeliveryInfoResponse"))) {
843 844
		if (type != Q_NULLPTR) {
			*type = MsgInfo::TYPE_DELIVERY_INFO;
845
		}
846 847
		return parseAndShowXmlData(MsgInfo::TYPE_DELIVERY_INFO, idStr,
		    annotation, msgDescrHtml, attachModel, emailBody, xmlData);
848
	} else {
849 850 851
		if (type != Q_NULLPTR) {
			*type = MsgInfo::TYPE_UNKNOWN;
		}
852
		logErrorNL("%s", "Unknown ZFO format.");
853 854
	}

855
	return false;
856 857
}

858
bool Files::parseAndShowXmlData(enum MsgInfo::ZfoType type, QString *idStr,
859 860
    QString *annotation, QString *msgDescrHtml, FileListModel *attachModel,
    QString *emailBody, QByteArray &xmlData)
861
{
862
	debugFuncCall();
863

864 865 866 867 868
	if (type == MsgInfo::TYPE_UNKNOWN) {
		Q_ASSERT(0);
		return false;
	}

869 870
	Isds::Message message = Xml::parseCompleteMessage(xmlData);
	QList<Isds::Event> events;
871

872
	if (type == MsgInfo::TYPE_DELIVERY_INFO) {
873
		events = Xml::parseDeliveryInfo(xmlData);
874 875 876 877
	}

	QString html = divStart;

878
	html += "<h3>" + tr("General") + "</h3>";
879 880
	html += strongInfoLine(tr("Subject"), message.envelope().dmAnnotation());
	QString size = QString::number(message.envelope().dmAttachmentSize());
881
	html += strongInfoLine(tr("Attachment size"),
882
	    (size == "0") ? "&lt;1 kB" : "~" + size + " kB");
883
	html += strongInfoLine(tr("Personal delivery"),
884
	    (message.envelope().dmPersonalDelivery()) ? tr("Yes") : tr("No"));
885
	html += strongInfoLine(tr("Delivery by fiction"),
886
	    (message.envelope().dmAllowSubstDelivery()) ? tr("Yes") : tr("No"));
887 888

	html += "<h3>" + tr("Sender") + "</h3>";
889 890 891 892 893 894
	html += strongInfoLine(tr("Databox ID"),
	    message.envelope().dbIDSender());
	html += strongInfoLine(tr("Name"),
	    message.envelope().dmSender());
	html += strongInfoLine(tr("Address"),
	    message.envelope().dmSenderAddress());
895 896

	html += "<h3>" + tr("Recipient") + "</h3>";
897 898 899 900 901 902 903 904
	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());
905
	}
906

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

949 950
	html += "<h3>" + tr("Message state") + "</h3>";
	html += strongInfoLine(tr("Delivery time"),
951
	    dateTimeStrFromDbFormat(
952
	        utcDateTimeToDbFormatStr(message.envelope().dmDeliveryTime()),
953
	        DATETIME_QML_FORMAT));
954
	html += strongInfoLine(tr("Accetance time"),
955
	    dateTimeStrFromDbFormat(
956
	        utcDateTimeToDbFormatStr(message.envelope().dmAcceptanceTime()),
957
	        DATETIME_QML_FORMAT));
958
	html += strongInfoLine(tr("Status"),
959
	    QString::number(message.envelope().dmMessageStatus()));
960

961
	if (type == MsgInfo::TYPE_DELIVERY_INFO) {
962
		html += "<h3>" + tr("Events") + "</h3>";
963
		foreach (const Isds::Event &event, events) {
964 965
			html += divStart +
			    strongInfoLine(dateTimeStrFromDbFormat(
966 967
			        utcDateTimeToDbFormatStr(event.time()),
			        DATETIME_QML_FORMAT), event.descr())
968 969 970 971
			    + divEnd;
		}
	}

972 973
	html += divEnd;

974
	// Create body for email
975 976 977 978
	QString body = generateEmailBodyText(message.envelope().dmId(),
	    message.envelope().dmSender(), message.envelope().dmRecipient(),
	    dateTimeStrFromDbFormat(
	    utcDateTimeToDbFormatStr(message.envelope().dmAcceptanceTime()),
979
	    DATETIME_QML_FORMAT));
980

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

1005 1006
	return true;
}