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

#include <QDesktopServices>
25
#include <QFileInfo>
Martin Straka's avatar
Martin Straka committed
26
#include <QQmlEngine>
27
#include <QStringBuilder>
28

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

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

56 57
Files::Files(QObject *parent)
    : QObject(parent)
58 59 60
{
}

61
void Files::attachmentSavingNotification(const QString &destPath)
62
{
63 64 65 66
	QFileInfo fi(destPath);

	Dialogues::errorMessage(
	    !destPath.isEmpty() ? Dialogues::INFORMATION : Dialogues::CRITICAL,
67 68 69 70 71
	    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());
72 73 74 75
}

void Files::deleteExpiredFilesFromDbs(int days)
{
76
	debugFuncCall();
77

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

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

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

119 120
QString Files::getAttachmentFileIcon(const QString &fileName)
{
121 122 123 124 125 126 127 128 129 130 131 132
	/* 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");
133 134 135 136 137 138 139 140
}

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

141
QByteArray Files::getFileRawContentFromDb(const QString &userName, int fileId)
142
{
143
	debugFuncCall();
144

145
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
146 147
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
148 149 150 151
		Q_ASSERT(0);
		return QByteArray();
	}

152 153
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
154
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
155

156
	if (fDb == Q_NULLPTR) {
157 158
		logErrorNL("Cannot access file database for username '%s'.",
		    userName.toUtf8().constData());
159 160 161
		return QByteArray();
	}

162
	return fDb->getFileFromDb(fileId).binaryContent();
163 164
}

165
void Files::openAttachmentFromDb(const QString &userName, int fileId)
166
{
167
	debugFuncCall();
168

169
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
170 171
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
172 173 174 175
		Q_ASSERT(0);
		return;
	}

176 177
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
178
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
179

180
	if (fDb == Q_NULLPTR) {
181 182
		logErrorNL("Cannot access file database for username '%s'.",
		    userName.toUtf8().constData());
183 184 185
		return;
	}

186
	Isds::Document document(fDb->getFileFromDb(fileId));
187

188
	openAttachment(document.fileDescr(), document.binaryContent());
189 190
}

191
void Files::openAttachment(const QString &fileName,
192
    const QByteArray &binaryData)
193
{
194
	if (Q_UNLIKELY(fileName.isEmpty() || binaryData.isEmpty())) {
195
		logErrorNL("%s", "File name or its content is empty.");
196
		Q_ASSERT(0);
197 198 199
		return;
	}

200 201
	if (isZfoFile(fileName)) {
		/* Don't open zfo files from here. */
202
		logErrorNL("%s", "The application should open ZFO files by itself.");
203
		Q_ASSERT(0);
204 205 206
		return;
	}

207
	QString filePath(writeFile(appTmpDirPath(), fileName, binaryData));
208 209

	if (!filePath.isEmpty()) {
210 211
		logInfoNL("Creating temporary file '%s'.",
		    filePath.toUtf8().constData());
212 213
		openAttachmentFromPath(filePath);
	} else {
214 215
		logErrorNL("Cannot create temporary file for '%s'.",
		    fileName.toUtf8().constData());
216 217 218 219 220 221 222 223 224 225
		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()) {
226
		logErrorNL("%s", "File path is empty.");
227
		Q_ASSERT(0);
228 229 230 231 232
		return;
	}

	if (isZfoFile(filePath)) {
		/* Don't open zfo files from here. */
233
		logErrorNL("%s", "The application should open ZFO files by itself.");
234
		Q_ASSERT(0);
235 236
		return;
	}
237

238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
#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
253 254
}

255
void Files::sendMsgFilesWithEmail(const QString &userName, qint64 msgId,
256
    MsgAttachFlags attachFlags)
257
{
258
	debugFuncCall();
259

260 261
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
	        (GlobInstcs::messageDbsPtr == Q_NULLPTR) ||
262
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
263
	        (GlobInstcs::zfoDbPtr == Q_NULLPTR) ||
264
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
265 266 267 268
		Q_ASSERT(0);
		return;
	}

269 270 271 272 273 274 275
	if (userName.isEmpty() || msgId <= 0) {
		return;
	}

	QString body;
	QString subject;

276
	/* Fill email subject and email body */
277 278
	MessageDb *msgDb = GlobInstcs::messageDbsPtr->accessMessageDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
279
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
280
	if (msgDb == Q_NULLPTR) {
281
		logErrorNL("Cannot access message database for '%s'.",
Karel Slaný's avatar
Karel Slaný committed
282
		    userName.toUtf8().constData());
283 284
		return;
	}
285
	if (!msgDb->getMessageEmailDataFromDb(msgId, body, subject)) {
286
		logErrorNL("Missing data of message '%s'.",
Karel Slaný's avatar
Karel Slaný committed
287
		    QString::number(msgId).toUtf8().constData());
288 289 290
		return;
	}

291
	QList<Isds::Document> documents;
292 293

	/* Get attachment files from database if needed */
294
	if (attachFlags & MSG_ATTACHS) {
295 296 297 298
		FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
		    GlobInstcs::setPtr->dbsLocation, userName,
		    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
		if (fDb == Q_NULLPTR) {
299
			logErrorNL("Cannot access file database for '%s'.",
Karel Slaný's avatar
Karel Slaný committed
300
			    userName.toUtf8().constData());
301 302 303
			return;
		}

304 305
		documents = fDb->getFilesFromDb(msgId);
		if (documents.isEmpty()) {
306
			logErrorNL("Missing attachments for message '%s'.",
Karel Slaný's avatar
Karel Slaný committed
307
			    QString::number(msgId).toUtf8().constData());
308 309
			return;
		}
310 311
	}

312
	/* Get zfo file from database if needed */
313
	if (attachFlags & MSG_ZFO) {
314 315 316
		Isds::Document document;
		document.setBase64Content(GlobInstcs::zfoDbPtr->getZfoContentFromDb(
		    msgId, (*GlobInstcs::acntMapPtr)[userName].isTestAccount()));
317
		if (document.binaryContent().isEmpty()) {
318
			logErrorNL("Missing zfo data for message '%s'.",
Karel Slaný's avatar
Karel Slaný committed
319
			    QString::number(msgId).toUtf8().constData());
320 321 322 323
			Dialogues::errorMessage(Dialogues::CRITICAL,
			    tr("ZFO missing"),
			    tr("ZFO message is not present in local database."),
			    tr("Download complete message again to obtain it."));
324 325 326
			Q_ASSERT(0);
			return;
		}
327 328
		document.setFileDescr(QString("DZ_%1.zfo").arg(msgId));
		documents.append(document);
329
	}
330

331
	/* Create email content, email attachment path, email eml content */
332 333
	removeDirFromDocLoc(DATOVKA_MAIL_DIR_NAME);
	QString targetPath(appEmailDirPath(QString::number(msgId)));
334
	const QString boundary = generateBoundaryString();
335 336
	QString emailMessage = createEmailMessage(QString(), body, subject,
	    boundary);
337
	QStringList filePathList;
338

339
	/* Write attachment files to email directory */
340 341
	foreach (const Isds::Document &document, documents) {
		QString fileName = document.fileDescr();
342
		if (fileName.isEmpty()) {
343
			logErrorNL("%s", "File name is empty.");
344 345
			return;
		}
346
		fileName = writeFile(targetPath, fileName,
347
		    document.binaryContent());
348
		filePathList.append(fileName);
349 350
		addAttachmentToEmailMessage(emailMessage, document.fileDescr(),
		    document.base64Content().toUtf8(), boundary);
351 352 353 354
	}

	finishEmailMessage(emailMessage, boundary);

355
	/* Send email */
356 357
	sendEmail(emailMessage, filePathList, QString(), subject, body,
	    QString::number(msgId));
358
}
359 360 361

void Files::deleteFileDb(const QString &userName)
{
362
	debugFuncCall();
363

364 365
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
	        (GlobInstcs::messageDbsPtr == Q_NULLPTR) ||
366 367
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
368 369 370 371
		Q_ASSERT(0);
		return;
	}

372
	int msgResponse = Dialogues::message(Dialogues::QUESTION,
373 374 375
	    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."),
376 377
	    Dialogues::NO | Dialogues::YES, Dialogues::NO);
	if (msgResponse == Dialogues::NO) {
378 379 380
		return;
	}

381 382
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
383
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
384
	if (fDb == Q_NULLPTR) {
385
		logErrorNL("%s", "Cannot access file database.");
386 387
		return;
	}
388
	if (!GlobInstcs::fileDbsPtr->deleteDb(fDb)) {
389
		logErrorNL("%s", "Cannot delete file database.");
390 391 392
		return;
	}

393 394
	MessageDb *msgDb = GlobInstcs::messageDbsPtr->accessMessageDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
395
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
396
	if (msgDb == Q_NULLPTR) {
397
		logErrorNL("%s", "Cannot access message database.");
398 399 400
		return;
	}
	if (!msgDb->setAttachmentsDownloaded(false)) {
401
		logErrorNL("%s", "Message data missing.");
402 403 404
		return;
	}
}
405

Karel Slaný's avatar
Karel Slaný committed
406
void Files::vacuumFileDbs(void)
407
{
408
	debugFuncCall();
409

410
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
411 412
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
413 414 415 416
		Q_ASSERT(0);
		return;
	}

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

419
	QStringList userNameList(GlobInstcs::acntMapPtr->keys());
420
	foreach (const QString &userName, userNameList) {
421 422
		FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
		    GlobInstcs::setPtr->dbsLocation, userName,
423
		    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
424
		if (fDb == Q_NULLPTR) {
425 426 427
			logErrorNL(
			    "Cannot access file database for username '%s'.",
			    userName.toUtf8().constData());
428 429
			return;
		}
430
		fDb->vacuum();
431 432
	}

Karel Slaný's avatar
Karel Slaný committed
433
	emit statusBarTextChanged(tr("Operation Vacuum has finished"), false);
434
}
435 436 437

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

440 441
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
	        (GlobInstcs::messageDbsPtr == Q_NULLPTR) ||
442 443
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
444 445 446 447
		Q_ASSERT(0);
		return false;
	}

448 449
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
450
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
451
	if (fDb == Q_NULLPTR) {
452 453
		logErrorNL("Cannot access file database for username '%s'.",
		    userName.toUtf8().constData());
454 455 456 457
		return false;
	}

	if (fDb->deleteFilesFromDb(msgId)) {
458 459
		MessageDb *msgDb = GlobInstcs::messageDbsPtr->accessMessageDb(
		    GlobInstcs::setPtr->dbsLocation, userName,
460
		    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
Martin Straka's avatar
Martin Straka committed
461
		if (msgDb == Q_NULLPTR) {
462
			logErrorNL("%s", "Cannot access message database.");
463 464 465 466 467 468 469
			return false;
		}
		return msgDb->setAttachmentDownloaded(msgId, false);
	}

	return false;
}
470

471 472 473 474
bool Files::fileReadable(const QString &filePath)
{
	if (filePath.isEmpty()) {
		Q_ASSERT(0);
475
		logErrorNL("%s", "Target ZFO path is empty.");
476 477 478 479 480 481
		return false;
	}

	{
		QFileInfo fileInfo(filePath);
		if (!fileInfo.isFile() || !fileInfo.isReadable()) {
482 483
			logErrorNL("Cannot open ZFO file '%s'.",
			    filePath.toUtf8().constData());
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504
			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);
505 506
		logErrorNL("Cannot open file '%s'.",
		    filePath.toUtf8().constData());
507 508 509 510 511 512 513 514
		return QByteArray();
	}

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

515 516
MsgInfo *Files::zfoData(const QVariant &attachModelVariant,
    const QByteArray &rawZfoData)
517
{
518
	enum MsgInfo::ZfoType type = MsgInfo::TYPE_UNKNOWN;
519
	QString idStr, annot, htmlDescr, emailBody;
520

521
	bool ret = parseXmlData(&type, &idStr, &annot, &htmlDescr,
522
	    FileListModel::fromVariant(attachModelVariant),
523
	    &emailBody, Xml::getXmlFromCms(rawZfoData));
524

525
	return ret ?
526
	    new (std::nothrow) MsgInfo(type, idStr, annot, htmlDescr,
527
	        emailBody) :
528
	    new (std::nothrow) MsgInfo();
529 530 531
}

bool Files::setAttachmentModel(FileListModel &attachModel,
532
    const QString &userName, qint64 msgId)
533
{
534
	debugFuncCall();
535

536
	if (Q_UNLIKELY((GlobInstcs::setPtr == Q_NULLPTR) ||
537 538
	        (GlobInstcs::fileDbsPtr == Q_NULLPTR) ||
	        (GlobInstcs::acntMapPtr == Q_NULLPTR))) {
539 540 541 542
		Q_ASSERT(0);
		return false;
	}

543 544
	FileDb *fDb = GlobInstcs::fileDbsPtr->accessFileDb(
	    GlobInstcs::setPtr->dbsLocation, userName,
545
	    (*GlobInstcs::acntMapPtr)[userName].storeToDisk());
546 547

	if (fDb == Q_NULLPTR) {
548
		logErrorNL("%s", "Cannot access file database.");
549 550 551
		return false;
	}
	attachModel.clearAll();
552
	fDb->setFileModelFromDb(attachModel, msgId);
553 554 555 556 557 558
	return true;
}

void Files::sendAttachmentEmailZfo(const QVariant &attachModelVariant,
    const QString &msgIdStr, QString subject, QString body)
{
559
	debugFuncCall();
560 561

	/* Obtain pointer to attachment model. */
562
	const FileListModel *attachModel =
563
	    FileListModel::fromVariant(attachModelVariant);
564 565 566 567 568 569 570 571 572 573 574 575 576 577
	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;

578
	const QString boundary = generateBoundaryString();
579 580
	QString emailMessage = createEmailMessage(QString(), body, subject,
	    boundary);
581

582 583
	removeDirFromDocLoc(DATOVKA_MAIL_DIR_NAME);
	QString targetPath(appEmailDirPath(msgIdStr));
584 585 586 587

	for (int row = 0; row < attachModel->rowCount(); ++row) {
		QModelIndex idx(attachModel->index(row));
		/*
588 589
		 * On Android the attachment must be saved and then explicitly
		 * added into the email message.
590
		 */
591 592
		QByteArray binaryData(
		    attachModel->data(idx, FileListModel::ROLE_BINARY_DATA).toByteArray());
593 594
		QString attachName(attachModel->data(idx,
		    FileListModel::ROLE_FILE_NAME).toString());
595
		QString filePath(writeFile(targetPath, attachName, binaryData));
596 597
		fileList.append(filePath);
		addAttachmentToEmailMessage(emailMessage, attachName,
598
		    binaryData.toBase64(), boundary);
599 600 601 602
	}

	finishEmailMessage(emailMessage, boundary);

603 604
	sendEmail(emailMessage, fileList, QString(), subject, body,
	    QString::number(msgId));
605 606
}

607 608
void Files::saveMsgFilesToDisk(const QString &userName,
    const QString &msgIdStr, MsgAttachFlags attachFlags)
609
{
610
	debugFuncCall();
611

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

620 621 622 623 624 625 626 627 628 629 630 631 632
	/* 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;
	}

633
	QList<Isds::Document> documents;
634

635 636 637 638 639 640
	/* 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) {
641
			logErrorNL("Cannot access file database for '%s'.",
642 643 644 645
			    userName.toUtf8().constData());
			return;
		}

646 647
		documents = fDb->getFilesFromDb(msgId);
		if (documents.isEmpty()) {
648
			logErrorNL("Missing attachments for message '%s'.",
649 650 651
			    QString::number(msgId).toUtf8().constData());
			return;
		}
Martin Straka's avatar
Martin Straka committed
652
	}
653

654 655
	/* Get zfo file from database if needed */
	if (attachFlags & MSG_ZFO) {
656 657 658
		Isds::Document document;
		document.setBase64Content(GlobInstcs::zfoDbPtr->getZfoContentFromDb(
		    msgId, (*GlobInstcs::acntMapPtr)[userName].isTestAccount()));
659
		if (document.binaryContent().isEmpty()) {
660
			logErrorNL("Missing zfo data for message '%s'.",
661 662 663 664 665 666 667 668
			    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;
		}
669 670
		document.setFileDescr(QString("DZ_%1.zfo").arg(msgId));
		documents.append(document);
671 672
	}

673
	QString filePath(appMsgAttachDirPath(msgIdStr));
674

675
	QString destPath;
676 677
	foreach (const Isds::Document &document, documents) {
		destPath = writeFile(filePath, document.fileDescr(),
678
		    document.binaryContent());
679 680 681 682 683 684 685 686
	}

	attachmentSavingNotification(destPath);
}

void Files::saveAttachmentsToDiskZfo(const QVariant &attachModelVariant,
    const QString &msgIdStr)
{
687
	debugFuncCall();
688 689 690

	/* Obtain pointer to attachment model. */
	const FileListModel *attachModel =
691
	    FileListModel::fromVariant(attachModelVariant);
692 693 694 695 696
	if (attachModel == Q_NULLPTR) {
		Q_ASSERT(0);
		return;
	}

697
	QString targetPath(appMsgAttachDirPath(msgIdStr));
698 699 700 701

	QString destPath;
	for (int row = 0; row < attachModel->rowCount(); ++row) {
		QModelIndex idx(attachModel->index(row));
702 703
		destPath = writeFile(targetPath, attachModel->data(idx,
		    FileListModel::ROLE_FILE_NAME).toString(),
704 705
		    attachModel->data(idx,
		        FileListModel::ROLE_BINARY_DATA).toByteArray());
706 707 708 709 710
	}

	attachmentSavingNotification(destPath);
}

711 712 713 714 715 716 717
void Files::deleteTmpFileFromStorage(const QString &filePath)
{
#if defined Q_OS_IOS
	QFile file(filePath);
	file.remove();
#else
	Q_UNUSED(filePath);
718
#endif
719 720
}

721
bool Files::parseXmlData(enum MsgInfo::ZfoType *type, QString *idStr,
722 723
    QString *annotation, QString *msgDescrHtml, FileListModel *attachModel,
    QString *emailBody, QByteArray xmlData)
724
{
725
	debugFuncCall();
726

727
	if (xmlData.isEmpty()) {
728
		logErrorNL("%s", "XML content is empty.");
729
		return false;
730 731
	}

732
	/* Test if zfo is message, delivery info or unknown format */
733
	if (xmlData.contains(QByteArray("MessageDownloadResponse"))) {
734 735
		if (type != Q_NULLPTR) {
			*type = MsgInfo::TYPE_MESSAGE;
736
		}
737 738
		return parseAndShowXmlData(MsgInfo::TYPE_MESSAGE, idStr,
		    annotation, msgDescrHtml, attachModel, emailBody, xmlData);
739
	} else if (xmlData.contains(QByteArray("GetDeliveryInfoResponse"))) {
740 741
		if (type != Q_NULLPTR) {
			*type = MsgInfo::TYPE_DELIVERY_INFO;
742
		}
743 744
		return parseAndShowXmlData(MsgInfo::TYPE_DELIVERY_INFO, idStr,
		    annotation, msgDescrHtml, attachModel, emailBody, xmlData);
745
	} else {
746 747 748
		if (type != Q_NULLPTR) {
			*type = MsgInfo::TYPE_UNKNOWN;
		}
749
		logErrorNL("%s", "Unknown ZFO format.");
750 751
	}

752
	return false;
753 754
}

755
bool Files::parseAndShowXmlData(enum MsgInfo::ZfoType type, QString *idStr,
756 757
    QString *annotation, QString *msgDescrHtml, FileListModel *attachModel,
    QString *emailBody, QByteArray &xmlData)
758
{
759
	debugFuncCall();
760

761 762 763 764 765
	if (type == MsgInfo::TYPE_UNKNOWN) {
		Q_ASSERT(0);
		return false;
	}

766 767
	Isds::Message message = Xml::parseCompleteMessage(xmlData);
	QList<Isds::Event> events;
768

769
	if (type == MsgInfo::TYPE_DELIVERY_INFO) {
770
		events = Xml::parseDeliveryInfo(xmlData);
771 772 773 774
	}

	QString html = divStart;

775
	html += "<h3>" + tr("General") + "</h3>";
776 777
	html += strongInfoLine(tr("Subject"), message.envelope().dmAnnotation());
	QString size = QString::number(message.envelope().dmAttachmentSize());
778
	html += strongInfoLine(tr("Attachment size"),
779
	    (size == "0") ? "&lt;1 kB" : "~" + size + " kB");
780
	html += strongInfoLine(tr("Personal delivery"),
781
	    (message.envelope().dmPersonalDelivery()) ? tr("Yes") : tr("No"));
782
	html += strongInfoLine(tr("Delivery by fiction"),
783
	    (message.envelope().dmAllowSubstDelivery()) ? tr("Yes") : tr("No"));
784 785

	html += "<h3>" + tr("Sender") + "</h3>";
786 787 788 789 790 791
	html += strongInfoLine(tr("Databox ID"),
	    message.envelope().dbIDSender());
	html += strongInfoLine(tr("Name"),
	    message.envelope().dmSender());
	html += strongInfoLine(tr("Address"),
	    message.envelope().dmSenderAddress());
792 793

	html += "<h3>" + tr("Recipient") + "</h3>";
794 795 796 797 798 799 800 801
	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());
802
	}
803

804
	QString tmpHtml;
805
	if (!message.envelope().dmSenderIdent().isEmpty()) {
806
		tmpHtml += strongInfoLine(tr("Our file mark"),
807
		    message.envelope().dmSenderIdent());
808
	}
809
	if (!message.envelope().dmSenderRefNumber().isEmpty()) {
810
		tmpHtml += strongInfoLine(tr("Our reference number"),
811
		    message.envelope().dmSenderRefNumber());
812
	}
813
	if (!message.envelope().dmRecipientIdent().isEmpty()) {
814
		tmpHtml += strongInfoLine(tr("Your file mark"),
815
		    message.envelope().dmRecipientIdent());
816
	}
817
	if (!message.envelope().dmRecipientRefNumber().isEmpty()) {
818
		tmpHtml += strongInfoLine(tr("Your reference number"),
819
		    message.envelope().dmRecipientRefNumber());
820
	}
821 822 823
	if (!message.envelope().dmLegalTitleLawStr().isEmpty()) {
		tmpHtml += strongInfoLine(tr("Law"),
		    message.envelope().dmLegalTitleLawStr());
824
	}
825 826 827
	if (!message.envelope().dmLegalTitleYearStr().isEmpty()) {
		tmpHtml += strongInfoLine(tr("Year"),
		    message.envelope().dmLegalTitleYearStr());
828
	}
829 830 831
	if (!message.envelope().dmLegalTitleSect().isEmpty()) {
		tmpHtml += strongInfoLine(tr("Section"),
		    message.envelope().dmLegalTitleSect());
832
	}
833
	if (!message.envelope().dmLegalTitlePar().isEmpty()) {
834
		tmpHtml += strongInfoLine(tr("Paragraph"),
835
		    message.envelope().dmLegalTitlePar());
836
	}
837 838 839
	if (!message.envelope().dmLegalTitlePoint().isEmpty()) {
		tmpHtml += strongInfoLine(tr("Letter"),
		    message.envelope().dmLegalTitlePoint());
840 841
	}
	if (!tmpHtml.isEmpty()) {
842
		html += "<h3>" + tr("Additional info") + "</h3>";
843 844
		html += tmpHtml;
	}
845

846 847
	html += "<h3>" + tr("Message state") + "</h3>";
	html += strongInfoLine(tr("Delivery time"),
848
	    dateTimeStrFromDbFormat(
849
	        utcDateTimeToDbFormatStr(message.envelope().dmDeliveryTime()),
850
	        DATETIME_QML_FORMAT));
851
	html += strongInfoLine(tr("Accetance time"),
852
	    dateTimeStrFromDbFormat(
853
	        utcDateTimeToDbFormatStr(message.envelope().dmAcceptanceTime()),
854
	        DATETIME_QML_FORMAT));
855
	html += strongInfoLine(tr("Status"),
856
	    QString::number(message.envelope().dmMessageStatus()));
857

858
	if (type == MsgInfo::TYPE_DELIVERY_INFO) {
859
		html += "<h3>" + tr("Events") + "</h3>";
860
		foreach (const Isds::Event &event, events) {
861 862
			html += divStart +
			    strongInfoLine(dateTimeStrFromDbFormat(
863 864
			        utcDateTimeToDbFormatStr(event.time()),
			        DATETIME_QML_FORMAT), event.descr())
865 866 867 868
			    + divEnd;
		}
	}

869 870
	html += divEnd;

871
	// Create body for email
872 873 874 875
	QString body = generateEmailBodyText(message.envelope().dmId(),
	    message.envelope().dmSender(), message.envelope().dmRecipient(),
	    dateTimeStrFromDbFormat(
	    utcDateTimeToDbFormatStr(message.envelope().dmAcceptanceTime()),
876
	    DATETIME_QML_FORMAT));
877

878
	if (idStr != Q_NULLPTR) {
879
		*idStr = QString::number(message.envelope().dmId());
880
	}
881
	if (annotation != Q_NULLPTR) {
882
		*annotation = message.envelope().dmAnnotation();
883 884 885 886 887
	}
	if (msgDescrHtml != Q_NULLPTR) {
		*msgDescrHtml = html;
	}
	if (attachModel != Q_NULLPTR) {
888
		attachModel->clearAll();
889
		foreach (const Isds::Document &document, message.documents()) {
Martin Straka's avatar
Martin Straka committed
890
			attachModel->appendFileEntry(
891
			    FileListModel::Entry(-1, document.fileDescr(),
892
			    document.binaryContent(), document.binaryContent().size(),
893
			    QString()));
894
		}
895 896 897 898
	}
	if (emailBody != Q_NULLPTR) {
		*emailBody = body;
	}
899

900 901
	return true;
}