files.cpp 28.5 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>
27
#include <QMessageBox>
Martin Straka's avatar
Martin Straka committed
28
#include <QQmlEngine>
29
#include <QStringBuilder>
30

31 32 33
#if defined (Q_OS_ANDROID)
#include "android/src/android_io.h"
#endif
Martin Straka's avatar
Martin Straka committed
34
#include "ios/src/url_opener.h"
35
#include "src/auxiliaries/email_helper.h"
Martin Straka's avatar
Martin Straka committed
36
#include "src/auxiliaries/ios_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 645 646 647 648 649 650 651 652 653 654 655
#ifdef Q_OS_IOS
static
void exportFilesiOS(const QStringList &destFilePaths,
    const QString &targetDir)
{
	QMessageBox msgBox;
	msgBox.setText(QObject::tr("You can export files into iCloud or into device local storage."));
	msgBox.setDetailedText(QObject::tr("Do you want to export files to Datovka iCloud container?"));
	msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
	msgBox.setDefaultButton(QMessageBox::No);
	int ret = msgBox.exec();

	if (ret == QMessageBox::Yes) {
Martin Straka's avatar
Martin Straka committed
656
		GlobInstcs::iOSHelperPtr->storeFilesToCloud(destFilePaths, targetDir);
657
	} else if (ret == QMessageBox::No) {
Martin Straka's avatar
Martin Straka committed
658
		GlobInstcs::iOSHelperPtr->storeFilesToDeviceStorage(destFilePaths);
659 660 661 662
	}
}
#endif

663 664
void Files::saveMsgFilesToDisk(const QString &userName,
    const QString &msgIdStr, MsgAttachFlags attachFlags)
665
{
666
	debugFuncCall();
667

668
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
669
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
670
	        (GlobInstcs::zfoDbPtr == Q_NULLPTR) ||
671
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
672 673 674 675
		Q_ASSERT(0);
		return;
	}

676 677 678 679 680 681 682 683 684 685 686 687 688
	/* 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;
	}

689
	QList<Isds::Document> documents;
690

691 692 693 694 695 696
	/* 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) {
697
			logErrorNL("Cannot access file database for '%s'.",
698 699 700 701
			    userName.toUtf8().constData());
			return;
		}

702 703
		documents = fDb->getFilesFromDb(msgId);
		if (documents.isEmpty()) {
704
			logErrorNL("Missing attachments for message '%s'.",
705 706 707
			    QString::number(msgId).toUtf8().constData());
			return;
		}
Martin Straka's avatar
Martin Straka committed
708
	}
709

710 711
	/* Get zfo file from database if needed */
	if (attachFlags & MSG_ZFO) {
712 713 714
		Isds::Document document;
		document.setBase64Content(GlobInstcs::zfoDbPtr->getZfoContentFromDb(
		    msgId, (*GlobInstcs::acntMapPtr)[userName].isTestAccount()));
715
		if (document.binaryContent().isEmpty()) {
716
			logErrorNL("Missing zfo data for message '%s'.",
717 718 719 720 721 722 723 724
			    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;
		}
725 726
		document.setFileDescr(QString("DZ_%1.zfo").arg(msgId));
		documents.append(document);
727 728
	}

729 730 731 732 733 734 735 736 737 738
	QString targetPath;

#ifdef Q_OS_IOS

	targetPath = appMsgAttachDirPathiOS(msgIdStr);

#else
	targetPath = appMsgAttachDirPath(msgIdStr);

#endif
739

740 741
	QString destFilePath;
	QStringList destFilePaths;
742
	foreach (const Isds::Document &document, documents) {
743
		destFilePath = writeFile(targetPath, document.fileDescr(),
744
		    document.binaryContent());
745 746 747
		if (!destFilePath.isEmpty()) {
			destFilePaths.append(destFilePath);
		}
748 749
	}

750 751 752 753 754 755
#ifndef Q_OS_IOS

	attachmentSavingNotification(destFilePath);

#else

756
	exportFilesiOS(destFilePaths, joinDirs(userName, msgIdStr));
757 758

#endif
759 760 761 762 763
}

void Files::saveAttachmentsToDiskZfo(const QVariant &attachModelVariant,
    const QString &msgIdStr)
{
764
	debugFuncCall();
765 766 767

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

774 775 776 777 778 779 780 781 782 783
	QString targetPath;

#ifdef Q_OS_IOS

	targetPath = appMsgAttachDirPathiOS(msgIdStr);

#else
	targetPath = appMsgAttachDirPath(msgIdStr);

#endif
784

785 786
	QString destFilePath;
	QStringList destFilePaths;
787 788
	for (int row = 0; row < attachModel->rowCount(); ++row) {
		QModelIndex idx(attachModel->index(row));
789
		destFilePath = writeFile(targetPath, attachModel->data(idx,
790
		    FileListModel::ROLE_FILE_NAME).toString(),
791 792
		    attachModel->data(idx,
		        FileListModel::ROLE_BINARY_DATA).toByteArray());
793 794 795
		if (!destFilePath.isEmpty()) {
			destFilePaths.append(destFilePath);
		}
796 797
	}

798 799 800 801 802 803
#ifndef Q_OS_IOS

	attachmentSavingNotification(destFilePath);

#else

804
	exportFilesiOS(destFilePaths, msgIdStr);
805 806

#endif
807 808
}

809 810 811 812 813 814 815
void Files::deleteTmpFileFromStorage(const QString &filePath)
{
#if defined Q_OS_IOS
	QFile file(filePath);
	file.remove();
#else
	Q_UNUSED(filePath);
816
#endif
817 818
}

819 820 821 822 823 824 825 826 827
bool Files::isIos(void)
{
#ifdef Q_OS_IOS
	return true;
#else
	return false;
#endif
}

828
bool Files::parseXmlData(enum MsgInfo::ZfoType *type, QString *idStr,
829 830
    QString *annotation, QString *msgDescrHtml, FileListModel *attachModel,
    QString *emailBody, QByteArray xmlData)
831
{
832
	debugFuncCall();
833

834
	if (xmlData.isEmpty()) {
835
		logErrorNL("%s", "XML content is empty.");
836
		return false;
837 838
	}

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

859
	return false;
860 861
}

862
bool Files::parseAndShowXmlData(enum MsgInfo::ZfoType type, QString *idStr,
863 864
    QString *annotation, QString *msgDescrHtml, FileListModel *attachModel,
    QString *emailBody, QByteArray &xmlData)
865
{
866
	debugFuncCall();
867

868 869 870 871 872
	if (type == MsgInfo::TYPE_UNKNOWN) {
		Q_ASSERT(0);
		return false;
	}

873 874
	Isds::Message message = Xml::parseCompleteMessage(xmlData);
	QList<Isds::Event> events;
875

876
	if (type == MsgInfo::TYPE_DELIVERY_INFO) {
877
		events = Xml::parseDeliveryInfo(xmlData);
878 879 880 881
	}

	QString html = divStart;

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

	html += "<h3>" + tr("Sender") + "</h3>";
893 894 895 896 897 898
	html += strongInfoLine(tr("Databox ID"),
	    message.envelope().dbIDSender());
	html += strongInfoLine(tr("Name"),
	    message.envelope().dmSender());
	html += strongInfoLine(tr("Address"),
	    message.envelope().dmSenderAddress());
899 900

	html += "<h3>" + tr("Recipient") + "</h3>";
901 902 903 904 905 906 907 908
	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());
909
	}
910

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

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

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

976 977
	html += divEnd;

978
	// Create body for email
979 980 981 982
	QString body = generateEmailBodyText(message.envelope().dmId(),
	    message.envelope().dmSender(), message.envelope().dmRecipient(),
	    dateTimeStrFromDbFormat(
	    utcDateTimeToDbFormatStr(message.envelope().dmAcceptanceTime()),
983
	    DATETIME_QML_FORMAT));
984

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

1007 1008
	return true;
}