/*
* Copyright (C) 2014-2018 CZ.NIC
*
* 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
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* 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 .
*
* 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
#include
#include
#include
#include
#include
#include
#include
#include
#include "src/accounts.h"
#include "src/datovka_shared/io/records_management_db.h"
#include "src/datovka_shared/localisation/localisation.h"
#include "src/dialogues/qml_dialogue_helper.h"
#include "src/dialogues/qml_input_dialogue.h"
#include "src/dialogues/dialogues.h"
#include "src/files.h"
#include "src/global.h"
#include "src/io/filesystem.h"
#include "src/locker.h"
#include "src/log/log.h"
#include "src/net/isds_wrapper.h"
#include "src/models/accountmodel.h"
#include "src/models/databoxmodel.h"
#include "src/models/filemodel.h"
#include "src/models/list_sort_filter_proxy_model.h"
#include "src/models/messagemodel.h"
#if defined(Q_OS_ANDROID)
#include "src/os_android.h"
#endif /* defined(Q_OS_ANDROID) */
#include "src/qml_interaction/attachment_data.h"
#include "src/qml_interaction/image_provider.h"
#include "src/qml_interaction/interaction_filesystem.h"
#include "src/qml_interaction/interaction_zfo_file.h"
#include "src/qml_interaction/message_envelope.h"
#include "src/qml_interaction/string_manipulation.h"
#include "src/records_management/models/upload_hierarchy_list_model.h"
#include "src/records_management/models/upload_hierarchy_qml_proxy_model.h"
#include "src/records_management.h"
#include "src/settings.h"
#include "src/setwrapper.h"
#include "src/sqlite/db_tables.h"
#include "src/sqlite/account_db.h"
#include "src/sqlite/file_db_container.h"
#include "src/sqlite/message_db_container.h"
#include "src/sqlite/zfo_db.h"
#include "src/worker/emitter.h"
#include "src/worker/pool.h"
#include "src/zfo.h"
/* iOS app_delegate - for interaction with iOS action Open in... */
#if defined Q_OS_IOS
#include "ios/src/qt_app_delegate.h"
#endif
/* ACCOUNT DB filename */
#define ACNT_DB_NAME "accounts.db"
/* APPLICATION IDENTIFICATION */
#define APP_ORG_NAME "CZ.NIC, z.s.p.o."
#define APP_ORG_DOMAIN "cz.nic"
#define DEFAULT_FONT_FAMILY "System"
/* namespace for QML registered objects */
const char *uri = "cz.nic.mobileDatovka"; /* Pages and components. */
/*!
* @brief Used when registering types.
*/
struct QmlTypeEntry {
const char *typeName;
int major;
int minor;
};
#define QML_PAGE_LOC "qrc:/qml/pages"
/*!
* @brief NULL-terminated list of pages.
*/
static
const struct QmlTypeEntry qmlPages[] = {
{ "PageAboutApp", 1, 0 },
{ "PageAccountDetail", 1, 0 },
{ "PageAccountList", 1, 0 },
{ "PageChangePassword", 1, 0 },
{ "PageContactList", 1, 0 },
{ "PageDataboxDetail", 1, 0 },
{ "PageDataboxSearch", 1, 0 },
{ "PageImportMessage", 1, 0 },
{ "PageMenuAccount", 1, 0 },
{ "PageMenuDatovkaSettings", 1, 0 },
{ "PageMenuMessage", 1, 0 },
{ "PageMenuMessageDetail", 1, 0 },
{ "PageMenuMessageList", 1, 0 },
{ "PageMessageDetail", 1, 0 },
{ "PageMessageList", 1, 0 },
{ "PageMessageSearch", 1, 0 },
{ "PageRecordsManagementUpload", 1, 0 },
{ "PageSendMessage", 1, 0 },
{ "PageSettingsAccount", 1, 0 },
{ "PageSettingsGeneral", 1, 0 },
{ "PageSettingsPin", 1, 0 },
{ "PageSettingsRecordsManagement", 1, 0 },
{ "PageSettingsStorage", 1, 0 },
{ "PageSettingsSync", 1, 0 },
{ NULL, 0, 0 }
};
#define QML_COMPONENT_LOC "qrc:/qml/components"
/*!
* @brief NULL-terminated list of components.
*/
static
const struct QmlTypeEntry qmlComponents[] = {
{ "AccessibleButton", 1, 0 },
{ "AccessibleButtonWithImage", 1, 0 },
{ "AccessibleComboBox", 1, 0 },
{ "AccessibleImageButton", 1, 0 },
{ "AccessibleMenu", 1, 0 },
{ "AccessibleOverlaidImageButton", 1, 0 },
{ "AccessibleSpinBox", 1, 0 },
{ "AccessibleSpinBoxZeroMax", 1, 0 },
{ "AccessibleSwitch", 1, 0 },
{ "AccessibleTabButton", 1, 0 },
{ "AccessibleText", 1, 0 },
{ "AccessibleTextButton", 1, 0 },
{ "AccessibleTextField", 1, 0 },
{ "DataboxList", 1, 0 },
{ "FilterBar", 1, 0 },
{ "InputLineMenu", 1, 0 },
{ "MessageList", 1, 0 },
{ "OverlaidImage", 1, 0 },
{ "PageHeader", 1, 0 },
{ "ProgressBar", 1, 0 },
{ "ScrollableListView", 1, 0 },
{ NULL, 0, 0 }
};
#define QML_DIALOGUES "qrc:/qml/dialogues"
/*!
* @brief NULL-terminated list of components.
*/
static
const struct QmlTypeEntry qmlDialogues[] = {
{ "FileDialogue", 1, 0 },
{ "InputDialogue", 1, 0},
{ "MessageDialogue", 1, 0},
{ "PasteInputDialogue", 1, 0 },
{ NULL, 0, 0 }
};
/*!
* @brief Initialises command line parser.
*
* @param[in,out] parser Command line parser object to be initialised.
* @return 0 on success, -1 on failure
*/
static
int setupCmdLineParser(QCommandLineParser &parser)
{
parser.setApplicationDescription(QObject::tr("Data box application"));
parser.addHelpOption();
parser.addVersionOption();
parser.addPositionalArgument("[zfo-file]",
QObject::tr("ZFO file to be viewed."));
return 0;
}
/*!
* @brief Registers QML types.
*
* @param[in] uri Namespace for QML objects.
* @param[in] location Location of the QML description file.
* @Param[in] entries List of entries describing the types.
*/
static
void registerQmlTypes(const char *uri, const char *location,
const struct QmlTypeEntry *entries)
{
if ((uri == NULL) || (location == NULL)) {
Q_ASSERT(0);
return;
}
const struct QmlTypeEntry *entry = entries;
while ((entry != NULL) && (entry->typeName != NULL)) {
qmlRegisterType(
QUrl(QString("%1/%2.qml").arg(location).arg(entry->typeName)),
uri, entry->major, entry->minor, entry->typeName);
++entry;
}
}
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QQuickStyle::setStyle("Material");
//qputenv("QT_QUICK_CONTROLS_STYLE", "material");
/* First thing to create is the application object. */
QApplication app(argc, argv);
/*
* This object needs to be created in the same thread as the
* application.
*/
QmlDlgHelper dlgHelper;
QmlDlgHelper::dlgEmitter = &dlgHelper;
/* Perform check that configuration file can be accessed. */
const QString settingsFileName(Settings::settingsPath());
if (settingsFileName.isEmpty()) {
return EXIT_FAILURE;
}
QCoreApplication::addLibraryPath("./");
/* Initialise random number generator. */
qsrand(QDateTime::currentDateTimeUtc().toTime_t());
/* set application data, identification, etc. */
app.setOrganizationName(APP_ORG_NAME);
app.setOrganizationDomain(APP_ORG_DOMAIN);
app.setApplicationName(APP_NAME);
app.setApplicationVersion(VERSION);
QCommandLineParser parser;
if (0 != setupCmdLineParser(parser)) {
return EXIT_FAILURE;
}
/* Process command-line arguments. */
parser.process(app);
/* Create globally accessible objects. */
{
GlobInstcs::msgProcEmitterPtr =
new (std::nothrow) MessageProcessingEmitter;
if (GlobInstcs::msgProcEmitterPtr == Q_NULLPTR) {
qCritical("Cannot create status message emitter.");
return EXIT_FAILURE;
}
/*
* Only one worker thread currently.
* TODO -- To be able to run multiple threads in the pool
* a locking mechanism over isds context structures must
* be implemented. Also, per-context queueing
* ought to be implemented to avoid unnecessary waiting.
*/
GlobInstcs::workPoolPtr = new (std::nothrow) WorkerPool(1);
if (GlobInstcs::workPoolPtr == Q_NULLPTR) {
qCritical("Cannot create worker pool.");
return EXIT_FAILURE;
}
GlobInstcs::setPtr = new (std::nothrow) Settings;
if (GlobInstcs::setPtr == Q_NULLPTR) {
qCritical("cannot create settings.");
return EXIT_FAILURE;
}
}
QStringList cmdLineFileNames;
#if defined(Q_OS_ANDROID)
cmdLineFileNames = IntentNotification::getIntentArguments();
#else
cmdLineFileNames = parser.positionalArguments();
#endif /* defined(Q_OS_ANDROID) */
/* load global settings */
{
QSettings settings(Settings::settingsPath(),
QSettings::IniFormat);
GlobInstcs::setPtr->loadFromSettings(settings);
}
/* set font family and font size from settings */
QFont font;
font.setFamily(DEFAULT_FONT_FAMILY);
font.setPointSize(GlobInstcs::setPtr->fontSize);
app.setFont(font);
/* load datovka localization and qtbase localization */
QTranslator datovkaTrans;
QTranslator qtbaseTrans;
QString lang(
Localisation::shortLangName(GlobInstcs::setPtr->language));
Localisation::setProgramLocale(GlobInstcs::setPtr->language);
if (!datovkaTrans.load("datovka_" + lang, ":/locale/")) {
qDebug() << "Could not load datovka localisation file...";
}
app.installTranslator(&datovkaTrans);
if (!qtbaseTrans.load("qtbase_" + lang, ":/locale/")) {
qDebug() << "Could not load qtbase localisation file...";
}
app.installTranslator(&qtbaseTrans);
/* Start worker threads. */
GlobInstcs::workPoolPtr->start();
logInfoNL("%s", "Worker pool started.");
/* Init and use these class - we need register it to QML */
Messages messages;
Accounts accounts;
Files files;
IsdsWrapper isds;
GlobalSettingsQmlWrapper settings;
RecordsManagement recordsManagement;
StringManipulation strManipulation;
Zfo zfo;
/* Connect slot for isds cxt delete when account was deleted or updated */
QObject::connect(&accounts, SIGNAL(removeIsdsCtx(QString)),
&isds, SLOT(removeIsdsCtx(QString)));
/* Register application state changes. */
class Locker locker;
QObject::connect(&app,
SIGNAL(applicationStateChanged(Qt::ApplicationState)),
&locker, SLOT(processNewState(Qt::ApplicationState)));
app.installEventFilter(&locker);
/* Create and init account model. */
AccountListModel *accountModelPtr = new (std::nothrow) AccountListModel;
if (Q_NULLPTR == accountModelPtr) {
qCritical("Cannot create account model.");
return EXIT_FAILURE;
}
accountModelPtr->setObjectName("mainAccountModel");
/* get main handle of appliaction and QML */
QQmlApplicationEngine engine;
QQmlContext *ctx = engine.rootContext();
globImgProvPtr = new (std::nothrow) ImageProvider;
if (Q_NULLPTR == globImgProvPtr) {
qCritical("Cannot create image provider.");
return EXIT_FAILURE;
}
engine.addImageProvider(IMAGE_PROVIDER_ID, globImgProvPtr);
/* Register application pages to QML */
registerQmlTypes(uri, QML_PAGE_LOC, qmlPages);
registerQmlTypes(uri, QML_COMPONENT_LOC, qmlComponents);
registerQmlTypes(uri, QML_DIALOGUES, qmlDialogues);
/* Register types into QML. */
AccountListModel::declareQML();
AttachmentData::declareQML();
DataboxListModel::declareQML();
DataboxModelEntry::declareQML();
Dialogues::declareQML();
FileListModel::declareQML();
Files::declareQML();
InteractionFilesystem::declareQML();
ListSortFilterProxyModel::declareQML();
MessageListModel::declareQML();
Messages::declareQML();
MsgEnvelope::declareQML();
MsgInfo::declareQML();
UploadHierarchyListModel::declareQML();
UploadHierarchyQmlProxyModel::declareQML();
InteractionZfoFile interactionZfoFile;
/* Inicialize app delegate component for interaction with iOS
* Reaction on the iOS action "Open in..." */
#if defined Q_OS_IOS
QtAppDelegateInitialize(&interactionZfoFile);
#endif
/* register classes in QML */
ctx->setContextProperty("isds", &isds);
ctx->setContextProperty("messages", &messages);
ctx->setContextProperty("accounts", &accounts);
ctx->setContextProperty("files", &files);
ctx->setContextProperty("settings", &settings);
ctx->setContextProperty("strManipulation", &strManipulation);
ctx->setContextProperty("locker", &locker);
ctx->setContextProperty("interactionZfoFile", &interactionZfoFile);
ctx->setContextProperty("dlgEmitter", QmlDlgHelper::dlgEmitter);
ctx->setContextProperty("zfo", &zfo);
ctx->setContextProperty("recordsManagement", &recordsManagement);
/* register and set models in QML */
ctx->setContextProperty(accountModelPtr->objectName(), accountModelPtr);
/* Localise description in tables. */
accntinfTbl.reloadLocalisedDescription();
userinfTbl.reloadLocalisedDescription();
msgsTbl.reloadLocalisedDescription();
flsTbl.reloadLocalisedDescription();
evntsTbl.reloadLocalisedDescription();
msgZfoTbl.reloadLocalisedDescription();
/* Init and open account database. */
AccountDb accountDb("ACCOUNTS", false);
MsgDbContainer messageDbs("MESSAGES");
FileDbContainer fileDbs("FILES");
ZfoDb zfoDb("ZFOS", false);
{
GlobInstcs::accountDbPtr = &accountDb;
GlobInstcs::messageDbsPtr = &messageDbs;
GlobInstcs::fileDbsPtr = &fileDbs;
GlobInstcs::zfoDbPtr = &zfoDb;
GlobInstcs::recMgmtDbPtr = new (std::nothrow)
RecordsManagementDb("recordsManagementDb", false);
if (GlobInstcs::recMgmtDbPtr == Q_NULLPTR) {
logErrorNL("%s",
"Cannot allocate records management db.");
return EXIT_FAILURE;
}
}
QString dirName(
existingWritableLocation(QStandardPaths::AppDataLocation));
if (dirName.isEmpty()) {
logErrorNL("%s",
"Cannot determine application data location.");
Q_ASSERT(0);
return EXIT_FAILURE;
}
QString dbPath(dirName + QDir::separator() + ACNT_DB_NAME);
if (!GlobInstcs::accountDbPtr->openDb(dbPath,
SQLiteDb::CREATE_MISSING)) {
logErrorNL("%s", "Account database not found!");
return EXIT_FAILURE;
}
/* Open records management database. */
QString rmDbPath(dirName + QDir::separator() + RECORDS_MANAGEMENT_DB_FILE);
if (!GlobInstcs::recMgmtDbPtr->openDb(
rmDbPath, SQLiteDb::CREATE_MISSING)) {
logErrorNL("Error opening records management db '%s'.",
rmDbPath.toUtf8().constData());
return EXIT_FAILURE;
}
/* Load accounts from settings to account model. */
{
QSettings settings(Settings::settingsPath(),
QSettings::IniFormat);
accountModelPtr->loadAccountsFromSettings(settings);
/*
* No need to load counters here as some messages are likely
* to be deleted.
*/
}
/*
* Open ZFO database, the second parameter means: true = zfo db will
* store on disk, false = only in memory
*/
if (!GlobInstcs::zfoDbPtr->openDb(
ZFO_DB_NAME, (GlobInstcs::setPtr->zfoDbSizeMBs > 0))) {
qDebug() << "ERROR: zfo db not found!";
}
/* load UI (QML) */
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
/* OpenSSL support test */
if (QSslSocket::supportsSsl()) {
/* set last update text to status bar */
if (!GlobInstcs::setPtr->lastUpdateStr().isEmpty() &&
GlobInstcs::setPtr->pinCode.isEmpty()) {
emit isds.statusBarTextChanged(
QObject::tr("Last synchronisation: %1").
arg(GlobInstcs::setPtr->lastUpdateStr()), false, true);
}
} else {
Dialogues::errorMessage(Dialogues::WARNING,
QObject::tr("Security problem"),
QObject::tr("OpenSSL support is required!"),
QObject::tr("The device does not support OpenSSL. "
"The application won't work correctly."));
}
/* Deletion of messages from db is disabled when equal to 0. */
if (GlobInstcs::setPtr->msgLifeTimeInDays > 0) {
messages.deleteExpiredMessagesFromDbs(
GlobInstcs::setPtr->msgLifeTimeInDays);
}
/* Deletion of files from db is disabled when equal to 0. */
if (GlobInstcs::setPtr->fileLifeTimeInDays > 0) {
files.deleteExpiredFilesFromDbs(
GlobInstcs::setPtr->fileLifeTimeInDays);
}
/* Inactivity locking is disabled when equal to 0. */
if (GlobInstcs::setPtr->pinInactTimeoutInSecs > 0) {
locker.setInactivityInterval(
GlobInstcs::setPtr->pinInactTimeoutInSecs);
}
/* Load counters. */
Accounts::loadModelCounters(accountModelPtr);
/*
* Show PIN screen if needed. Encoded PIN is checked because it hasn't
* been decoded yet.
*/
if (!GlobInstcs::setPtr->pinCode.isEmpty()) {
emit locker.lockApp();
}
/*
* Open files passed via command line.
*/
if (!cmdLineFileNames.isEmpty()) {
foreach (const QString &filePath, cmdLineFileNames) {
interactionZfoFile.openZfoFile(filePath);
}
}
#if defined(Q_OS_ANDROID)
IntentNotification intentNotification(interactionZfoFile);
QObject::connect(&app, SIGNAL(applicationStateChanged(Qt::ApplicationState)),
&intentNotification, SLOT(scanIntentsForFiles(Qt::ApplicationState)));
#endif /* defined(Q_OS_ANDROID) */
QmlInputDialogue::searchPersistentDialogues(&engine);
/* Run app main event loop */
int ret = app.exec();
/* Wait until all threads finished. */
logInfoNL("%s", "Waiting for pending worker threads.");
GlobInstcs::workPoolPtr->wait();
GlobInstcs::workPoolPtr->stop();
logInfoNL("%s", "All worker threads finished");
/* Close all OTP connections if exist */
isds.closeAllOtpConnections();
/*
* Store the configuration only when PIN has been recovered or is not
* used.
*/
if (!GlobInstcs::setPtr->_pinVal.isEmpty() ||
!GlobInstcs::setPtr->pinConfigured()) {
/*
* The PIN was set/recovered or
* incomplete data to check/recover the PIN were supplied.
*/
GlobalSettingsQmlWrapper::saveAllSettings(accountModelPtr);
}
delete accountModelPtr;
accountModelPtr = Q_NULLPTR;
/* Destroy globally accessible objects. */
{
delete GlobInstcs::recMgmtDbPtr; GlobInstcs::recMgmtDbPtr = Q_NULLPTR;
GlobInstcs::zfoDbPtr = Q_NULLPTR;
GlobInstcs::fileDbsPtr = Q_NULLPTR;
GlobInstcs::messageDbsPtr = Q_NULLPTR;
GlobInstcs::accountDbPtr = Q_NULLPTR;
delete GlobInstcs::setPtr; GlobInstcs::setPtr = Q_NULLPTR;
delete GlobInstcs::workPoolPtr; GlobInstcs::workPoolPtr = Q_NULLPTR;
delete GlobInstcs::msgProcEmitterPtr; GlobInstcs::msgProcEmitterPtr = Q_NULLPTR;
}
return ret;
}