files.cpp 27.7 KB
Newer Older
1
/*
2
 * Copyright (C) 2014-2019 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
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
15
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 17 18 19 20 21 22 23 24
 *
 * 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 <QDir>
26
#include <QFileInfo>
Martin Straka's avatar
Martin Straka committed
27
#include <QQmlEngine>
28
#include <QStringBuilder>
29

30 31 32
#if defined (Q_OS_ANDROID)
#include "android/src/android_io.h"
#endif
33
#include "ios/src/icloud_helper.h"
Martin Straka's avatar
Martin Straka committed
34
#include "ios/src/url_opener.h"
35
#include "src/auxiliaries/email_helper.h"
36
#include "src/auxiliaries/icloud_helper.h"
37
#include "src/common.h"
38
#include "src/crypto/crypto.h"
39
#include "src/datovka_shared/log/log.h"
40
#include "src/dialogues/dialogues.h"
41
#include "src/files.h"
42
#include "src/global.h"
43 44
#include "src/io/filesystem.h"
#include "src/models/accountmodel.h"
45
#include "src/settings.h"
46
#include "src/sqlite/dbs.h"
47 48
#include "src/sqlite/file_db_container.h"
#include "src/sqlite/message_db_container.h"
49
#include "src/sqlite/zfo_db.h"
50 51 52
#include "src/xml/xml_base.h"
#include "src/xml/xml_download_delivery_info.h"
#include "src/xml/xml_download_message.h"
53

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

62 63
Files::Files(QObject *parent)
    : QObject(parent)
64 65 66
{
}

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

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

void Files::deleteExpiredFilesFromDbs(int days)
{
82
	debugFuncCall();
83

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

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

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

125 126
QString Files::getAttachmentFileIcon(const QString &fileName)
{
127 128 129 130 131 132 133 134 135 136 137 138
	/* List of know file extension sufixes. For these we have png images. */
	static const QSet<QString> knowExp(QSet<QString> () <<
	    "avi" << "bmp" << "doc" << "docx" << "dwg" << "gif" << "html" <<
	    "htm" << "jpeg" << "jpg" << "mpeg" << "mpg" << "mp3" << "ods" <<
	    "odt" << "pdf" << "png" << "ppt" << "pptx" << "rtf" << "tiff" <<
	    "txt" << "wav" << "xls" << "xlsx" << "xml" << "zfo");

	const QString ext(QFileInfo(fileName).suffix().toLower());
	if ((!ext.isEmpty()) && knowExp.contains(ext)) {
		return QStringLiteral("qrc:/fileicons/fileicon_") % ext % QStringLiteral(".png");
	}
	return QStringLiteral("qrc:/fileicons/fileicon_blank.png");
139 140 141 142 143 144 145 146
}

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

147
QByteArray Files::getFileRawContentFromDb(const QString &userName, int fileId)
148
{
149
	debugFuncCall();
150

151
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
152 153
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
154 155 156 157
		Q_ASSERT(0);
		return QByteArray();
	}

158 159
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
160
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
161

162
	if (fDb == Q_NULLPTR) {
163 164
		logErrorNL("Cannot access file database for username '%s'.",
		    userName.toUtf8().constData());
165 166 167
		return QByteArray();
	}

168
	return fDb->getFileFromDb(fileId).binaryContent();
169 170
}

171
void Files::openAttachmentFromDb(const QString &userName, int fileId)
172
{
173
	debugFuncCall();
174

175
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
176 177
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
178 179 180 181
		Q_ASSERT(0);
		return;
	}

182 183
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
184
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
185

186
	if (fDb == Q_NULLPTR) {
187 188
		logErrorNL("Cannot access file database for username '%s'.",
		    userName.toUtf8().constData());
189 190 191
		return;
	}

192
	Isds::Document document(fDb->getFileFromDb(fileId));
193

194
	openAttachment(document.fileDescr(), document.binaryContent());
195 196
}

197
void Files::openAttachment(const QString &fileName,
198
    const QByteArray &binaryData)
199
{
200
	if (Q_UNLIKELY(fileName.isEmpty() || binaryData.isEmpty())) {
201
		logErrorNL("%s", "File name or its content is empty.");
202
		Q_ASSERT(0);
203 204 205
		return;
	}

206 207
	if (isZfoFile(fileName)) {
		/* Don't open zfo files from here. */
208
		logErrorNL("%s", "The application should open ZFO files by itself.");
209
		Q_ASSERT(0);
210 211 212
		return;
	}

213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
	QString docLocationRoot = appTmpDirPath();

#if defined (Q_OS_ANDROID)

	docLocationRoot = QStandardPaths::standardLocations(
	    QStandardPaths::AppDataLocation).value(0);

	QString  documentsWorkPath = docLocationRoot.append("/share_example_x_files");
	if (!QDir(documentsWorkPath).exists()) {
		if (!QDir("").mkpath(documentsWorkPath)) {
			logErrorNL("Failed to create share directory: '%s'.",
			    documentsWorkPath.toUtf8().constData());
			Dialogues::errorMessage(Dialogues::CRITICAL,
			    tr("Open attachment error"),
			    tr("Cannot save selected file to disk for opening."),
			    QString());
			return;
		}
	}
#endif

	QString filePath(writeFile(docLocationRoot, fileName, binaryData));
235 236

	if (!filePath.isEmpty()) {
237 238
		logInfoNL("Creating temporary file '%s'.",
		    filePath.toUtf8().constData());
239 240
		openAttachmentFromPath(filePath);
	} else {
241 242
		logErrorNL("Cannot create temporary file for '%s'.",
		    fileName.toUtf8().constData());
243 244 245 246 247 248 249 250 251 252
		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()) {
253
		logErrorNL("%s", "File path is empty.");
254
		Q_ASSERT(0);
255 256 257 258 259
		return;
	}

	if (isZfoFile(filePath)) {
		/* Don't open zfo files from here. */
260
		logErrorNL("%s", "The application should open ZFO files by itself.");
261
		Q_ASSERT(0);
262 263
		return;
	}
264

265 266 267 268 269
#ifdef Q_OS_IOS

	UrlOpener urlOpener;
	urlOpener.openFile(filePath);

270 271 272 273 274 275 276 277
#elif defined (Q_OS_ANDROID)

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

279
#else
280

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

#endif
289 290
}

291
void Files::sendMsgFilesWithEmail(const QString &userName, qint64 msgId,
292
    MsgAttachFlags attachFlags)
293
{
294
	debugFuncCall();
295

296 297
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
	        (GlobInstcs::messageDbsPtr == Q_NULLPTR) ||
298
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
299
	        (GlobInstcs::zfoDbPtr == Q_NULLPTR) ||
300
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
301 302 303 304
		Q_ASSERT(0);
		return;
	}

305 306 307 308 309 310 311
	if (userName.isEmpty() || msgId <= 0) {
		return;
	}

	QString body;
	QString subject;

312
	/* Fill email subject and email body */
313 314
	MessageDb *msgDb = GlobInstcs::messageDbsPtr->accessMessageDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
315
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
316
	if (msgDb == Q_NULLPTR) {
317
		logErrorNL("Cannot access message database for '%s'.",
Karel Slaný's avatar
Karel Slaný committed
318
		    userName.toUtf8().constData());
319 320
		return;
	}
321
	if (!msgDb->getMessageEmailDataFromDb(msgId, body, subject)) {
322
		logErrorNL("Missing data of message '%s'.",
Karel Slaný's avatar
Karel Slaný committed
323
		    QString::number(msgId).toUtf8().constData());
324 325 326
		return;
	}

327
	QList<Isds::Document> documents;
328 329

	/* Get attachment files from database if needed */
330
	if (attachFlags & MSG_ATTACHS) {
331 332 333 334
		FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
		    GlobInstcs::setPtr->dbsLocation, userName,
		    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
		if (fDb == Q_NULLPTR) {
335
			logErrorNL("Cannot access file database for '%s'.",
Karel Slaný's avatar
Karel Slaný committed
336
			    userName.toUtf8().constData());
337 338 339
			return;
		}

340 341
		documents = fDb->getFilesFromDb(msgId);
		if (documents.isEmpty()) {
342
			logErrorNL("Missing attachments for message '%s'.",
Karel Slaný's avatar
Karel Slaný committed
343
			    QString::number(msgId).toUtf8().constData());
344 345
			return;
		}
346 347
	}

348
	/* Get zfo file from database if needed */
349
	if (attachFlags & MSG_ZFO) {
350 351 352
		Isds::Document document;
		document.setBase64Content(GlobInstcs::zfoDbPtr->getZfoContentFromDb(
		    msgId, (*GlobInstcs::acntMapPtr)[userName].isTestAccount()));
353
		if (document.binaryContent().isEmpty()) {
354
			logErrorNL("Missing zfo data for message '%s'.",
Karel Slaný's avatar
Karel Slaný committed
355
			    QString::number(msgId).toUtf8().constData());
356 357 358 359
			Dialogues::errorMessage(Dialogues::CRITICAL,
			    tr("ZFO missing"),
			    tr("ZFO message is not present in local database."),
			    tr("Download complete message again to obtain it."));
360 361 362
			Q_ASSERT(0);
			return;
		}
363 364
		document.setFileDescr(QString("DZ_%1.zfo").arg(msgId));
		documents.append(document);
365
	}
366

367
	/* Create email content, email attachment path, email eml content */
368 369
	removeDirFromDocLoc(DATOVKA_MAIL_DIR_NAME);
	QString targetPath(appEmailDirPath(QString::number(msgId)));
370
	const QString boundary = generateBoundaryString();
371 372
	QString emailMessage = createEmailMessage(QString(), body, subject,
	    boundary);
373
	QStringList filePathList;
374

375
	/* Write attachment files to email directory */
376 377
	foreach (const Isds::Document &document, documents) {
		QString fileName = document.fileDescr();
378
		if (fileName.isEmpty()) {
379
			logErrorNL("%s", "File name is empty.");
380 381
			return;
		}
382
		fileName = writeFile(targetPath, fileName,
383
		    document.binaryContent());
384
		filePathList.append(fileName);
385 386
		addAttachmentToEmailMessage(emailMessage, document.fileDescr(),
		    document.base64Content().toUtf8(), boundary);
387 388 389 390
	}

	finishEmailMessage(emailMessage, boundary);

391
	/* Send email */
392 393
	sendEmail(emailMessage, filePathList, QString(), subject, body,
	    QString::number(msgId));
394
}
395 396 397

void Files::deleteFileDb(const QString &userName)
{
398
	debugFuncCall();
399

400 401
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
	        (GlobInstcs::messageDbsPtr == Q_NULLPTR) ||
402 403
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
404 405 406 407
		Q_ASSERT(0);
		return;
	}

408
	int msgResponse = Dialogues::message(Dialogues::QUESTION,
409 410 411
	    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."),
412 413
	    Dialogues::NO | Dialogues::YES, Dialogues::NO);
	if (msgResponse == Dialogues::NO) {
414 415 416
		return;
	}

417 418
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
419
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
420
	if (fDb == Q_NULLPTR) {
421
		logErrorNL("%s", "Cannot access file database.");
422 423
		return;
	}
424
	if (!GlobInstcs::fileDbsPtr->deleteDb(fDb)) {
425
		logErrorNL("%s", "Cannot delete file database.");
426 427 428
		return;
	}

429 430
	MessageDb *msgDb = GlobInstcs::messageDbsPtr->accessMessageDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
431
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
432
	if (msgDb == Q_NULLPTR) {
433
		logErrorNL("%s", "Cannot access message database.");
434 435 436
		return;
	}
	if (!msgDb->setAttachmentsDownloaded(false)) {
437
		logErrorNL("%s", "Message data missing.");
438 439 440
		return;
	}
}
441

Karel Slaný's avatar
Karel Slaný committed
442
void Files::vacuumFileDbs(void)
443
{
444
	debugFuncCall();
445

446
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
447 448
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
449 450 451 452
		Q_ASSERT(0);
		return;
	}

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

455
	QStringList userNameList(GlobInstcs::acntMapPtr->keys());
456
	foreach (const QString &userName, userNameList) {
457 458
		FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
		    GlobInstcs::setPtr->dbsLocation, userName,
459
		    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
460
		if (fDb == Q_NULLPTR) {
461 462 463
			logErrorNL(
			    "Cannot access file database for username '%s'.",
			    userName.toUtf8().constData());
464 465
			return;
		}
466
		fDb->vacuum();
467 468
	}

Karel Slaný's avatar
Karel Slaný committed
469
	emit statusBarTextChanged(tr("Operation Vacuum has finished"), false);
470
}
471 472 473

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

476 477
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
	        (GlobInstcs::messageDbsPtr == Q_NULLPTR) ||
478 479
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
480 481 482 483
		Q_ASSERT(0);
		return false;
	}

484 485
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
486
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
487
	if (fDb == Q_NULLPTR) {
488 489
		logErrorNL("Cannot access file database for username '%s'.",
		    userName.toUtf8().constData());
490 491 492 493
		return false;
	}

	if (fDb->deleteFilesFromDb(msgId)) {
494 495
		MessageDb *msgDb = GlobInstcs::messageDbsPtr->accessMessageDb(
		    GlobInstcs::setPtr->dbsLocation, userName,
496
		    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
497
		if (msgDb == Q_NULLPTR) {
498
			logErrorNL("%s", "Cannot access message database.");
499 500 501 502 503 504 505
			return false;
		}
		return msgDb->setAttachmentDownloaded(msgId, false);
	}

	return false;
}
506

507 508 509 510
bool Files::fileReadable(const QString &filePath)
{
	if (filePath.isEmpty()) {
		Q_ASSERT(0);
511
		logErrorNL("%s", "Target ZFO path is empty.");
512 513 514 515 516 517
		return false;
	}

	{
		QFileInfo fileInfo(filePath);
		if (!fileInfo.isFile() || !fileInfo.isReadable()) {
518 519
			logErrorNL("Cannot open ZFO file '%s'.",
			    filePath.toUtf8().constData());
520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540
			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);
541 542
		logErrorNL("Cannot open file '%s'.",
		    filePath.toUtf8().constData());
543 544 545 546 547 548 549 550
		return QByteArray();
	}

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

551 552
MsgInfo *Files::zfoData(const QVariant &attachModelVariant,
    const QByteArray &rawZfoData)
553
{
554
	enum MsgInfo::ZfoType type = MsgInfo::TYPE_UNKNOWN;
555
	QString idStr, annot, htmlDescr, emailBody;
556

557
	bool ret = parseXmlData(&type, &idStr, &annot, &htmlDescr,
558
	    FileListModel::fromVariant(attachModelVariant),
559
	    &emailBody, Xml::getXmlFromCms(rawZfoData));
560

561
	return ret ?
562
	    new (std::nothrow) MsgInfo(type, idStr, annot, htmlDescr,
563
	        emailBody) :
564
	    new (std::nothrow) MsgInfo();
565 566 567
}

bool Files::setAttachmentModel(FileListModel &attachModel,
568
    const QString &userName, qint64 msgId)
569
{
570
	debugFuncCall();
571

572
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
573 574
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
575 576 577 578
		Q_ASSERT(0);
		return false;
	}

579 580
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
581
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
582 583

	if (fDb == Q_NULLPTR) {
584
		logErrorNL("%s", "Cannot access file database.");
585 586 587
		return false;
	}
	attachModel.clearAll();
588
	fDb->setFileModelFromDb(attachModel, msgId);
589 590 591 592 593 594
	return true;
}

void Files::sendAttachmentEmailZfo(const QVariant &attachModelVariant,
    const QString &msgIdStr, QString subject, QString body)
{
595
	debugFuncCall();
596 597

	/* Obtain pointer to attachment model. */
598
	const FileListModel *attachModel =
599
	    FileListModel::fromVariant(attachModelVariant);
600 601 602 603 604 605 606 607 608 609 610 611 612 613
	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;

614
	const QString boundary = generateBoundaryString();
615 616
	QString emailMessage = createEmailMessage(QString(), body, subject,
	    boundary);
617

618 619
	removeDirFromDocLoc(DATOVKA_MAIL_DIR_NAME);
	QString targetPath(appEmailDirPath(msgIdStr));
620 621 622 623

	for (int row = 0; row < attachModel->rowCount(); ++row) {
		QModelIndex idx(attachModel->index(row));
		/*
624 625
		 * On Android the attachment must be saved and then explicitly
		 * added into the email message.
626
		 */
627 628
		QByteArray binaryData(
		    attachModel->data(idx, FileListModel::ROLE_BINARY_DATA).toByteArray());
629 630
		QString attachName(attachModel->data(idx,
		    FileListModel::ROLE_FILE_NAME).toString());
631
		QString filePath(writeFile(targetPath, attachName, binaryData));
632 633
		fileList.append(filePath);
		addAttachmentToEmailMessage(emailMessage, attachName,
634
		    binaryData.toBase64(), boundary);
635 636 637 638
	}

	finishEmailMessage(emailMessage, boundary);

639 640
	sendEmail(emailMessage, fileList, QString(), subject, body,
	    QString::number(msgId));
641 642
}

643 644
void Files::saveMsgFilesToDisk(const QString &userName,
    const QString &msgIdStr, MsgAttachFlags attachFlags)
645
{
646
	debugFuncCall();
647

648
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
649
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
650
	        (GlobInstcs::zfoDbPtr == Q_NULLPTR) ||
651
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
652 653 654 655
		Q_ASSERT(0);
		return;
	}

656 657 658 659 660 661 662 663 664 665 666 667 668
	/* 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;
	}

669
	QList<Isds::Document> documents;
670

671 672 673 674 675 676
	/* 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) {
677
			logErrorNL("Cannot access file database for '%s'.",
678 679 680 681
			    userName.toUtf8().constData());
			return;
		}

682 683
		documents = fDb->getFilesFromDb(msgId);
		if (documents.isEmpty()) {
684
			logErrorNL("Missing attachments for message '%s'.",
685 686 687
			    QString::number(msgId).toUtf8().constData());
			return;
		}
Martin Straka's avatar
Martin Straka committed
688
	}
689

690 691
	/* Get zfo file from database if needed */
	if (attachFlags & MSG_ZFO) {
692 693 694
		Isds::Document document;
		document.setBase64Content(GlobInstcs::zfoDbPtr->getZfoContentFromDb(
		    msgId, (*GlobInstcs::acntMapPtr)[userName].isTestAccount()));
695
		if (document.binaryContent().isEmpty()) {
696
			logErrorNL("Missing zfo data for message '%s'.",
697 698 699 700 701 702 703 704
			    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;
		}
705 706
		document.setFileDescr(QString("DZ_%1.zfo").arg(msgId));
		documents.append(document);
707 708
	}

709
	QString filePath(appMsgAttachDirPath(msgIdStr));
710

711 712
	QString destFilePath;
	QStringList destFilePaths;
713
	foreach (const Isds::Document &document, documents) {
714
		destFilePath = writeFile(filePath, document.fileDescr(),
715
		    document.binaryContent());
716 717 718
		if (!destFilePath.isEmpty()) {
			destFilePaths.append(destFilePath);
		}
719 720
	}

721 722 723 724 725 726
#ifndef Q_OS_IOS

	attachmentSavingNotification(destFilePath);

#else

727
	ICloudHelper::storeFilesToCloud(destFilePaths, joinDirs(userName,
728 729 730
	    msgIdStr));

#endif
731 732 733 734 735
}

void Files::saveAttachmentsToDiskZfo(const QVariant &attachModelVariant,
    const QString &msgIdStr)
{
736
	debugFuncCall();
737 738 739

	/* Obtain pointer to attachment model. */
	const FileListModel *attachModel =
740
	    FileListModel::fromVariant(attachModelVariant);
741 742 743 744 745
	if (attachModel == Q_NULLPTR) {
		Q_ASSERT(0);
		return;
	}

746
	QString targetPath(appMsgAttachDirPath(msgIdStr));
747

748 749
	QString destFilePath;
	QStringList destFilePaths;
750 751
	for (int row = 0; row < attachModel->rowCount(); ++row) {
		QModelIndex idx(attachModel->index(row));
752
		destFilePath = writeFile(targetPath, attachModel->data(idx,
753
		    FileListModel::ROLE_FILE_NAME).toString(),
754 755
		    attachModel->data(idx,
		        FileListModel::ROLE_BINARY_DATA).toByteArray());
756 757 758
		if (!destFilePath.isEmpty()) {
			destFilePaths.append(destFilePath);
		}
759 760
	}

761 762 763 764 765 766
#ifndef Q_OS_IOS

	attachmentSavingNotification(destFilePath);

#else

767
	ICloudHelper::storeFilesToCloud(destFilePaths, msgIdStr);
768 769

#endif
770 771
}

772 773 774 775 776 777 778
void Files::deleteTmpFileFromStorage(const QString &filePath)
{
#if defined Q_OS_IOS
	QFile file(filePath);
	file.remove();
#else
	Q_UNUSED(filePath);
779
#endif
780 781
}

782 783 784 785 786 787 788 789 790
bool Files::isIos(void)
{
#ifdef Q_OS_IOS
	return true;
#else
	return false;
#endif
}

791
bool Files::parseXmlData(enum MsgInfo::ZfoType *type, QString *idStr,
792 793
    QString *annotation, QString *msgDescrHtml, FileListModel *attachModel,
    QString *emailBody, QByteArray xmlData)
794
{
795
	debugFuncCall();
796

797
	if (xmlData.isEmpty()) {
798
		logErrorNL("%s", "XML content is empty.");
799
		return false;
800 801
	}

802
	/* Test if zfo is message, delivery info or unknown format */
803
	if (xmlData.contains(QByteArray("MessageDownloadResponse"))) {
804 805
		if (type != Q_NULLPTR) {
			*type = MsgInfo::TYPE_MESSAGE;
806
		}
807 808
		return parseAndShowXmlData(MsgInfo::TYPE_MESSAGE, idStr,
		    annotation, msgDescrHtml, attachModel, emailBody, xmlData);
809
	} else if (xmlData.contains(QByteArray("GetDeliveryInfoResponse"))) {
810 811
		if (type != Q_NULLPTR) {
			*type = MsgInfo::TYPE_DELIVERY_INFO;
812
		}
813 814
		return parseAndShowXmlData(MsgInfo::TYPE_DELIVERY_INFO, idStr,
		    annotation, msgDescrHtml, attachModel, emailBody, xmlData);
815
	} else {
816 817 818
		if (type != Q_NULLPTR) {
			*type = MsgInfo::TYPE_UNKNOWN;
		}
819
		logErrorNL("%s", "Unknown ZFO format.");
820 821
	}

822
	return false;
823 824
}

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

831 832 833 834 835
	if (type == MsgInfo::TYPE_UNKNOWN) {
		Q_ASSERT(0);
		return false;
	}

836 837
	Isds::Message message = Xml::parseCompleteMessage(xmlData);
	QList<Isds::Event> events;
838

839
	if (type == MsgInfo::TYPE_DELIVERY_INFO) {
840
		events = Xml::parseDeliveryInfo(xmlData);
841 842 843 844
	}

	QString html = divStart;

845
	html += "<h3>" + tr("General") + "</h3>";
846 847
	html += strongInfoLine(tr("Subject"), message.envelope().dmAnnotation());
	QString size = QString::number(message.envelope().dmAttachmentSize());
848
	html += strongInfoLine(tr("Attachment size"),
849
	    (size == "0") ? "&lt;1 kB" : "~" + size + " kB");
850
	html += strongInfoLine(tr("Personal delivery"),
851
	    (message.envelope().dmPersonalDelivery()) ? tr("Yes") : tr("No"));
852
	html += strongInfoLine(tr("Delivery by fiction"),
853
	    (message.envelope().dmAllowSubstDelivery()) ? tr("Yes") : tr("No"));
854 855

	html += "<h3>" + tr("Sender") + "</h3>";
856 857 858 859 860 861
	html += strongInfoLine(tr("Databox ID"),
	    message.envelope().dbIDSender());
	html += strongInfoLine(tr("Name"),
	    message.envelope().dmSender());
	html += strongInfoLine(tr("Address"),
	    message.envelope().dmSenderAddress());
862 863

	html += "<h3>" + tr("Recipient") + "</h3>";
864 865 866 867 868 869 870 871
	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());
872
	}
873

874
	QString tmpHtml;
875
	if (!message.envelope().dmSenderIdent().isEmpty()) {
876
		tmpHtml += strongInfoLine(tr("Our file mark"),
877
		    message.envelope().dmSenderIdent());
878
	}
879
	if (!message.envelope().dmSenderRefNumber().isEmpty()) {
880
		tmpHtml += strongInfoLine(tr("Our reference number"),
881
		    message.envelope().dmSenderRefNumber());
882
	}
883
	if (!message.envelope().dmRecipientIdent().isEmpty()) {
884
		tmpHtml += strongInfoLine(tr("Your file mark"),
885
		    message.envelope().dmRecipientIdent());
886
	}
887
	if (!message.envelope().dmRecipientRefNumber().isEmpty()) {
888
		tmpHtml += strongInfoLine(tr("Your reference number"),
889
		    message.envelope().dmRecipientRefNumber());
890
	}
891 892 893
	if (!message.envelope().dmLegalTitleLawStr().isEmpty()) {
		tmpHtml += strongInfoLine(tr("Law"),
		    message.envelope().dmLegalTitleLawStr());
894
	}
895 896 897
	if (!message.envelope().dmLegalTitleYearStr().isEmpty()) {
		tmpHtml += strongInfoLine(tr("Year"),
		    message.envelope().dmLegalTitleYearStr());
898
	}
899 900 901
	if (!message.envelope().dmLegalTitleSect().isEmpty()) {
		tmpHtml += strongInfoLine(tr("Section"),
		    message.envelope().dmLegalTitleSect());
902
	}
903
	if (!message.envelope().dmLegalTitlePar().isEmpty()) {
904
		tmpHtml += strongInfoLine(tr("Paragraph"),
905
		    message.envelope().dmLegalTitlePar());
906
	}
907 908 909
	if (!message.envelope().dmLegalTitlePoint().isEmpty()) {
		tmpHtml += strongInfoLine(tr("Letter"),
		    message.envelope().dmLegalTitlePoint());
910 911
	}
	if (!tmpHtml.isEmpty()) {
912
		html += "<h3>" + tr("Additional info") + "</h3>";
913 914
		html += tmpHtml;
	}
915

916 917
	html += "<h3>" + tr("Message state") + "</h3>";
	html += strongInfoLine(tr("Delivery time"),
918
	    dateTimeStrFromDbFormat(
919
	        utcDateTimeToDbFormatStr(message.envelope().dmDeliveryTime()),
920
	        DATETIME_QML_FORMAT));
921
	html += strongInfoLine(tr("Accetance time"),
922
	    dateTimeStrFromDbFormat(
923
	        utcDateTimeToDbFormatStr(message.envelope().dmAcceptanceTime()),
924
	        DATETIME_QML_FORMAT));
925
	html += strongInfoLine(tr("Status"),
926
	    QString::number(message.envelope().dmMessageStatus()));
927

928
	if (type == MsgInfo::TYPE_DELIVERY_INFO) {
929
		html += "<h3>" + tr("Events") + "</h3>";
930
		foreach (const Isds::Event &event, events) {
931 932
			html += divStart +
			    strongInfoLine(dateTimeStrFromDbFormat(
933 934
			        utcDateTimeToDbFormatStr(event.time()),
			        DATETIME_QML_FORMAT), event.descr())
935 936 937 938
			    + divEnd;
		}
	}

939 940
	html += divEnd;

941
	// Create body for email
942 943 944 945
	QString body = generateEmailBodyText(message.envelope().dmId(),
	    message.envelope().dmSender(), message.envelope().dmRecipient(),
	    dateTimeStrFromDbFormat(
	    utcDateTimeToDbFormatStr(message.envelope().dmAcceptanceTime()),
946
	    DATETIME_QML_FORMAT));
947

948
	if (idStr != Q_NULLPTR) {
949
		*idStr = QString::number(message.envelope().dmId());
950
	}
951
	if (annotation != Q_NULLPTR) {
952
		*annotation = message.envelope().dmAnnotation();
953 954 955 956 957
	}
	if (msgDescrHtml != Q_NULLPTR) {
		*msgDescrHtml = html;
	}
	if (attachModel != Q_NULLPTR) {
958
		attachModel->clearAll();
959
		foreach (const Isds::Document &document, message.documents()) {
Martin Straka's avatar
Martin Straka committed
960
			attachModel->appendFileEntry(
961
			    FileListModel::Entry(-1, document.fileDescr(),
962
			    document.binaryContent(), document.binaryContent().size(),
963
			    QString()));
964
		}
965 966 967 968
	}
	if (emailBody != Q_NULLPTR) {
		*emailBody = body;
	}
969

970 971
	return true;
}