From 96eb5982477415f25d0b1531f11699c6b36a4985 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Thu, 25 Jan 2018 10:10:02 +0100 Subject: [PATCH 01/63] Added records management settings page. --- qml/main.qml | 1 + qml/pages/PageMenuDatovkaSettings.qml | 13 ++ qml/pages/PageRecordsManagementSettings.qml | 187 ++++++++++++++++++++ res/qml.qrc | 2 + res/ui/briefcase.svg | 6 + src/main.cpp | 1 + 6 files changed, 210 insertions(+) create mode 100644 qml/pages/PageRecordsManagementSettings.qml create mode 100755 res/ui/briefcase.svg diff --git a/qml/main.qml b/qml/main.qml index 3e6d2f58..11f42c2a 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -77,6 +77,7 @@ ApplicationWindow { property Component pageMessageDetail: PageMessageDetail {} property Component pageMessageList: PageMessageList {} property Component pageMessageSearch: PageMessageSearch {} + property Component pageRecordsManagementSettings: PageRecordsManagementSettings {} property Component pageSendMessage: PageSendMessage {} property Component pageSettingsAccount: PageSettingsAccount {} property Component pageSettingsGeneral: PageSettingsGeneral {} diff --git a/qml/pages/PageMenuDatovkaSettings.qml b/qml/pages/PageMenuDatovkaSettings.qml index c1e48977..7898fb5b 100644 --- a/qml/pages/PageMenuDatovkaSettings.qml +++ b/qml/pages/PageMenuDatovkaSettings.qml @@ -105,6 +105,12 @@ Component { "accountModel": accountModel }, StackView.Immediate) }, + "settRecMan": function callSettRecMan() { + pageView.replace(pageRecordsManagementSettings, { + "pageView": pageView, + "statusBar": statusBar + }, StackView.Immediate) + }, "userGuide": function callUserGuide() { Qt.openUrlExternally("https://secure.nic.cz/files/datove_schranky/redirect/mobile-manual.html") pageView.pop(StackView.Immediate) @@ -162,6 +168,13 @@ Component { name: qsTr("Security and PIN") funcName: "settSecPin" } + ListElement { + image: "qrc:/ui/briefcase.svg" + showEntry: true + showNext: true + name: qsTr("Records management") + funcName: "settRecMan" + } ListElement { image: "qrc:/ui/information.svg" showEntry: true diff --git a/qml/pages/PageRecordsManagementSettings.qml b/qml/pages/PageRecordsManagementSettings.qml new file mode 100644 index 00000000..f032f5ce --- /dev/null +++ b/qml/pages/PageRecordsManagementSettings.qml @@ -0,0 +1,187 @@ +/* + * 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. + */ + +import QtQuick 2.7 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.2 +import cz.nic.mobileDatovka 1.0 + +Item { + id: pageRecordsManagementSettings + + /* These properties must be set by caller. */ + property var pageView + property var statusBar + + Component.onCompleted: { + } + + PageHeader { + id: headerBar + title: qsTr("Records management settings") + onBackClicked: { + pageView.pop(StackView.Immediate) + } + Row { + anchors.verticalCenter: parent.verticalCenter + spacing: defaultMargin + anchors.right: parent.right + anchors.rightMargin: defaultMargin + AccessibleImageButton { + id: acceptElement + anchors.verticalCenter: parent.verticalCenter + sourceSize.height: imgHeightHeader + source: "qrc:/ui/checkbox-marked-circle.svg" + accessibleName: qsTr("Accept changes") + } + } + } // PageHeader + Flickable { + id: flickable + z: 0 + anchors.top: headerBar.bottom + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + contentHeight: flickContent.implicitHeight + Pane { + id: flickContent + anchors.fill: parent + Column { + anchors.right: parent.right + anchors.left: parent.left + spacing: formItemVerticalSpacing + AccessibleText { + color: datovkaPalette.mid + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + width: parent.width + text: qsTr("Please fill service url, identification token and click button 'Get service info'") + } + AccessibleText { + color: datovkaPalette.text + text: qsTr("URL") + } + TextField { + id: urlTextField + width: parent.width + height: inputItemHeight + font.pointSize: defaultTextFont.font.pointSize + placeholderText: qsTr("Enter url") + InputLineMenu { + id: urlMenu + inputTextControl: urlTextField + isPassword: false + } + onPressAndHold: { + if (settings.useExplicitClipboardOperations()) { + urlMenu.implicitWidth = computeMenuWidth(urlMenu) + urlMenu.open() + } + } + } + AccessibleText { + color: datovkaPalette.text + text: qsTr("Token") + } + TextField { + id: tokenTextField + width: parent.width + height: inputItemHeight + font.pointSize: defaultTextFont.font.pointSize + placeholderText: qsTr("Enter token") + InputLineMenu { + id: tokenMenu + inputTextControl: tokenTextField + isPassword: false + } + onPressAndHold: { + if (settings.useExplicitClipboardOperations()) { + tokenMenu.implicitWidth = computeMenuWidth(tokenMenu) + tokenMenu.open() + } + } + } + Row { + spacing: formItemVerticalSpacing * 5 + anchors.horizontalCenter: parent.horizontalCenter + AccessibleButton { + id: info + height: inputItemHeight + font.pointSize: defaultTextFont.font.pointSize + text: qsTr("Get service info") + onClicked: { + // TODO - connect to service + } + } + AccessibleButton { + id: clear + height: inputItemHeight + font.pointSize: defaultTextFont.font.pointSize + text: qsTr("Clear") + onClicked: { + // TODO - delete records from settings + urlTextField.clear() + tokenTextField.clear() + serviceInfo1.visible = false + serviceInfo2.visible = false + } + } + } // Row + Row { + id: serviceInfo1 + spacing: formItemVerticalSpacing + visible: false + AccessibleText { + height: inputItemHeight + font.pointSize: defaultTextFont.font.pointSize + text: qsTr("Service name:") + } + AccessibleText { + id: serviceName + height: inputItemHeight + font.pointSize: defaultTextFont.font.pointSize + text: "Service" + } + } // Row + Row { + id: serviceInfo2 + spacing: formItemVerticalSpacing + visible: false + AccessibleText { + height: inputItemHeight + font.pointSize: defaultTextFont.font.pointSize + text: qsTr("Token name:") + } + AccessibleText { + id: tokenName + height: inputItemHeight + font.pointSize: defaultTextFont.font.pointSize + text: "Token" + } + } // Row + } // Column layout + } // Pane + ScrollIndicator.vertical: ScrollIndicator {} + } // Flickable +} // Item diff --git a/res/qml.qrc b/res/qml.qrc index 3dc80f59..6a96959f 100644 --- a/res/qml.qrc +++ b/res/qml.qrc @@ -44,6 +44,7 @@ ui/blank.svg ui/bookmark-check.svg ui/book-open.svg + ui/briefcase.svg ui/call-split.svg ui/close-octagon.svg ui/content-save.svg @@ -140,6 +141,7 @@ ../qml/pages/PageMessageDetail.qml ../qml/pages/PageMessageList.qml ../qml/pages/PageMessageSearch.qml + ../qml/pages/PageRecordsManagementSettings.qml ../qml/pages/PageSendMessage.qml ../qml/pages/PageSettingsAccount.qml ../qml/pages/PageSettingsGeneral.qml diff --git a/res/ui/briefcase.svg b/res/ui/briefcase.svg new file mode 100755 index 00000000..b4afb054 --- /dev/null +++ b/res/ui/briefcase.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main.cpp b/src/main.cpp index 1deab149..b04ca20a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -109,6 +109,7 @@ const struct QmlTypeEntry qmlPages[] = { { "PageMessageDetail", 1, 0 }, { "PageMessageList", 1, 0 }, { "PageMessageSearch", 1, 0 }, + { "PageRecordsManagementSettings", 1, 0 }, { "PageSendMessage", 1, 0 }, { "PageSettingsAccount", 1, 0 }, { "PageSettingsGeneral", 1, 0 }, -- GitLab From 795fae5c13399e57c050578b777b2a63ae02fadd Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Mon, 29 Jan 2018 09:43:08 +0100 Subject: [PATCH 02/63] Added get service info and records management database. --- mobile-datovka.pro | 16 + qml/pages/PageRecordsManagementSettings.qml | 65 +++- .../io/records_management_db.cpp | 291 +++++++++++++++ src/datovka_shared/io/records_management_db.h | 142 ++++++++ .../records_management/conversion.cpp | 56 +++ .../records_management/conversion.h | 36 ++ .../io/records_management_connection.cpp | 339 ++++++++++++++++++ .../io/records_management_connection.h | 128 +++++++ .../records_management/json/entry_error.cpp | 252 +++++++++++++ .../records_management/json/entry_error.h | 136 +++++++ .../records_management/json/helper.cpp | 185 ++++++++++ .../records_management/json/helper.h | 124 +++++++ .../records_management/json/service_info.cpp | 135 +++++++ .../records_management/json/service_info.h | 108 ++++++ .../settings/records_management.cpp | 289 +++++++++++++++ .../settings/records_management.h | 166 +++++++++ src/main.cpp | 47 ++- src/records_management.cpp | 101 ++++++ src/records_management.h | 79 ++++ src/settings.cpp | 10 +- src/setwrapper.cpp | 23 +- src/setwrapper.h | 39 +- src/sqlite/db_tables.cpp | 68 +++- src/sqlite/db_tables.h | 11 +- 24 files changed, 2811 insertions(+), 35 deletions(-) create mode 100644 src/datovka_shared/io/records_management_db.cpp create mode 100644 src/datovka_shared/io/records_management_db.h create mode 100644 src/datovka_shared/records_management/conversion.cpp create mode 100644 src/datovka_shared/records_management/conversion.h create mode 100644 src/datovka_shared/records_management/io/records_management_connection.cpp create mode 100644 src/datovka_shared/records_management/io/records_management_connection.h create mode 100644 src/datovka_shared/records_management/json/entry_error.cpp create mode 100644 src/datovka_shared/records_management/json/entry_error.h create mode 100644 src/datovka_shared/records_management/json/helper.cpp create mode 100644 src/datovka_shared/records_management/json/helper.h create mode 100644 src/datovka_shared/records_management/json/service_info.cpp create mode 100644 src/datovka_shared/records_management/json/service_info.h create mode 100644 src/datovka_shared/settings/records_management.cpp create mode 100644 src/datovka_shared/settings/records_management.h create mode 100644 src/records_management.cpp create mode 100644 src/records_management.h diff --git a/mobile-datovka.pro b/mobile-datovka.pro index 8ea5bc4d..434f7315 100644 --- a/mobile-datovka.pro +++ b/mobile-datovka.pro @@ -88,10 +88,17 @@ SOURCES += \ src/accounts.cpp \ src/auxiliaries/attachment_helper.cpp \ src/auxiliaries/email_helper.cpp \ + src/datovka_shared/io/records_management_db.cpp \ src/datovka_shared/io/sqlite/db.cpp \ src/datovka_shared/io/sqlite/db_single.cpp \ src/datovka_shared/io/sqlite/table.cpp \ + src/datovka_shared/records_management/conversion.cpp \ + src/datovka_shared/records_management/io/records_management_connection.cpp \ + src/datovka_shared/records_management/json/entry_error.cpp \ + src/datovka_shared/records_management/json/helper.cpp \ + src/datovka_shared/records_management/json/service_info.cpp \ src/datovka_shared/settings/pin.cpp \ + src/datovka_shared/settings/records_management.cpp \ src/datovka_shared/utility/strings.cpp \ src/datovka_shared/worker/pool.cpp \ src/dialogues/dialogues.cpp \ @@ -123,6 +130,7 @@ SOURCES += \ src/qml_interaction/message_envelope.cpp \ src/qml_interaction/message_info.cpp \ src/qml_interaction/string_manipulation.cpp \ + src/records_management.cpp \ src/settings.cpp \ src/setwrapper.cpp \ src/sqlite/account_db.cpp \ @@ -153,10 +161,17 @@ HEADERS += \ src/auxiliaries/attachment_helper.h \ src/auxiliaries/email_helper.h \ src/common.h \ + src/datovka_shared/io/records_management_db.h \ src/datovka_shared/io/sqlite/db.h \ src/datovka_shared/io/sqlite/db_single.h \ src/datovka_shared/io/sqlite/table.h \ + src/datovka_shared/records_management/conversion.h \ + src/datovka_shared/records_management/io/records_management_connection.h \ + src/datovka_shared/records_management/json/entry_error.h \ + src/datovka_shared/records_management/json/helper.h \ + src/datovka_shared/records_management/json/service_info.h \ src/datovka_shared/settings/pin.h \ + src/datovka_shared/settings/records_management.h \ src/datovka_shared/utility/strings.h \ src/datovka_shared/worker/pool.h \ src/dialogues/dialogues.h \ @@ -188,6 +203,7 @@ HEADERS += \ src/qml_interaction/message_envelope.h \ src/qml_interaction/message_info.h \ src/qml_interaction/string_manipulation.h \ + src/records_management.h \ src/settings.h \ src/setwrapper.h \ src/sqlite/account_db.h \ diff --git a/qml/pages/PageRecordsManagementSettings.qml b/qml/pages/PageRecordsManagementSettings.qml index f032f5ce..798c486a 100644 --- a/qml/pages/PageRecordsManagementSettings.qml +++ b/qml/pages/PageRecordsManagementSettings.qml @@ -32,8 +32,24 @@ Item { /* These properties must be set by caller. */ property var pageView property var statusBar + /* Remember last service url from settings */ + property string lastUrlFromSettings: "" + + /* Enable button if all required fields are filled */ + function areReguiredFieldsFilled() { + infoButton.enabled = (urlTextField.text.toString() !== "" && tokenTextField.text.toString() !== "") + clearButton.enabled = infoButton.enabled + return infoButton.enabled + } Component.onCompleted: { + urlTextField.text = settings.rmUrl() + lastUrlFromSettings = settings.rmUrl() + tokenTextField.text = settings.rmToken() + serviceInfo1.visible = false + serviceInfo2.visible = serviceInfo1.visible + serviceLogo.visible = serviceInfo1.visible + recordsManagement.loadStoredServiceInfo() } PageHeader { @@ -53,6 +69,12 @@ Item { sourceSize.height: imgHeightHeader source: "qrc:/ui/checkbox-marked-circle.svg" accessibleName: qsTr("Accept changes") + onClicked: { + recordsManagement.updateServiceInfo(urlTextField.text, lastUrlFromSettings, "", serviceName.text, tokenName.text) + settings.setRmUrl(urlTextField.text) + settings.setRmToken(tokenTextField.text) + pageView.pop(StackView.Immediate) + } } } } // PageHeader @@ -82,7 +104,7 @@ Item { color: datovkaPalette.text text: qsTr("URL") } - TextField { + AccessibleTextField { id: urlTextField width: parent.width height: inputItemHeight @@ -99,12 +121,15 @@ Item { urlMenu.open() } } + onTextChanged: { + areReguiredFieldsFilled() + } } AccessibleText { color: datovkaPalette.text text: qsTr("Token") } - TextField { + AccessibleTextField { id: tokenTextField width: parent.width height: inputItemHeight @@ -121,30 +146,33 @@ Item { tokenMenu.open() } } + onTextChanged: { + areReguiredFieldsFilled() + } } Row { spacing: formItemVerticalSpacing * 5 anchors.horizontalCenter: parent.horizontalCenter AccessibleButton { - id: info + id: infoButton height: inputItemHeight font.pointSize: defaultTextFont.font.pointSize text: qsTr("Get service info") onClicked: { - // TODO - connect to service + recordsManagement.callServiceInfo(urlTextField.text, tokenTextField.text) } } AccessibleButton { - id: clear + id: clearButton height: inputItemHeight font.pointSize: defaultTextFont.font.pointSize text: qsTr("Clear") onClicked: { - // TODO - delete records from settings urlTextField.clear() tokenTextField.clear() serviceInfo1.visible = false - serviceInfo2.visible = false + serviceInfo2.visible = serviceInfo1.visible + serviceLogo.visible = serviceInfo1.visible } } } // Row @@ -161,7 +189,7 @@ Item { id: serviceName height: inputItemHeight font.pointSize: defaultTextFont.font.pointSize - text: "Service" + text: "" } } // Row Row { @@ -177,11 +205,30 @@ Item { id: tokenName height: inputItemHeight font.pointSize: defaultTextFont.font.pointSize - text: "Token" + text: "" } } // Row + AccessibleImageButton { + id: serviceLogo + anchors.horizontalCenter: parent.horizontalCenter + width: imgHeightHeader * 1.4 + height: imgHeightHeader * 1.4 + source: "qrc:/ui/briefcase.svg" + accessibleName: qsTr("Records management logo.") + } } // Column layout } // Pane ScrollIndicator.vertical: ScrollIndicator {} } // Flickable + Connections { + target: recordsManagement + onServiceInfo: { + serviceInfo1.visible = true + serviceInfo2.visible = serviceInfo1.visible + serviceLogo.visible = serviceInfo1.visible + serviceName.text = srName + tokenName.text = srToken + //serviceLogo.source = srLogo + } + } } // Item diff --git a/src/datovka_shared/io/records_management_db.cpp b/src/datovka_shared/io/records_management_db.cpp new file mode 100644 index 00000000..8b4d0804 --- /dev/null +++ b/src/datovka_shared/io/records_management_db.cpp @@ -0,0 +1,291 @@ +/* + * 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 "src/sqlite/db_tables.h" +#include "src/datovka_shared/io/records_management_db.h" +#include "src/log/log.h" + +/*! + * @brief Delete all entries from table. + * + * @param[in,out] db SQL database. + * @param[in] tblName Name of table whose content should be erased. + * @return True on success. + */ +static +bool deleteTableContent(QSqlDatabase &db, const QString &tblName) +{ + if (tblName.isEmpty()) { + return false; + } + + QSqlQuery query(db); + + QString queryStr = "DELETE FROM " + tblName; + if (!query.prepare(queryStr)) { + logErrorNL("Cannot prepare SQL query: %s.", + query.lastError().text().toUtf8().constData()); + return false; + } + if (!query.exec()) { + logErrorNL("Cannot execute SQL query: %s.", + query.lastError().text().toUtf8().constData()); + return false; + } + + return true; +} + +bool RecordsManagementDb::deleteAllEntries(void) +{ + deleteTableContent(m_db, QStringLiteral("service_info")); + deleteTableContent(m_db, QStringLiteral("stored_files_messages")); + + return true; +} + +/*! + * @brief Insert a new service info record into service info table. + * + * @param[in,out] db SQL database. + * @param[in] entry Service info entry. + * @return True on success. + */ +static +bool insertServiceInfo(QSqlDatabase &db, + const RecordsManagementDb::ServiceInfoEntry &entry) +{ + if (!entry.isValid()) { + Q_ASSERT(0); + return false; + } + + QSqlQuery query(db); + + QString queryStr = "INSERT INTO service_info " + "(url, name, token_name, logo_svg) VALUES " + "(:url, :name, :tokenName, :logoSvg)"; + if (!query.prepare(queryStr)) { + logErrorNL("Cannot prepare SQL query: %s.", + query.lastError().text().toUtf8().constData()); + return false; + } + + query.bindValue(":url", entry.url); + query.bindValue(":name", entry.name); + query.bindValue(":tokenName", entry.tokenName); + query.bindValue(":logoSvg", entry.logoSvg.toBase64()); + + if (!query.exec()) { + logErrorNL("Cannot execute SQL query: %s.", + query.lastError().text().toUtf8().constData()); + return false; + } + + return true; +} + +bool RecordsManagementDb::updateServiceInfo(const ServiceInfoEntry &entry) +{ + if (!entry.isValid()) { + return false; + } + + if (!beginTransaction()) { + return false; + } + + if (!deleteTableContent(m_db, QStringLiteral("service_info"))) { + goto rollback; + } + + if (!insertServiceInfo(m_db, entry)) { + goto rollback; + } + + return commitTransaction(); + +rollback: + rollbackTransaction(); + return false; +} + +RecordsManagementDb::ServiceInfoEntry RecordsManagementDb::serviceInfo(void) const +{ + QSqlQuery query(m_db); + + QString queryStr = "SELECT url, name, token_name, logo_svg " + "FROM service_info"; + if (!query.prepare(queryStr)) { + logErrorNL("Cannot prepare SQL query: %s.", + query.lastError().text().toUtf8().constData()); + return ServiceInfoEntry(); + } + + if (query.exec() && query.isActive()) { + query.first(); + if (query.isValid()) { + ServiceInfoEntry entry; + + entry.url = query.value(0).toString(); + entry.name = query.value(1).toString(); + entry.tokenName = query.value(2).toString(); + entry.logoSvg = QByteArray::fromBase64( + query.value(3).toByteArray()); + + Q_ASSERT(entry.isValid()); + return entry; + } else { + return ServiceInfoEntry(); + } + } else { + logErrorNL( + "Cannot execute SQL query and/or read SQL data: %s.", + query.lastError().text().toUtf8().constData()); + return ServiceInfoEntry(); + } +} + +bool RecordsManagementDb::deleteAllStoredMsg(void) +{ + return deleteTableContent(m_db, + QStringLiteral("stored_files_messages")); +} + +bool RecordsManagementDb::deleteStoredMsg(qint64 dmId) +{ + QSqlQuery query(m_db); + + QString queryStr = "DELETE FROM stored_files_messages " + "WHERE dm_id = :dm_id"; + if (!query.prepare(queryStr)) { + logErrorNL("Cannot prepare SQL query: %s.", + query.lastError().text().toUtf8().constData()); + return false; + } + query.bindValue(":dm_id", dmId); + if (!query.exec()) { + logErrorNL("Cannot execute SQL query: %s.", + query.lastError().text().toUtf8().constData()); + return false; + } + + return true; +} + +bool RecordsManagementDb::updateStoredMsg(qint64 dmId, + const QStringList &locations) +{ +#define LIST_SEPARATOR QLatin1String("^") + + QSqlQuery query(m_db); + + QString queryStr = "INSERT OR REPLACE INTO stored_files_messages " + "(dm_id, separator, joined_locations) VALUES " + "(:dm_id, :separator, :joined_locations)"; + if (!query.prepare(queryStr)) { + logErrorNL("Cannot prepare SQL query: %s.", + query.lastError().text().toUtf8().constData()); + return false; + } + + query.bindValue(":dm_id", dmId); + query.bindValue(":separator", LIST_SEPARATOR); + query.bindValue(":joined_locations", locations.join(LIST_SEPARATOR)); + + if (!query.exec()) { + logErrorNL("Cannot exec SQL query: %s.", + query.lastError().text().toUtf8().constData()); + return false; + } + + return true; + +#undef LIST_SEPARATOR +} + +QList RecordsManagementDb::getAllDmIds(void) const +{ + QSqlQuery query(m_db); + + QList dmIdList; + + QString queryStr = "SELECT dm_id FROM stored_files_messages"; + if (!query.prepare(queryStr)) { + logErrorNL("Cannot prepare SQL query: %s.", + query.lastError().text().toUtf8().constData()); + return dmIdList; + } + if (query.exec() && query.isActive()) { + query.first(); + while (query.isValid()) { + dmIdList.append(query.value(0).toLongLong()); + query.next(); + } + } + return dmIdList; +} + +QStringList RecordsManagementDb::storedMsgLocations(qint64 dmId) const +{ + QSqlQuery query(m_db); + + QString queryStr = "SELECT separator, joined_locations " + "FROM stored_files_messages WHERE dm_id = :dm_id"; + if (!query.prepare(queryStr)) { + logErrorNL("Cannot prepare SQL query: %s.", + query.lastError().text().toUtf8().constData()); + return QStringList(); + } + query.bindValue(":dm_id", dmId); + if (query.exec() && query.isActive()) { + query.first(); + if (query.isValid()) { + QString separator(query.value(0).toString()); + return query.value(1).toString().split(separator); + } else { + return QStringList(); + } + } else { + logErrorNL( + "Cannot execute SQL query and/or read SQL data: %s.", + query.lastError().text().toUtf8().constData()); + return QStringList(); + } +} + +QList RecordsManagementDb::listOfTables(void) const +{ + QList tables; + tables.append(&srvcInfTbl); + tables.append(&strdFlsMsgsTbl); + return tables; +} + +RecordsManagementDb *globRecordsManagementDbPtr = Q_NULLPTR; diff --git a/src/datovka_shared/io/records_management_db.h b/src/datovka_shared/io/records_management_db.h new file mode 100644 index 00000000..ce91fcbd --- /dev/null +++ b/src/datovka_shared/io/records_management_db.h @@ -0,0 +1,142 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +#include "src/datovka_shared/io/sqlite/db_single.h" + +#define RECORDS_MANAGEMENT_DB_FILE "records_management.db" + +/*! + * @brief Encapsulates records management database. + */ +class RecordsManagementDb : public SQLiteDbSingle { + +public: + class ServiceInfoEntry { + public: + /*! + * @brief Constructor. + */ + ServiceInfoEntry(void) + : url(), name(), tokenName(), logoSvg() + { + } + + /*! + * @brief Return true in entry is valid. + */ + inline + bool isValid(void) const { + return !url.isEmpty(); + } + + QString url; /*!< Service URL. */ + QString name; /*!< Service name. */ + QString tokenName; /*!< Token name. */ + QByteArray logoSvg; /*!< Raw SVG data. */ + }; + + /* Use parent class constructor. */ + using SQLiteDbSingle::SQLiteDbSingle; + + /*! + * @brief Erases all database entries. + * + * @return True on success. + */ + bool deleteAllEntries(void); + + /*! + * @brief Update service info entry. + * + * @note There can be only one service info entry. + * + * @param[in] entry Service info entry. + */ + bool updateServiceInfo(const ServiceInfoEntry &entry); + + /*! + * @brief Obtain service information from database. + * + * @return Invalid service info if no valid service information found. + */ + ServiceInfoEntry serviceInfo(void) const; + + /*! + * @brief Deletes all message-related data. + * + * @return True on success. + */ + bool deleteAllStoredMsg(void); + + /*! + * @brief Deletes stored locations for given message. + * + * @param[in] dmId Message identifier. + * @return True on success. + */ + bool deleteStoredMsg(qint64 dmId); + + /*! + * @brief Inserts or replaces stored locations for given message. + * + * @param[in] dmId Message identifier. + * @param[in] locations List of locations to be stored. + * @return True on success. + */ + bool updateStoredMsg(qint64 dmId, const QStringList &locations); + + /*! + * @brief Obtains all message identifiers. + * + * @return List of message identifiers, empty list on error. + */ + QList getAllDmIds(void) const; + + /*! + * @brief Reads stored location or given message. + * + * @param[in] dmId Message identifier. + * @return List of locations, empty list on error. + */ + QStringList storedMsgLocations(qint64 dmId) const; + +protected: + /*! + * @brief Returns list of tables. + * + * @return List of pointers to tables. + */ + virtual + QList listOfTables(void) const Q_DECL_OVERRIDE; +}; + +/*! + * @brief Global records management database. + */ +extern RecordsManagementDb *globRecordsManagementDbPtr; diff --git a/src/datovka_shared/records_management/conversion.cpp b/src/datovka_shared/records_management/conversion.cpp new file mode 100644 index 00000000..52236383 --- /dev/null +++ b/src/datovka_shared/records_management/conversion.cpp @@ -0,0 +1,56 @@ +/* + * 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 "src/datovka_shared/records_management/conversion.h" + +QList createIdList(const QStringList &strList, bool *ok) +{ + QList idList; + bool iOk = false; + foreach (const QString &str, strList) { + qint64 id = str.toLongLong(&iOk); + idList.append(id); + if (!iOk) { + if (ok != Q_NULLPTR) { + *ok = false; + } + qCritical("Cannot convert '%s' into qint64.", + str.toUtf8().constData()); + return QList(); + } + if (id < 0) { + if (ok != Q_NULLPTR) { + *ok = false; + } + qCritical("%s", "Received negative identifier."); + return QList(); + } + } + + if (ok != Q_NULLPTR) { + *ok = true; + } + return idList; +} diff --git a/src/datovka_shared/records_management/conversion.h b/src/datovka_shared/records_management/conversion.h new file mode 100644 index 00000000..bbe2c837 --- /dev/null +++ b/src/datovka_shared/records_management/conversion.h @@ -0,0 +1,36 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +/*! + * @brief Convert list of strings into list of qint64. + * + * @param[in] strList String list. + * @param[out] ok Set to true if all entries are successfully converted. + * @return List if qint64. + */ +QList createIdList(const QStringList &strList, bool *ok); diff --git a/src/datovka_shared/records_management/io/records_management_connection.cpp b/src/datovka_shared/records_management/io/records_management_connection.cpp new file mode 100644 index 00000000..692233cf --- /dev/null +++ b/src/datovka_shared/records_management/io/records_management_connection.cpp @@ -0,0 +1,339 @@ +/* + * 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 "src/datovka_shared/records_management/io/records_management_connection.h" + +/* Must be set to false for production releases. */ +const bool RecordsManagementConnection::ignoreSslErrorsDflt = true; + +#define logFuncCall() \ + qDebug("%s()", __func__) + +/*! + * @brief Converts service identifier onto service name. + */ +static +const QString &serviceName(enum RecordsManagementConnection::ServiceId srvcId) +{ + static const QString SrvServiceInfo(QStringLiteral("service_info")); + static const QString SrvUploadHierarchy(QStringLiteral("upload_hierarchy")); + static const QString SrvUploadFile(QStringLiteral("upload_file")); + static const QString SrvStoredFiles(QStringLiteral("stored_files")); + static const QString InvalidService; + + switch (srvcId) { + case RecordsManagementConnection::SRVC_SERVICE_INFO: + return SrvServiceInfo; + break; + case RecordsManagementConnection::SRVC_UPLOAD_HIERARCHY: + return SrvUploadHierarchy; + break; + case RecordsManagementConnection::SRVC_UPLOAD_FILE: + return SrvUploadFile; + break; + case RecordsManagementConnection::SRVC_STORED_FILES: + return SrvStoredFiles; + break; + default: + Q_ASSERT(0); + return InvalidService; + break; + } +} + +/*! + * @brief Create URL from base URL and from service identifier. + */ +static +QUrl constructUrl(QString baseUrl, + enum RecordsManagementConnection::ServiceId srvcId) +{ + const QString &srvcName(serviceName(srvcId)); + + if (baseUrl.isEmpty() || srvcName.isEmpty()) { + return QUrl(); + } + + if (baseUrl.at(baseUrl.length() - 1) != '/') { + baseUrl += '/'; + } + return QUrl(baseUrl + srvcName); +} + +RecordsManagementConnection::RecordsManagementConnection(bool ignoreSslErrors, + QObject *parent) + : QObject(parent), + m_baseUrlStr(), + m_tokenStr(), + m_agentName(), + m_timeOut(60000), /* Milliseconds. */ + m_ignoreSslErrors(ignoreSslErrors), + m_nam(this) +{ + connect(&m_nam, SIGNAL(sslErrors(QNetworkReply *, const QList)), + this, SLOT(handleSslErrors(QNetworkReply *, const QList))); +} + +void RecordsManagementConnection::setConnection(const QString &baseUrl, + const QString &token) +{ + m_baseUrlStr = baseUrl; + m_tokenStr = token; +} + +bool RecordsManagementConnection::communicate(enum ServiceId srvcId, + const QByteArray &requestData, QByteArray &replyData) +{ + logFuncCall(); + + QNetworkRequest request(createRequest(srvcId)); + + QNetworkReply *reply = sendRequest(request, requestData); + if (reply == Q_NULLPTR) { + return false; + } + + /* Set timeout timer */ + QTimer timer; + timer.setSingleShot(true); + QEventLoop eventLoop; + connect(&timer, SIGNAL(timeout()), &eventLoop, SLOT(quit())); + connect(reply, SIGNAL(finished()), &eventLoop, SLOT(quit())); + timer.start(m_timeOut); + eventLoop.exec(); + + qDebug("Loop exited, reply finished: %d", reply->isFinished()); + QList headerList(reply->rawHeaderList()); + if (!reply->rawHeaderPairs().isEmpty()) { + qDebug("%s", "Received raw headers:"); + foreach (const QNetworkReply::RawHeaderPair &pair, + reply->rawHeaderPairs()) { + qDebug("%s: %s", pair.first.constData(), + pair.second.constData()); + } + } + + bool retVal = false; + replyData.clear(); + + if (timer.isActive()) { + timer.stop(); + retVal = processReply(reply, replyData); + if (!retVal && (Q_NULLPTR != reply)) { + emit connectionError(reply->errorString()); + } + } else { + /* Timeout expired. */ + disconnect(reply, SIGNAL(finished()), &eventLoop, SLOT(quit())); + qCritical("Connection timed out. Check your internet connection."); + reply->abort(); + } + + reply->deleteLater(); reply = Q_NULLPTR; + + return retVal; +} + +static +bool readAndAddCert(const QByteArray &certData, QSsl::EncodingFormat fmt) +{ + if (certData.isEmpty()) { + Q_ASSERT(0); + return false; + } + + QSslCertificate cert(certData, fmt); + if (certData.isNull()) { + return false; + } + + QSslSocket::addDefaultCaCertificate(cert); + + return true; +} + +bool RecordsManagementConnection::addTrustedCertificate(const QString &filePath) +{ + QByteArray certData; + + { + QFile certFile(filePath); + if (!certFile.open(QIODevice::ReadOnly)) { + return false; + } + certData = certFile.readAll(); + certFile.close(); + } + if (certData.isEmpty()) { + return false; + } + + if (readAndAddCert(certData, QSsl::Pem)) { + qDebug("Read PEN certificate '%s'.", + filePath.toUtf8().constData()); + return true; + } else { + qWarning("Supplied certificate '%s' is not in PEM format.", + filePath.toUtf8().constData()); + } + if (readAndAddCert(certData, QSsl::Der)) { + qDebug("Read DER certificate '%s'.", + filePath.toUtf8().constData()); + return true; + } else { + qWarning("Supplied certificate '%s' is not in DER format.", + filePath.toUtf8().constData()); + } + + qCritical("Could not read certificate '%s'.", + filePath.toUtf8().constData()); + return false; +} + +void RecordsManagementConnection::handleSslErrors(QNetworkReply *reply, + const QList &errors) +{ + Q_UNUSED(reply); + + QString errMsg("Unspecified SSL error."); + + if (!errors.isEmpty()) { + QStringList errList; + foreach (const QSslError &error, errors) { + errList.append(error.errorString()); + } + errMsg = errList.join(QStringLiteral("; ")); + } + + qCritical("%s", errMsg.toUtf8().constData()); + emit connectionError(errMsg); + + if (m_ignoreSslErrors) { + qWarning("Ignoring obtained SSL errors."); + emit connectionError(QStringLiteral("Ignoring obtained SSL errors.")); + + if (reply != Q_NULLPTR) { + reply->ignoreSslErrors(); + } + } +} + +QNetworkRequest RecordsManagementConnection::createRequest( + enum ServiceId srvcId) const +{ + logFuncCall(); + + QNetworkRequest request; + + request.setUrl(constructUrl(m_baseUrlStr, srvcId)); + + /* Fill request header. */ + request.setRawHeader("User-Agent", m_agentName.toUtf8()); + request.setRawHeader("Host", request.url().host().toUtf8()); + request.setRawHeader("Authentication", m_tokenStr.toUtf8()); + request.setRawHeader("Accept", "application/json"); + request.setRawHeader("Content-Type", "application/json"); + + return request; +} + +QNetworkReply *RecordsManagementConnection::sendRequest( + const QNetworkRequest &request, const QByteArray &data) +{ + logFuncCall(); + + switch (m_nam.networkAccessible()) { + case QNetworkAccessManager::UnknownAccessibility: + case QNetworkAccessManager::NotAccessible: + qCritical("%s", + "Internet connection is probably not available. Check your network settings."); + return Q_NULLPTR; + break; + default: + break; + } + + QNetworkReply *reply = Q_NULLPTR; + + if (data.isEmpty()) { + reply = m_nam.get(request); + } else { + reply = m_nam.post(request, data); + } + + if (reply == Q_NULLPTR) { + qCritical("%s", "No reply."); + return Q_NULLPTR; + } + + return reply; +} + +bool RecordsManagementConnection::processReply(QNetworkReply *reply, + QByteArray &replyData) +{ + logFuncCall(); + + if (reply == Q_NULLPTR) { + Q_ASSERT(0); + return false; + } + + /* Response status code */ + int statusCode = reply->attribute( + QNetworkRequest::HttpStatusCodeAttribute).toInt(); +#if 0 + /* Store cookies */ + QVariant variantCookies = + reply->header(QNetworkRequest::SetCookieHeader); + QList listOfCookies( + qvariant_cast< QList >(variantCookies)); +#endif + + replyData = reply->readAll(); + + QVariant possibleRedirectUrl; + + switch (statusCode) { + case 200: /* 200 OK */ + break; + case 302: /* 302 Found */ + possibleRedirectUrl = reply->attribute( + QNetworkRequest::RedirectionTargetAttribute); + qWarning("Redirection '%s'?", + possibleRedirectUrl.toString().toUtf8().constData()); + // possibleRedirectUrl.toString(); + break; + default: /* Any other error. */ + qCritical("%s", reply->errorString().toUtf8().constData()); + return false; + break; + } + + return true; +} diff --git a/src/datovka_shared/records_management/io/records_management_connection.h b/src/datovka_shared/records_management/io/records_management_connection.h new file mode 100644 index 00000000..09229b1d --- /dev/null +++ b/src/datovka_shared/records_management/io/records_management_connection.h @@ -0,0 +1,128 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +/*! + * @brief Encapsulates connection to records management service. + */ +class RecordsManagementConnection : public QObject { + Q_OBJECT + +public: + /*! + * @brief Records management service identifiers. + */ + enum ServiceId { + SRVC_SERVICE_INFO, + SRVC_UPLOAD_HIERARCHY, + SRVC_UPLOAD_FILE, + SRVC_STORED_FILES + }; + + /*! + * @brief Use for controlling of global behaviour on SSL errors. + */ + static + const bool ignoreSslErrorsDflt; + + /*! + * @brief Constructor. + */ + explicit RecordsManagementConnection(bool ignoreSslErrors = false, + QObject *parent = Q_NULLPTR); + + /*! + * @brief Set connection data. + * + * @param[in] baseUrl Service base URL. + * @param[in] token Authentication token. + */ + void setConnection(const QString &baseUrl, const QString &token); + + /*! + * @brief Send request and wait for reply. + * + * @param[in] srvcId Srvice identifier. + */ + bool communicate(enum ServiceId srvcId, const QByteArray &requestData, + QByteArray &replyData); + + /*! + * @brief Add certificate to certificate store. + * + * @param[in] filePath Path to certificate file. + * @return True on success. + */ + static + bool addTrustedCertificate(const QString &filePath); + +signals: + /*! + * @brief Emitted when some error during communication occurs. + * + * @param[in] message Message string containing error description. + */ + void connectionError(const QString &message); + +private slots: + void handleSslErrors(QNetworkReply *reply, + const QList &errors); + +private: + /*! + * @brief Create network request. + * + * @param[in] srvcId Srvice identifier. + * @return Created network request. + */ + QNetworkRequest createRequest(enum ServiceId srvcId) const; + + /*! + * @brief Send request. + * + * @param[in] request Network request. + * @param[in] data Data to be sent along with the request. + * @return Null pointer on failure. + */ + QNetworkReply *sendRequest(const QNetworkRequest &request, + const QByteArray &data); + + static + bool processReply(QNetworkReply *reply, QByteArray &replyData); + + QString m_baseUrlStr; /*!< Service base URL. */ + QString m_tokenStr; /*!< Authentication token. */ + QString m_agentName; /*!< Usually the application name. */ + + unsigned int m_timeOut; /*!< Communication timeout. */ + bool m_ignoreSslErrors; /*!< True if SSL errors should be ignored. */ + QNetworkAccessManager m_nam; /*!< Network access manager. */ +}; diff --git a/src/datovka_shared/records_management/json/entry_error.cpp b/src/datovka_shared/records_management/json/entry_error.cpp new file mode 100644 index 00000000..c9ef4661 --- /dev/null +++ b/src/datovka_shared/records_management/json/entry_error.cpp @@ -0,0 +1,252 @@ +/* + * 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 "src/datovka_shared/records_management/json/entry_error.h" +#include "src/datovka_shared/records_management/json/helper.h" + +static +const QString keyCode("code"); +static +const QString keyDescription("description"); + +static const QString strNoError("NO_ERROR"); /* This one should not be used. */ +static const QString strMalformedRequest("MALFORMED_REQUEST"); +static const QString strMissingIdentifier("MISSING_IDENTIFIER"); +static const QString strWrongIdentifier("WRONG_IDENTIFIER"); +static const QString strUnsupportedFileFormat("UNSUPPORTED_FILE_FORMAT"); +static const QString strAlreadyPresent("ALREADY_PRESENT"); +static const QString strLimitExceeded("LIMIT_EXCEEDED"); +static const QString strUnspecified("UNSPECIFIED"); + +ErrorEntry::ErrorEntry(void) + : m_code(ERR_NO_ERROR), + m_description() +{ +} + +ErrorEntry::ErrorEntry(enum Code code, const QString &description) + : m_code(code), + m_description(description) +{ +} + +ErrorEntry::ErrorEntry(const ErrorEntry &ee) + : m_code(ee.m_code), + m_description(ee.m_description) +{ +} + +enum ErrorEntry::Code ErrorEntry::code(void) const +{ + return m_code; +} + +const QString &ErrorEntry::description(void) const +{ + return m_description; +} + +bool ErrorEntry::fromJsonVal(const QJsonValue *jsonVal) +{ + if (jsonVal == Q_NULLPTR) { + Q_ASSERT(0); + return false; + } + + if (jsonVal->isNull()) { + m_code = ERR_NO_ERROR; + m_description.clear(); + return true; + } + + if (!jsonVal->isObject()) { + return false; + } + + QJsonObject jsonObj(jsonVal->toObject()); + + QString codeStr, descrStr; + if (!JsonHelper::readString(jsonObj, keyCode, codeStr, false)) { + return false; + } + if (!JsonHelper::readString(jsonObj, keyDescription, descrStr, false)) { + return false; + } + + bool ok = false; + enum Code code = stringToCode(codeStr, &ok); + if (!ok) { + return false; + } + + m_code = code; + m_description = descrStr; + return true; +} + +bool ErrorEntry::toJsonVal(QJsonValue *jsonVal) const +{ + if (jsonVal == Q_NULLPTR) { + Q_ASSERT(0); + return false; + } + + if (m_code == ERR_NO_ERROR) { + *jsonVal = QJsonValue(); + return true; + } + + QJsonObject jsonObj; + jsonObj.insert(keyCode, codeToString(m_code)); + jsonObj.insert(keyDescription, m_description); + *jsonVal = jsonObj; + return true; +} + +QString ErrorEntry::trVerbose(void) const +{ + QString retStr(codeToString(m_code) + QLatin1String(" (")); + QString explanation; + + switch (m_code) { + case ERR_NO_ERROR: + explanation = tr("No error occurred"); + break; + case ERR_MALFORMED_REQUEST: + explanation = tr("Request was malformed"); + break; + case ERR_MISSING_IDENTIFIER: + explanation = tr("Identifier is missing"); + break; + case ERR_WRONG_IDENTIFIER: + explanation = tr("Supplied identifier is wrong"); + break; + case ERR_UNSUPPORTED_FILE_FORMAT: + explanation = tr("File format is not supported"); + break; + case ERR_ALREADY_PRESENT: + explanation = tr("Data are already present"); + break; + case ERR_LIMIT_EXCEEDED: + explanation = tr("Service limit was exceeded"); + break; + case ERR_UNSPECIFIED: + explanation = tr("Unspecified error"); + break; + default: + Q_ASSERT(0); + explanation = tr("Unknown error"); + break; + } + + retStr += explanation + QLatin1String(")"); + return retStr; +} + +const QString &ErrorEntry::codeToString(enum Code code) +{ + switch (code) { + case ERR_NO_ERROR: + Q_ASSERT(0); /* This one should never occur. */ + return strNoError; + break; + case ERR_MALFORMED_REQUEST: + return strMalformedRequest; + break; + case ERR_MISSING_IDENTIFIER: + return strMissingIdentifier; + break; + case ERR_WRONG_IDENTIFIER: + return strWrongIdentifier; + break; + case ERR_UNSUPPORTED_FILE_FORMAT: + return strUnsupportedFileFormat; + break; + case ERR_ALREADY_PRESENT: + return strAlreadyPresent; + break; + case ERR_LIMIT_EXCEEDED: + return strLimitExceeded; + break; + case ERR_UNSPECIFIED: + return strUnspecified; + break; + default: + Q_ASSERT(0); + return strUnspecified; + break; + } +} + +enum ErrorEntry::Code ErrorEntry::stringToCode(const QString &str, bool *ok) +{ + if (str == strNoError) { + if (ok != Q_NULLPTR) { + *ok = true; + } + return ErrorEntry::ERR_NO_ERROR; + } else if (str == strMalformedRequest) { + if (ok != Q_NULLPTR) { + *ok = true; + } + return ErrorEntry::ERR_MALFORMED_REQUEST; + } else if (str == strMissingIdentifier) { + if (ok != Q_NULLPTR) { + *ok = true; + } + return ERR_MISSING_IDENTIFIER; + } else if (str == strWrongIdentifier) { + if (ok != Q_NULLPTR) { + *ok = true; + } + return ERR_WRONG_IDENTIFIER; + } else if (str == strUnsupportedFileFormat) { + if (ok != Q_NULLPTR) { + *ok = true; + } + return ErrorEntry::ERR_UNSUPPORTED_FILE_FORMAT; + } else if (str == strAlreadyPresent) { + if (ok != Q_NULLPTR) { + *ok = true; + } + return ErrorEntry::ERR_ALREADY_PRESENT; + } else if (str == strLimitExceeded) { + if (ok != Q_NULLPTR) { + *ok = true; + } + return ErrorEntry::ERR_LIMIT_EXCEEDED; + } else if (str == strUnspecified) { + if (ok != Q_NULLPTR) { + *ok = true; + } + return ErrorEntry::ERR_UNSPECIFIED; + } else { + if (ok != Q_NULLPTR) { + *ok = false; + } + return ErrorEntry::ERR_UNSPECIFIED; + } +} diff --git a/src/datovka_shared/records_management/json/entry_error.h b/src/datovka_shared/records_management/json/entry_error.h new file mode 100644 index 00000000..00ebe9e4 --- /dev/null +++ b/src/datovka_shared/records_management/json/entry_error.h @@ -0,0 +1,136 @@ +/* + * 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. + */ + +#pragma once + +#include /* Q_DECLARE_TR_FUNCTIONS() */ +#include + +class QJsonValue; /* Forward declaration. */ + +/*! + * @brief Encapsulates any error entry. + */ +class ErrorEntry { + Q_DECLARE_TR_FUNCTIONS(ErrorEntry) + +public: + /*! + * @brief Error codes. + */ + enum Code { + ERR_NO_ERROR, /*!< Just for convenience. */ + ERR_MALFORMED_REQUEST, /*!< JSON request was corrupt. */ + ERR_MISSING_IDENTIFIER, /*!< JSON request provided no identifier. */ + ERR_WRONG_IDENTIFIER, /*!< Wrong identifier provided in JSON request. */ + ERR_UNSUPPORTED_FILE_FORMAT, /*!< Uploaded file format not supported. */ + ERR_ALREADY_PRESENT, /*!< File is already present. */ + ERR_LIMIT_EXCEEDED, /*!< Too many identifiers in a single request. */ + ERR_UNSPECIFIED /*!< Unspecified error. */ + }; + + /*! + * @brief Constructor. Constructs a no-error entry. + */ + ErrorEntry(void); + + /*! + * @brief Constructor. + * + * @param[in] code Error code. + * @param[in] description Additional error description. + */ + ErrorEntry(enum Code code, const QString &description); + + /*! + * @brief Copy constructor. + * + * @param[in] ee Error entry. + */ + ErrorEntry(const ErrorEntry &ee); + + /*! + * @brief Returns error code. + * + * @return Error code. + */ + enum Code code(void) const; + + /*! + * @brief Returns error description. + * + * @return Error description. + */ + const QString &description(void) const; + + /*! + * @brief Set content according to JSON value. + * + * @note Method does not modify value of error entry if false returned. + * JSON null values are converted into no-error entries. + * + * @param[in] jsonVal Value to read from. + * @return True on success, false else. + */ + bool fromJsonVal(const QJsonValue *jsonVal); + + /*! + * @brief Set content of supplied JSON value. + * + * @note No-error entries are converted into JSON null values. + * + * @param[out] jsonVal Value to store to. + * @return True on success, false else. + */ + bool toJsonVal(QJsonValue *jsonVal) const; + + /*! + * @brief Returns translated error description. + * + * @return String containing localised description. + */ + QString trVerbose(void) const; + +private: + /*! + * @brief Converts error code into string as used in JSON. + * + * @param[in] code Code value to be converted into a string. + * @return JSON string code representation. + */ + static + const QString &codeToString(enum Code code); + + /*! + * @brief Converts error string as used in JSON into error code. + * + * @param[in] str JSON string code representation. + * @param[out] ok Set to true if conversion was ok. + * @return Code value. + */ + static + enum Code stringToCode(const QString &str, bool *ok = Q_NULLPTR); + + enum Code m_code; /*!< Error code. */ + QString m_description; /*!< Error description as obtained from JSON. */ +}; diff --git a/src/datovka_shared/records_management/json/helper.cpp b/src/datovka_shared/records_management/json/helper.cpp new file mode 100644 index 00000000..bbbb7423 --- /dev/null +++ b/src/datovka_shared/records_management/json/helper.cpp @@ -0,0 +1,185 @@ +/* + * 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 "src/datovka_shared/records_management/json/helper.h" + +bool JsonHelper::readRootObject(const QByteArray &json, QJsonObject &jsonObj) +{ + QJsonDocument jsonDoc; + { + QJsonParseError parseErr; + jsonDoc = QJsonDocument::fromJson(json, &parseErr); + if (jsonDoc.isNull()) { + qCritical("Error parsing JSON: %s", + parseErr.errorString().toUtf8().constData()); + return false; + } + } + if (!jsonDoc.isObject()) { + qCritical("%s", "JSON document contains no object."); + return false; + } + + QJsonObject jsonTmpObj(jsonDoc.object()); + if (jsonTmpObj.isEmpty()) { + qCritical("%s", "JSON object is empty."); + return false; + } + + jsonObj = jsonTmpObj; + return true; +} + +bool JsonHelper::readValue(const QJsonObject &jsonObj, const QString &key, + QJsonValue &jsonVal) +{ + if (jsonObj.isEmpty() || key.isEmpty()) { + qCritical("%s", "JSON object or sought key is empty."); + return false; + } + + jsonVal = jsonObj.value(key); + if (jsonVal.isUndefined()) { + qCritical("Missing key '%s' in JSON object.", + key.toUtf8().constData()); + return false; + } + + return true; +} + +bool JsonHelper::readInt(const QJsonObject &jsonObj, const QString &key, + int &val, bool acceptNull) +{ + QJsonValue jsonVal; + if (!readValue(jsonObj, key, jsonVal)) { + return false; + } + if (jsonVal.isNull()) { + val = 0; /* Null value. */ + return acceptNull; + } + int readVal = jsonVal.toInt(-1); + if (readVal == -1) { + qCritical("Value related to key '%s' is not an integer.", + key.toUtf8().constData()); + return false; + } + + val = readVal; + return true; +} + +bool JsonHelper::readString(const QJsonObject &jsonObj, const QString &key, + QString &val, bool acceptNull) +{ + QJsonValue jsonVal; + if (!readValue(jsonObj, key, jsonVal)) { + return false; + } + if (jsonVal.isNull()) { + val = QString(); /* Null string. */ + return acceptNull; + } + if (!jsonVal.isString()) { + qCritical("Value related to key '%s' is not a string.", + key.toUtf8().constData()); + return false; + } + + val = jsonVal.toString(); + return true; +} + +bool JsonHelper::readArray(const QJsonObject &jsonObj, const QString &key, + QJsonArray &arr, bool acceptNull) +{ + QJsonValue jsonVal; + if (!readValue(jsonObj, key, jsonVal)) { + return false; + } + if (jsonVal.isNull()) { + arr = QJsonArray(); /* Null array. */ + return acceptNull; + } + if (!jsonVal.isArray()) { + qCritical("Value related to key '%s' is not an array.", + key.toUtf8().constData()); + return false; + } + + arr = jsonVal.toArray(); + return true; +} + +bool JsonHelper::readStringList(const QJsonObject &jsonObj, const QString &key, + QStringList &val, bool acceptNull) +{ + QJsonArray jsonArr; + if (!readArray(jsonObj, key, jsonArr, acceptNull)) { + return false; + } + + QStringList tmpList; + + foreach (const QJsonValue &jsonVal, jsonArr) { + if (jsonVal.isNull()) { + qCritical("%s", "Found null value in array."); + return false; + } + if (!jsonVal.isString()) { + qCritical("%s", "Found non-string value in array."); + return false; + } + + tmpList.append(jsonVal.toString()); + } + + val = tmpList; + return true; +} + +QString JsonHelper::toIndentedString(const QByteArray &json) +{ + if (json.isEmpty()) { + return QString(); + } + + QJsonDocument jsonDoc; + { + QJsonParseError parseErr; + jsonDoc = QJsonDocument::fromJson(json, &parseErr); + if (jsonDoc.isNull()) { + qCritical("Error parsing JSON: %s", + parseErr.errorString().toUtf8().constData()); + return QString(); + } + } + + return QString::fromUtf8(jsonDoc.toJson(QJsonDocument::Indented)); +} diff --git a/src/datovka_shared/records_management/json/helper.h b/src/datovka_shared/records_management/json/helper.h new file mode 100644 index 00000000..9e4413d1 --- /dev/null +++ b/src/datovka_shared/records_management/json/helper.h @@ -0,0 +1,124 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include + +/*! + * @brief JSON conversion helper functions. + */ +class JsonHelper { +private: + /*! + * @brief Private constructor. + */ + JsonHelper(void); + +public: + /*! + * @brief Reads a JSON object which comprises the document. + * + * @param[in] json JSON data. + * @param[out] jsonObj JSON object from the document. + * @return True on success, false else. + */ + static + bool readRootObject(const QByteArray &json, QJsonObject &jsonObj); + + /*! + * @brief Searches for a value on JSON object. + * + * @param[in] jsonObject Object to search in. + * @param[in] key Key to search for. + * @param[out] jsonVal Found value. + * @return True if key found, false else. + */ + static + bool readValue(const QJsonObject &jsonObj, const QString &key, + QJsonValue &jsonVal); + + /*! + * @brief Reads an integer value from supplied JSON object. + * + * @param[in] jsonObj JSON object. + * @param[in] key Key identifying the string. + * @param[out] val Value to be stored. + * @param[in] acceptNull True if null value should also be accepted. + * @return True on success, false else. + */ + static + bool readInt(const QJsonObject &jsonObj, const QString &key, + int &val, bool acceptNull); + + /*! + * @brief Reads a string value from supplied JSON object. + * + * @param[in] jsonObj JSON object. + * @param[in] key Key identifying the string. + * @param[out] val Value to be stored. + * @param[in] acceptNull True if null value should also be accepted. + * @return True on success, false else. + */ + static + bool readString(const QJsonObject &jsonObj, const QString &key, + QString &val, bool acceptNull); + + /*! + * @brief Reads an arry value from supplied JSON object. + * + * @param[in] jsonObj JSON object. + * @param[in] key Key identifying the string. + * @param[out] arr Array to be stored. + * @param[in] acceptNull True if null value should also be accepted. + * @return True on success, false else. + */ + static + bool readArray(const QJsonObject &jsonObj, const QString &key, + QJsonArray &arr, bool acceptNull); + + /*! + * @brief Reads a string list from supplied JSON object. + * + * @param[in] jsonObj JSON object. + * @param[in] key Key identifying the string list. + * @param[out] val Values to be stored. + * @param[in] acceptNull True if null value should also be accepted. + * @return True on success, false else. + */ + static + bool readStringList(const QJsonObject &jsonObj, const QString &key, + QStringList &val, bool acceptNull); + + /*! + * @brief Converts JSON document to indented string. + * + * @param[in] json JSON data. + * @retunr Non-empty indented string if JSON data could be read. + */ + static + QString toIndentedString(const QByteArray &json); +}; diff --git a/src/datovka_shared/records_management/json/service_info.cpp b/src/datovka_shared/records_management/json/service_info.cpp new file mode 100644 index 00000000..273c17c4 --- /dev/null +++ b/src/datovka_shared/records_management/json/service_info.cpp @@ -0,0 +1,135 @@ +/* + * 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 "src/datovka_shared/records_management/json/helper.h" +#include "src/datovka_shared/records_management/json/service_info.h" + +static +const QString keyLogoSvg("logo_svg"); +static +const QString keyName("name"); +static +const QString keyTokenName("token_name"); + +ServiceInfoResp::ServiceInfoResp(void) + : m_logoSvg(), + m_name(), + m_tokenName() +{ +} + +ServiceInfoResp::ServiceInfoResp(const QByteArray &logoSvg, const QString &name, + const QString &tokenName) + : m_logoSvg(logoSvg), + m_name(name), + m_tokenName(tokenName) +{ +} + +ServiceInfoResp::ServiceInfoResp(const ServiceInfoResp &sir) + : m_logoSvg(sir.m_logoSvg), + m_name(sir.m_name), + m_tokenName(sir.m_tokenName) +{ +} + +const QByteArray &ServiceInfoResp::logoSvg(void) const +{ + return m_logoSvg; +} + +const QString &ServiceInfoResp::name(void) const +{ + return m_name; +} + +const QString &ServiceInfoResp::tokenName(void) const +{ + return m_tokenName; +} + +bool ServiceInfoResp::isValid(void) const +{ + return !m_logoSvg.isNull() && !m_name.isNull() && !m_tokenName.isNull(); +} + +ServiceInfoResp ServiceInfoResp::fromJson(const QByteArray &json, bool *ok) +{ + QJsonObject jsonObj; + if (!JsonHelper::readRootObject(json, jsonObj)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return ServiceInfoResp(); + } + + ServiceInfoResp sir; + + { + QString valStr; + if (!JsonHelper::readString(jsonObj, keyLogoSvg, valStr, + false)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return ServiceInfoResp(); + } + + sir.m_logoSvg = QByteArray::fromBase64(valStr.toUtf8()); + } + + if (!JsonHelper::readString(jsonObj, keyName, sir.m_name, false)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return ServiceInfoResp(); + } + if (!JsonHelper::readString(jsonObj, keyTokenName, sir.m_tokenName, + false)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return ServiceInfoResp(); + } + + if (ok != Q_NULLPTR) { + *ok = true; + } + return sir; +} + +QByteArray ServiceInfoResp::toJson(void) const +{ + QJsonObject jsonObj; + jsonObj.insert(keyLogoSvg, !m_logoSvg.isNull() ? + QString::fromUtf8(m_logoSvg.toBase64()) : QJsonValue()); + jsonObj.insert(keyName, !m_name.isNull() ? m_name : QJsonValue()); + jsonObj.insert(keyTokenName, !m_tokenName.isNull() ? + m_tokenName : QJsonValue()); + + return QJsonDocument(jsonObj).toJson(QJsonDocument::Indented); +} diff --git a/src/datovka_shared/records_management/json/service_info.h b/src/datovka_shared/records_management/json/service_info.h new file mode 100644 index 00000000..0645c2cb --- /dev/null +++ b/src/datovka_shared/records_management/json/service_info.h @@ -0,0 +1,108 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +/*! + * @brief Encapsulates the service_info response. + */ +class ServiceInfoResp { +private: + /*! + * @brief Constructor. Creates an invalid structure. + */ + ServiceInfoResp(void); + + /*! + * @brief Constructor. + * + * @param[in] logoSvg SVG logo stored as raw data. + * @param[in] name Service provider name. + * @param[in] tokenMame Security token name. + */ + ServiceInfoResp(const QByteArray &logoSvg, const QString &name, + const QString &tokenName); + +public: + /*! + * @brief Copy constructor. + * + * @param[in] sir Service info response. + */ + ServiceInfoResp(const ServiceInfoResp &sir); + + /*! + * @brief Return raw SVG data. + * + * @return Stored raw SVG data. + */ + const QByteArray &logoSvg(void) const; + + /*! + * @brief Return service provider name. + * + * @return Stored name. + */ + const QString &name(void) const; + + /*! + * @brief Return security token name. + * + * @return Stored token name. + */ + const QString &tokenName(void) const; + + /*! + * @brief Check whether content is valid. + * + * @return True if content is valid. + */ + bool isValid(void) const; + + /*! + * @brief Creates a service info structure from supplied JSON document. + * + * @param[in] json JSON document. + * @param[out] ok Set to true on success. + * @return Invalid structure on error a valid structure else. + */ + static + ServiceInfoResp fromJson(const QByteArray &json, bool *ok = Q_NULLPTR); + + /*! + * @brief Converts service info structure into a JSON document. + * + * @note Unspecified values are stores as null into the JSON document. + * + * @return JSON document containing stored data. + */ + QByteArray toJson(void) const; + +private: + QByteArray m_logoSvg; /*!< Raw SVG data. */ + QString m_name; /*!< Service provider name. */ + QString m_tokenName; /*!< Obtained token identifier. */ +}; diff --git a/src/datovka_shared/settings/records_management.cpp b/src/datovka_shared/settings/records_management.cpp new file mode 100644 index 00000000..6f26cfe2 --- /dev/null +++ b/src/datovka_shared/settings/records_management.cpp @@ -0,0 +1,289 @@ +/* + * 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 "src/datovka_shared/crypto/crypto_pwd.h" +#include "src/datovka_shared/crypto/crypto_wrapped.h" +#include "src/log/log.h" +#include "src/datovka_shared/settings/records_management.h" + +/* Records management configuration entry names. */ +namespace RMNames { + const QLatin1String rmGroup("records_management"); + + const QLatin1String url("location_url"); + const QLatin1String token("access_token"); + const QLatin1String tokenAlg("access_token_alg"); + const QLatin1String tokenSalt("access_token_salt"); + const QLatin1String tokenIv("access_token_iv"); + const QLatin1String tokenCode("access_token_code"); +} + +RecordsManagementSettings globRecordsManagementSet; + +RecordsManagementSettings::RecordsManagementSettings(void) + : m_url(), + m_token(), + m_tokenAlg(), + m_tokenSalt(), + m_tokenIv(), + m_tokenCode() +{ +} + +bool RecordsManagementSettings::isValid(void) const +{ + /* URL and plain or encrypted token. */ + return !m_url.isEmpty() && (!m_token.isEmpty() || + (!m_tokenAlg.isEmpty() && !m_tokenSalt.isEmpty() && + !m_tokenIv.isEmpty() && !m_tokenCode.isEmpty())); +} + +const QString &RecordsManagementSettings::url(void) const +{ + return m_url; +} + +void RecordsManagementSettings::setUrl(const QString &url) +{ + m_url = url; +} + +const QString &RecordsManagementSettings::token(void) const +{ + return m_token; +} + +void RecordsManagementSettings::setToken(const QString &token) +{ + m_token = token; +} + +const QString &RecordsManagementSettings::tokenAlg(void) const +{ + return m_tokenAlg; +} + +void RecordsManagementSettings::setTokenAlg(const QString &tokenAlg) +{ + m_tokenAlg = tokenAlg; +} + +const QByteArray &RecordsManagementSettings::tokenSalt(void) const +{ + return m_tokenSalt; +} + +void RecordsManagementSettings::setTokenSalt(const QByteArray &tokenSalt) +{ + m_tokenSalt = tokenSalt; +} + +const QByteArray &RecordsManagementSettings::tokenIv(void) const +{ + return m_tokenIv; +} + +void RecordsManagementSettings::setTokenIv(const QByteArray &tokenIv) +{ + m_tokenIv = tokenIv; +} + +const QByteArray &RecordsManagementSettings::tokenCode(void) const +{ + return m_tokenCode; +} + +void RecordsManagementSettings::setTokenCode(const QByteArray &tokenCode) +{ + m_tokenCode = tokenCode; +} + +void RecordsManagementSettings::decryptToken(const QString &oldPin) +{ + if (!m_token.isEmpty()) { + /* Token already stored in decrypted form. */ + logDebugLv0NL("%s", + "Records management service token already held in decrypted form."); + return; + } + + if (oldPin.isEmpty()) { + /* + * Old PIN not given, token already should be in plain format. + */ + logDebugLv0NL("%s", + "No PIN supplied to decrypt records management service token."); + return; + } + + if (!m_tokenAlg.isEmpty() && !m_tokenSalt.isEmpty() && + !m_tokenCode.isEmpty()) { + logDebugLv0NL("%s", + "Decrypting records management service token."); + QString decrypted(decryptPwd(m_tokenCode, oldPin, m_tokenAlg, + m_tokenSalt, m_tokenIv)); + if (decrypted.isEmpty()) { + logWarningNL("%s", + "Failed decrypting records management service token."); + } + + /* Store token. */ + if (!decrypted.isEmpty()) { + m_token = decrypted; + } + } +} + +/*! + * @brief Restores token value, + * + * @note The PIN value may not be known when the settings are read. Therefore + * token decryption is performed somewhere else. + * + * @param[in,out] rmData Records management data to store token into. + * @param[in] settings Settings structure. + * @param[in] groupName Settings group name. + */ +static +void readTokenData(RecordsManagementSettings &rmData, const QSettings &settings, + const QString &groupName) +{ + QString prefix; + if (!groupName.isEmpty()) { + prefix = groupName + QLatin1String("/"); + } + + { + QString token(settings.value(prefix + RMNames::token, + QString()).toString()); + rmData.setToken( + QString::fromUtf8(QByteArray::fromBase64(token.toUtf8()))); + } + + rmData.setTokenAlg(settings.value(prefix + RMNames::tokenAlg, + QString()).toString()); + + rmData.setTokenSalt(QByteArray::fromBase64( + settings.value(prefix + RMNames::tokenSalt, + QString()).toString().toUtf8())); + + rmData.setTokenIv(QByteArray::fromBase64( + settings.value(prefix + RMNames::tokenIv, + QString()).toString().toUtf8())); + + rmData.setTokenCode(QByteArray::fromBase64( + settings.value(prefix + RMNames::tokenCode, + QString()).toString().toUtf8())); + + if (!rmData.token().isEmpty() && !rmData.tokenCode().isEmpty()) { + logWarningNL("%s", + "Records management has both encrypted and unencrypted token set."); + } +} + +void RecordsManagementSettings::loadFromSettings(const QSettings &settings) +{ + const QString prefix(RMNames::rmGroup + QLatin1String("/")); + + m_url = settings.value(prefix + RMNames::url, QString()).toString(); + readTokenData(*this, settings, RMNames::rmGroup); + + if (!isValid()) { + m_url.clear(); + m_token.clear(); + m_tokenAlg.clear(); + m_tokenSalt.clear(); + m_tokenIv.clear(); + m_tokenCode.clear(); + } +} + +/*! + * @brief Stores encrypted token into settings. + * + * @param[in] pinVal PIN value to be used for token encryption. + * @param[in,out] settings Settings structure. + * @param[in] rmData records management data to be stored. + * @param[in] token Token to be stored. + */ +static +bool storeEncryptedToken(const QString &pinVal, QSettings &settings, + const RecordsManagementSettings &rmData, const QString &token) +{ + /* Currently only one cryptographic algorithm is supported. */ + const struct pwd_alg *pwdAlgDesc = &aes256_cbc; + + /* Ignore the algorithm settings. */ + const QString tokenAlg(aes256_cbc.name); + QByteArray tokenSalt(rmData.tokenSalt()); + QByteArray tokenIV(rmData.tokenIv()); + + if (tokenSalt.size() < pwdAlgDesc->key_len) { + tokenSalt = randomSalt(pwdAlgDesc->key_len); + } + + if (tokenIV.size() < pwdAlgDesc->iv_len) { + tokenIV = randomSalt(pwdAlgDesc->iv_len); + } + + QByteArray tokenCode = encryptPwd(token, pinVal, pwdAlgDesc->name, + tokenSalt, tokenIV); + if (tokenCode.isEmpty()) { + return false; + } + + settings.setValue(RMNames::tokenAlg, tokenAlg); + settings.setValue(RMNames::tokenSalt, + QString::fromUtf8(tokenSalt.toBase64())); + settings.setValue(RMNames::tokenIv, + QString::fromUtf8(tokenIV.toBase64())); + settings.setValue(RMNames::tokenCode, + QString::fromUtf8(tokenCode.toBase64())); + + return true; +} + +void RecordsManagementSettings::saveToSettings(const QString &pinVal, + QSettings &settings) const +{ + if (!m_url.isEmpty() && !m_token.isEmpty()) { + settings.beginGroup(RMNames::rmGroup); + + settings.setValue(RMNames::url, m_url); + + bool writePlainToken = pinVal.isEmpty(); + if (!writePlainToken) { + writePlainToken = !storeEncryptedToken(pinVal, settings, + *this, m_token); + } + if (writePlainToken) { /* Only when plain or encryption fails. */ + /* Store unencrypted token. */ + settings.setValue(RMNames::token, + QString(m_token.toUtf8().toBase64())); + } + + settings.endGroup(); + } +} diff --git a/src/datovka_shared/settings/records_management.h b/src/datovka_shared/settings/records_management.h new file mode 100644 index 00000000..166a64df --- /dev/null +++ b/src/datovka_shared/settings/records_management.h @@ -0,0 +1,166 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +/* + * @brief Encapsulates records management service settings. + */ +class RecordsManagementSettings { +public: + /*! + * @brief Constructor. + */ + RecordsManagementSettings(void); + + /*! + * @brief Test whether records management service is set. + * + * @note This method does not actually check the service availability. + * + * @return True is URL and token are set. + */ + bool isValid(void) const; + + /*! + * @brief Get service URL. + * + * @return Service URL. + */ + const QString &url(void) const; + + /*! + * @brief Set service URL. + * + * @param[in] url Service URL. + */ + void setUrl(const QString &url); + + /*! + * @brief Get service token. + * + * @return Service token. + */ + const QString &token(void) const; + + /*! + * @brief Set service token. + * + * @param[in] token Service token. + */ + void setToken(const QString &token); + + /*! + * @brief Get token encryption algorithm name. + * + * @return Algorithm name. + */ + const QString &tokenAlg(void) const; + + /*! + * @brief Set token encryption algorithm name. + * + * @param[in] tokenAlg Algorithm name. + */ + void setTokenAlg(const QString &tokenAlg); + + /*! + * @brief Get salt data. + * + * @return Salt. + */ + const QByteArray &tokenSalt(void) const; + + /*! + * @brief Set salt data. + * + * @param[in] tokenSalt data. + */ + void setTokenSalt(const QByteArray &tokenSalt); + + /*! + * @brief Get initialisation vector data. + * + * @return Initialisation vector. + */ + const QByteArray &tokenIv(void) const; + + /*! + * @brief Set initialisation vector data. + * + * @param[in] tokenIv Initialisation vector. + */ + void setTokenIv(const QByteArray &tokenIv); + + /*! + * @brief Get encrypted token data. + * + * @return Encrypted token. + */ + const QByteArray &tokenCode(void) const; + + /*! + * @brief Set encrypted token data. + * + * @param[in] tokenCode Encrypted token. + */ + void setTokenCode(const QByteArray &tokenCode); + + /*! + * @brief Used to decrypt the token. + * + * @param[in] oldPin PIN value used to decrypt old tokens. + */ + void decryptToken(const QString &oldPin); + + /*! + * @brief Load data from supplied settings. + * + * @param[in] settings Settings structure. + */ + void loadFromSettings(const QSettings &settings); + + /*! + * @brief Store data to settings structure. + * + * @param[in] pinVal PIN value to be used for password encryption. + * @param[out] settings Settings structure. + */ + void saveToSettings(const QString &pinVal, QSettings &settings) const; + +private: + QString m_url; /*!< Service URL. */ + QString m_token; /*!< Service access token. */ + QString m_tokenAlg; /*!< Cryptographic algorithm used to encrypt the token. */ + QByteArray m_tokenSalt; /*!< Salt data. */ + QByteArray m_tokenIv; /*!< Initialisation vector data. */ + QByteArray m_tokenCode; /*!< Encrypted token. */ +}; + +/*! + * @brief Global instance of the structure. + */ +extern RecordsManagementSettings globRecordsManagementSet; diff --git a/src/main.cpp b/src/main.cpp index b04ca20a..37759080 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -32,6 +32,7 @@ #include #include "src/accounts.h" +#include "src/datovka_shared/io/records_management_db.h" #include "src/dialogues/qml_dialogue_helper.h" #include "src/dialogues/qml_input_dialogue.h" #include "src/dialogues/dialogues.h" @@ -53,6 +54,7 @@ #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.h" #include "src/settings.h" #include "src/setwrapper.h" #include "src/sqlite/db_tables.h" @@ -289,6 +291,7 @@ int main(int argc, char *argv[]) Files files; IsdsWrapper isds; GlobalSettingsQmlWrapper settings; + RecordsManagement recordsManagement; StringManipulation strManipulation; Zfo zfo; @@ -337,6 +340,7 @@ int main(int argc, char *argv[]) InteractionZfoFile interactionZfoFile; + /* Inicialize app delegate component for interaction with iOS * Reaction on the iOS action "Open in..." */ #if defined Q_OS_IOS @@ -354,6 +358,7 @@ int main(int argc, char *argv[]) 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); @@ -370,21 +375,19 @@ int main(int argc, char *argv[]) AccountDb globAccountDb("ACCOUNTS", false); globAccountDbPtr = &globAccountDb; - { - QString dirName( - existingWritableLocation(QStandardPaths::AppDataLocation)); - if (dirName.isEmpty()) { - logErrorNL("%s", - "Cannot determine application data location."); - Q_ASSERT(0); - 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 (!globAccountDbPtr->openDb(dbPath, SQLiteDb::CREATE_MISSING)) { - logErrorNL("%s", "Account database not found!"); - return EXIT_FAILURE; - } + QString dbPath(dirName + QDir::separator() + ACNT_DB_NAME); + if (!globAccountDbPtr->openDb(dbPath, SQLiteDb::CREATE_MISSING)) { + logErrorNL("%s", "Account database not found!"); + return EXIT_FAILURE; } /* init message db container */ @@ -399,6 +402,22 @@ int main(int argc, char *argv[]) ZfoDb globZfoDb("ZFOS", false); globZfoDbPtr = &globZfoDb; + /* init and open records management database */ + globRecordsManagementDbPtr = new (std::nothrow) + RecordsManagementDb("recordsManagementDb", false); + if (Q_NULLPTR == globRecordsManagementDbPtr) { + logErrorNL("%s", "Cannot allocate records management db."); + return EXIT_FAILURE; + } + /* Open records management database. */ + QString rmDbPath(dirName + QDir::separator() + RECORDS_MANAGEMENT_DB_FILE); + if (!globRecordsManagementDbPtr->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(), diff --git a/src/records_management.cpp b/src/records_management.cpp new file mode 100644 index 00000000..61e3287b --- /dev/null +++ b/src/records_management.cpp @@ -0,0 +1,101 @@ +/* + * 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 "src/datovka_shared/io/records_management_db.h" +#include "src/datovka_shared/records_management/json/service_info.h" +#include "src/records_management.h" + +RecordsManagement::RecordsManagement(QObject *parent) + : QObject(parent) +{ +} + +void RecordsManagement::callServiceInfo(const QString &urlStr, const QString &tokenStr) +{ + QByteArray response; + RecordsManagementConnection m_rmc(RecordsManagementConnection::ignoreSslErrorsDflt, Q_NULLPTR); + m_rmc.setConnection(urlStr.trimmed(), tokenStr.trimmed()); + + if (m_rmc.communicate(RecordsManagementConnection::SRVC_SERVICE_INFO, QByteArray(), response)) { + if (!response.isEmpty()) { + bool ok = false; + ServiceInfoResp siRes( + ServiceInfoResp::fromJson(response, &ok)); + if (!ok || !siRes.isValid()) { + QMessageBox::critical(Q_NULLPTR, tr("Communication Error"), tr("Received invalid response.")); + return; + } + emit serviceInfo(siRes.logoSvg(), siRes.name(), siRes.tokenName()); + } else { + QMessageBox::critical(Q_NULLPTR, tr("Communication Error"), tr("Received empty response.")); + return; + } + } else { + return; + } +} + +void RecordsManagement::loadStoredServiceInfo(void) +{ + if (Q_NULLPTR == globRecordsManagementDbPtr) { + return; + } + + RecordsManagementDb::ServiceInfoEntry entry( + globRecordsManagementDbPtr->serviceInfo()); + if (!entry.isValid()) { + return; + } + + emit serviceInfo(entry.logoSvg, entry.name, entry.tokenName); +} + +bool RecordsManagement::updateServiceInfo(const QString &urlStr, + const QString &urlStrSettings, const QString &srLogo, + const QString &srName, const QString &srToken) +{ + if (Q_NULLPTR == globRecordsManagementDbPtr) { + return false; + } + + if (!urlStr.trimmed().isEmpty()) { + Q_ASSERT(!urlStr.trimmed().isEmpty()); + + RecordsManagementDb::ServiceInfoEntry entry; + entry.url = urlStr.trimmed(); + entry.name = srName; + entry.tokenName = srToken; + entry.logoSvg = srLogo.toUtf8(); + globRecordsManagementDbPtr->updateServiceInfo(entry); + + if (urlStrSettings != urlStr) { + globRecordsManagementDbPtr->deleteAllStoredMsg(); + } + } else { + globRecordsManagementDbPtr->deleteAllEntries(); + } + + return true; +} diff --git a/src/records_management.h b/src/records_management.h new file mode 100644 index 00000000..25e90855 --- /dev/null +++ b/src/records_management.h @@ -0,0 +1,79 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include "src/datovka_shared/records_management/io/records_management_connection.h" +#include "src/datovka_shared/settings/records_management.h" + +class RecordsManagement : public QObject { + Q_OBJECT + +public: + /*! + * @brief Constructor. + */ + RecordsManagement(QObject *parent = Q_NULLPTR); + + /*! + * @brief Calls service info and displays results. + * + * @param[in] urlStr records management url string. + * @param[in] tokenStr records management token string. + */ + Q_INVOKABLE + void callServiceInfo(const QString &urlStr, const QString &tokenStr); + + /*! + * @brief Loads service information from storage. + */ + Q_INVOKABLE + void loadStoredServiceInfo(void); + + /*! + * @brief Update record management settings. + * + * @param[in] urlStr new records management url string. + * @param[in] urlStrSettings Records management url string from settings. + * @param[in] srLogo Service SVG logo. + * @param[in] srName Service name. + * @param[in] srToken Service token. + * @return True when data have been updated, false else. + */ + Q_INVOKABLE + bool updateServiceInfo(const QString &urlStr, const QString &urlStrSettings, + const QString &srLogo, const QString &srName, const QString &srToken); + +signals: + + /*! + * @brief Send service info to QML. + * + * @param[in] srLogo Service SVG logo. + * @param[in] srName Service name. + * @param[in] srToken Service token. + */ + void serviceInfo(QString srLogo, QString srName, QString srToken); +}; diff --git a/src/settings.cpp b/src/settings.cpp index 3cb081af..d1d93a98 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2017 CZ.NIC + * 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 @@ -26,6 +26,7 @@ #include #include +#include "src/datovka_shared/settings/records_management.h" #include "src/io/filesystem.h" #include "src/log/log.h" #include "src/settings.h" @@ -130,6 +131,9 @@ void Settings::saveToSettings(QSettings &settings) const * The PIN settings are newly kept in a separate group. */ PinSettings::saveToSettings(settings); + + /* Records management settings. */ + globRecordsManagementSet.saveToSettings(globSet._pinVal, settings); } void Settings::loadFromSettings(const QSettings &settings) @@ -194,6 +198,10 @@ void Settings::loadFromSettings(const QSettings &settings) if (dbsLocation.isEmpty()) { dbsLocation = getDefaultDbAndConfigLocation(); } + + /* Records management settings. */ + globRecordsManagementSet.loadFromSettings(settings); + globRecordsManagementSet.decryptToken(globSet._pinVal); } QString Settings::settingsPath(void) diff --git a/src/setwrapper.cpp b/src/setwrapper.cpp index e974c825..7328750b 100644 --- a/src/setwrapper.cpp +++ b/src/setwrapper.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2017 CZ.NIC + * 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 @@ -25,6 +25,7 @@ #include "src/datovka_shared/crypto/crypto_pin.h" #include "src/datovka_shared/crypto/crypto_wrapped.h" +#include "src/datovka_shared/settings/records_management.h" #include "src/dialogues/dialogues.h" #include "src/models/accountmodel.h" #include "src/io/filesystem.h" @@ -136,6 +137,26 @@ void GlobalSettingsQmlWrapper::updatePinSettings(const QString &pinValue) PinSettings::updatePinSettings(globSet, pinValue); } +QString GlobalSettingsQmlWrapper::rmUrl(void) const +{ + return globRecordsManagementSet.url(); +} + +void GlobalSettingsQmlWrapper::setRmUrl(const QString &rmUrl) +{ + globRecordsManagementSet.setUrl(rmUrl); +} + +QString GlobalSettingsQmlWrapper::rmToken(void) const +{ + return globRecordsManagementSet.token(); +} + +void GlobalSettingsQmlWrapper::setRmToken(const QString &rmToken) +{ + globRecordsManagementSet.setToken(rmToken); +} + void GlobalSettingsQmlWrapper::verifyPin(const QString &pinValue) { if (!globSet.pinConfigured()) { diff --git a/src/setwrapper.h b/src/setwrapper.h index fb5782fd..c49b73d2 100644 --- a/src/setwrapper.h +++ b/src/setwrapper.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2017 CZ.NIC + * 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 @@ -21,8 +21,7 @@ * the two. */ -#ifndef _SETWRAPPER_H_ -#define _SETWRAPPER_H_ +#pragma once #include #include @@ -248,6 +247,38 @@ public: Q_INVOKABLE static void setLastUpdateToNow(void); + /*! + * @brief Get records management url string. + * + * @return Records management url string. + */ + Q_INVOKABLE + QString rmUrl(void) const; + + /*! + * @brief Set records management url string. + * + * @param[in] rmUrl records management url string. + */ + Q_INVOKABLE + void setRmUrl(const QString &rmUrl); + + /*! + * @brief Get records management token string. + * + * @return Records management token string. + */ + Q_INVOKABLE + QString rmToken(void) const; + + /*! + * @brief Set records management token string. + * + * @param[in] rmToken records management token string. + */ + Q_INVOKABLE + void setRmToken(const QString &rmToken); + /*! * @brief Gathers all settings and stores them into configuration file. * @@ -288,5 +319,3 @@ signals: */ void statusBarTextChanged(QString txt, bool busy); }; - -#endif /* _SETWRAPPER_H_ */ diff --git a/src/sqlite/db_tables.cpp b/src/sqlite/db_tables.cpp index a690975d..6ccfd1de 100644 --- a/src/sqlite/db_tables.cpp +++ b/src/sqlite/db_tables.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2017 CZ.NIC + * 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 @@ -423,3 +423,69 @@ namespace ZfoSizeCntTbl { SQLiteTbl zfoSizeCntTbl(ZfoSizeCntTbl::tabName, ZfoSizeCntTbl::knownAttrs, ZfoSizeCntTbl::attrProps, ZfoSizeCntTbl::colConstraints, ZfoSizeCntTbl::tblConstraint); + +namespace SrvcInfTbl { + const QString tabName("service_info"); + + const QVector< QPair > knownAttrs = { + {"url", DB_TEXT}, /* NOT NULL */ + {"name", DB_TEXT}, + {"token_name", DB_TEXT}, + {"logo_svg", DB_TEXT} + /* + * PRIMARY KEY (url) + */ + }; + + const QMap colConstraints = { + {"url", "NOT NULL"} + }; + + const QString tblConstraint( + ",\n" + " PRIMARY KEY (url)" + ); + + const QMap attrProps = { + {"url", {DB_TEXT, ""}}, + {"name", {DB_TEXT, ""}}, + {"token_name", {DB_TEXT, ""}}, + {"logo_svg", {DB_TEXT, ""}} + }; +} /* namespace SrvcInfTbl */ +SQLiteTbl srvcInfTbl(SrvcInfTbl::tabName, SrvcInfTbl::knownAttrs, + SrvcInfTbl::attrProps, SrvcInfTbl::colConstraints, + SrvcInfTbl::tblConstraint); + +namespace StrdFlsMsgsTbls { + const QString tabName("stored_files_messages"); + + const QVector< QPair > knownAttrs = { + {"dm_id", DB_INTEGER}, /* NOT NULL */ + {"separator", DB_TEXT}, /* NOT NULL */ + {"joined_locations", DB_TEXT} /* NOT NULL */ + /* + * PRIMARY KEY (dm_id) + */ + }; + + const QMap colConstraints = { + {"dm_id", "NOT NULL"}, + {"separator", "NOT NULL"}, + {"joined_locations", "NOT NULL"} + }; + + const QString tblConstraint( + ",\n" + " PRIMARY KEY (dm_id)" + ); + + const QMap attrProps = { + {"dm_id", {DB_INTEGER, ""}}, + {"separator", {DB_TEXT, ""}}, + {"joined_locations", {DB_TEXT, ""}} + }; +} /* namespace StrdFlsMsgsTbls */ +SQLiteTbl strdFlsMsgsTbl(StrdFlsMsgsTbls::tabName, StrdFlsMsgsTbls::knownAttrs, + StrdFlsMsgsTbls::attrProps, StrdFlsMsgsTbls::colConstraints, + StrdFlsMsgsTbls::tblConstraint); diff --git a/src/sqlite/db_tables.h b/src/sqlite/db_tables.h index f83845fa..13675db6 100644 --- a/src/sqlite/db_tables.h +++ b/src/sqlite/db_tables.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2017 CZ.NIC + * 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 @@ -21,8 +21,7 @@ * the two. */ -#ifndef _DB_TABLES_H_ -#define _DB_TABLES_H_ +#pragma once #include "src/datovka_shared/io/sqlite/table.h" @@ -53,4 +52,8 @@ extern SQLiteTbl msgZfoTbl; /*!< Table 'message_zfos'. */ */ extern SQLiteTbl zfoSizeCntTbl; /*!< Table 'zfo_size_cnt'. */ -#endif /* _DB_TABLES_H_ */ +/* + * Records management database. + */ +extern SQLiteTbl srvcInfTbl; /*!< Table 'service_info'. */ +extern SQLiteTbl strdFlsMsgsTbl; /*!< Table 'stored_files_messages'. */ -- GitLab From c4b8fabde34d8298b82eea72f6069c4b2faa3d48 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Mon, 29 Jan 2018 12:49:35 +0100 Subject: [PATCH 03/63] Added another records management json files --- mobile-datovka.pro | 7 +- .../records_management/json/stored_files.cpp | 517 ++++++++++++++++++ .../records_management/json/stored_files.h | 340 ++++++++++++ .../records_management/json/upload_file.cpp | 260 +++++++++ .../records_management/json/upload_file.h | 196 +++++++ .../json/upload_hierarchy.cpp | 369 +++++++++++++ .../json/upload_hierarchy.h | 203 +++++++ 7 files changed, 1891 insertions(+), 1 deletion(-) create mode 100644 src/datovka_shared/records_management/json/stored_files.cpp create mode 100644 src/datovka_shared/records_management/json/stored_files.h create mode 100644 src/datovka_shared/records_management/json/upload_file.cpp create mode 100644 src/datovka_shared/records_management/json/upload_file.h create mode 100644 src/datovka_shared/records_management/json/upload_hierarchy.cpp create mode 100644 src/datovka_shared/records_management/json/upload_hierarchy.h diff --git a/mobile-datovka.pro b/mobile-datovka.pro index 434f7315..7ef6ab28 100644 --- a/mobile-datovka.pro +++ b/mobile-datovka.pro @@ -97,6 +97,9 @@ SOURCES += \ src/datovka_shared/records_management/json/entry_error.cpp \ src/datovka_shared/records_management/json/helper.cpp \ src/datovka_shared/records_management/json/service_info.cpp \ + src/datovka_shared/records_management/json/stored_files.cpp \ + src/datovka_shared/records_management/json/upload_file.cpp \ + src/datovka_shared/records_management/json/upload_hierarchy.cpp \ src/datovka_shared/settings/pin.cpp \ src/datovka_shared/settings/records_management.cpp \ src/datovka_shared/utility/strings.cpp \ @@ -170,6 +173,9 @@ HEADERS += \ src/datovka_shared/records_management/json/entry_error.h \ src/datovka_shared/records_management/json/helper.h \ src/datovka_shared/records_management/json/service_info.h \ + src/datovka_shared/records_management/json/stored_files.h \ + src/datovka_shared/records_management/json/upload_file.h \ + src/datovka_shared/records_management/json/upload_hierarchy.h \ src/datovka_shared/settings/pin.h \ src/datovka_shared/settings/records_management.h \ src/datovka_shared/utility/strings.h \ @@ -228,7 +234,6 @@ HEADERS += \ src/worker/task_send_message.h \ src/worker/task_send_sms.h \ src/zfo.h - android { SOURCES += \ src/os_android.cpp diff --git a/src/datovka_shared/records_management/json/stored_files.cpp b/src/datovka_shared/records_management/json/stored_files.cpp new file mode 100644 index 00000000..995ce6a2 --- /dev/null +++ b/src/datovka_shared/records_management/json/stored_files.cpp @@ -0,0 +1,517 @@ +/* + * 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 "src/datovka_shared/records_management/conversion.h" +#include "src/datovka_shared/records_management/json/helper.h" +#include "src/datovka_shared/records_management/json/stored_files.h" + +static +const QString keyDmIds("dm_ids"); +static +const QString keyDiIds("di_ids"); + +static +const QString keyDmId("dm_id"); +static +const QString keyDiId("di_id"); +static +const QString keyLocations("locations"); + +static +const QString keyDms("dms"); +static +const QString keyDis("dis"); +static +const QString keyLimit("limit"); +static +const QString keyError("error"); + +StoredFilesReq::StoredFilesReq(void) + : m_dmIds(), + m_diIds() +{ +} + +StoredFilesReq::StoredFilesReq(const QList &dmIds, + const QList &diIds) + : m_dmIds(dmIds), + m_diIds(diIds) +{ +} + +StoredFilesReq::StoredFilesReq(const StoredFilesReq &sfr) + : m_dmIds(sfr.m_dmIds), + m_diIds(sfr.m_diIds) +{ +} + +const QList &StoredFilesReq::dmIds(void) const +{ + return m_dmIds; +} + +const QList &StoredFilesReq::diIds(void) const +{ + return m_diIds; +} + +bool StoredFilesReq::isValid(void) const +{ + return !m_dmIds.isEmpty() || !m_diIds.isEmpty(); +} + +StoredFilesReq StoredFilesReq::fromJson(const QByteArray &json, bool *ok) +{ + QJsonObject jsonObj; + if (!JsonHelper::readRootObject(json, jsonObj)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return StoredFilesReq(); + } + + StoredFilesReq sfr; + + { + QStringList strList; + if (!JsonHelper::readStringList(jsonObj, keyDmIds, strList, + false)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return StoredFilesReq(); + } + bool iOk = false; + sfr.m_dmIds = createIdList(strList, &iOk); + if (!iOk) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return StoredFilesReq(); + } + } + { + QStringList strList; + if (!JsonHelper::readStringList(jsonObj, keyDiIds, strList, + false)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return StoredFilesReq(); + } + bool iOk = false; + sfr.m_diIds = createIdList(strList, &iOk); + if (!iOk) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return StoredFilesReq(); + } + } + + if (ok != Q_NULLPTR) { + *ok = true; + } + return sfr; +} + +/*! + * @brief Convert list of qin64 into list of strings. + * + * @param[in] idList List of qint64. + * @return List of strings. + */ +static +QStringList createStrIdList(const QList &idList) +{ + QStringList strList; + foreach (qint64 id, idList) { + strList.append(QString::number(id)); + } + + return strList; +} + +QByteArray StoredFilesReq::toJson(void) const +{ + QJsonObject jsonObj; + jsonObj.insert(keyDmIds, + QJsonArray::fromStringList(createStrIdList(m_dmIds))); + jsonObj.insert(keyDiIds, + QJsonArray::fromStringList(createStrIdList(m_diIds))); + + return QJsonDocument(jsonObj).toJson(QJsonDocument::Indented); +} + +DmEntry::DmEntry(void) + : m_dmId(-1), + m_locations() +{ +} + +DmEntry::DmEntry(qint64 dmId, const QStringList &locations) + : m_dmId(dmId), + m_locations(locations) +{ +} + +DmEntry::DmEntry(const DmEntry &me) + : m_dmId(me.m_dmId), + m_locations(me.m_locations) +{ +} + +qint64 DmEntry::dmId(void) const +{ + return m_dmId; +} + +const QStringList &DmEntry::locations(void) const +{ + return m_locations; +} + +bool DmEntry::isValid(void) const +{ + return m_dmId >= 0; +} + +/*! + * @brief Read object content from JSON value. + * + * @param[in] jsnoVal JSON value. + * @param[in] idKey Key identifying data message or delivery info identifier. + * @param[out] id Read id. + * @param[out] locations Read list of locations. + * @return True on success, false else. + */ +static +bool fromJsonValue(const QJsonValue *jsonVal, const QString &idKey, + qint64 &id, QStringList &locations) +{ + if (jsonVal == Q_NULLPTR) { + Q_ASSERT(0); + return false; + } + + if (!jsonVal->isObject()) { + return false; + } + + QJsonObject jsonObj(jsonVal->toObject()); + + id = -1; + locations.clear(); + + QString idStr; + if (!JsonHelper::readString(jsonObj, idKey, idStr, false)) { + return false; + } + bool ok = false; + id = idStr.toLongLong(&ok); + if (!ok) { + return false; + } + if (id < 0) { + return false; + } + + if (!JsonHelper::readStringList(jsonObj, keyLocations, locations, + false)) { + return false; + } + + return true; +} + +bool DmEntry::fromJsonVal(const QJsonValue *jsonVal) +{ + qint64 id = -1; + QStringList locations; + if (!fromJsonValue(jsonVal, keyDmId, id, locations)) { + return false; + } + + m_dmId = id; + m_locations = locations; + return true; +} + +/*! + * @brief Write object to JSON value. + * + * @param[out] jsnoVal JSON value. + * @param[in] idKey Key identifying data message or delivery info identifier. + * @param[in] id Written id. + * @param[in] locations Written list of locations. + * @return True on success, false else. + */ +static +bool toJsonValue(QJsonValue *jsonVal, const QString &idKey, + qint64 id, const QStringList &locations) +{ + if (jsonVal == Q_NULLPTR) { + Q_ASSERT(0); + return false; + } + + QJsonObject jsonObj; + jsonObj.insert(idKey, QString::number(id)); + jsonObj.insert(keyLocations, QJsonArray::fromStringList(locations)); + *jsonVal = jsonObj; + return true; +} + +bool DmEntry::toJsonVal(QJsonValue *jsonVal) const +{ + return toJsonValue(jsonVal, keyDmId, m_dmId, m_locations); +} + +DiEntry::DiEntry(void) + : m_diId(-1), + m_locations() +{ +} + +DiEntry::DiEntry(qint64 diId, const QStringList &locations) + : m_diId(diId), + m_locations(locations) +{ +} + +DiEntry::DiEntry(const DiEntry &ie) + : m_diId(ie.m_diId), + m_locations(ie.m_locations) +{ +} + +qint64 DiEntry::diId(void) const +{ + return m_diId; +} + +const QStringList &DiEntry::locations(void) const +{ + return m_locations; +} + +bool DiEntry::isValid(void) const +{ + return m_diId >= 0; +} + +bool DiEntry::fromJsonVal(const QJsonValue *jsonVal) +{ + qint64 id = -1; + QStringList locations; + if (!fromJsonValue(jsonVal, keyDiId, id, locations)) { + return false; + } + + m_diId = id; + m_locations = locations; + return true; +} + +bool DiEntry::toJsonVal(QJsonValue *jsonVal) const +{ + return toJsonValue(jsonVal, keyDiId, m_diId, m_locations); +} + +StoredFilesResp::StoredFilesResp(void) + : m_dms(), + m_dis(), + m_limit(-1), + m_error() +{ +} + +StoredFilesResp::StoredFilesResp(const QList &dms, const QList &dis, + int limit, const ErrorEntry &error) + : m_dms(dms), + m_dis(dis), + m_limit(limit), + m_error(error) +{ +} + +StoredFilesResp::StoredFilesResp(const StoredFilesResp &sfr) + : m_dms(sfr.m_dms), + m_dis(sfr.m_dis), + m_limit(sfr.m_limit), + m_error(sfr.m_error) +{ +} + +const QList &StoredFilesResp::dms(void) const +{ + return m_dms; +} + +const QList &StoredFilesResp::dis(void) const +{ + return m_dis; +} + +int StoredFilesResp::limit(void) const +{ + return m_limit; +} + +const ErrorEntry &StoredFilesResp::error(void) const +{ + return m_error; +} + +bool StoredFilesResp::isValid(void) const +{ + return (m_limit > 0) && + ((!m_dms.isEmpty() || !m_dis.isEmpty()) || + (m_error.code() != ErrorEntry::ERR_NO_ERROR)); +} + +/*! + * @brief Template function that reads list of objects of type T from JSON + * array. + * + * @param[in] jsonObj JSON object to read data from. + * @param[in] idKey Key identifying the sought array of T. + * @param[out] list List to append read objects of type T to. + * @return True on success, false else. + */ +template +static +bool readArrayofObjects(const QJsonObject &jsonObj, const QString &idKey, + QList &list) +{ + QJsonValue jsonVal; + if (!JsonHelper::readValue(jsonObj, idKey, jsonVal)) { + return false; + } + if (!jsonVal.isArray()) { + return false; + } + foreach (const QJsonValue &jsonVal, jsonVal.toArray()) { + T te; /* Entry of type T. */ + if (!te.fromJsonVal(&jsonVal)) { + return false; + } + list.append(te); + } + return true; +} + +StoredFilesResp StoredFilesResp::fromJson(const QByteArray &json, bool *ok) +{ + QJsonObject jsonObj; + if (!JsonHelper::readRootObject(json, jsonObj)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return StoredFilesResp(); + } + + StoredFilesResp sfr; + if (!readArrayofObjects(jsonObj, keyDms, sfr.m_dms)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return StoredFilesResp(); + } + if (!readArrayofObjects(jsonObj, keyDis, sfr.m_dis)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return StoredFilesResp(); + } + { + if (!JsonHelper::readInt(jsonObj, keyLimit, sfr.m_limit, + false)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return StoredFilesResp(); + } + if (sfr.m_limit < 0) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return StoredFilesResp(); + } + } + { + QJsonValue jsonVal; + if (!JsonHelper::readValue(jsonObj, keyError, jsonVal)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return StoredFilesResp(); + } + if (!sfr.m_error.fromJsonVal(&jsonVal)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return StoredFilesResp(); + } + } + + if (ok != Q_NULLPTR) { + *ok = true; + } + return sfr; +} + +QByteArray StoredFilesResp::toJson(void) const +{ + QJsonObject jsonObj; + { + QJsonArray dms; + QJsonValue dmVal; + foreach (const DmEntry &me, m_dms) { + me.toJsonVal(&dmVal); + dms.append(dmVal); + } + jsonObj.insert(keyDms, dms); + } + { + QJsonArray dis; + QJsonValue diVal; + foreach (const DiEntry &ie, m_dis) { + ie.toJsonVal(&diVal); + dis.append(diVal); + } + jsonObj.insert(keyDis, dis); + } + jsonObj.insert(keyLimit, QString::number(m_limit)); + QJsonValue jsonVal; + m_error.toJsonVal(&jsonVal); + jsonObj.insert(keyError, jsonVal); + + return QJsonDocument(jsonObj).toJson(QJsonDocument::Indented); +} diff --git a/src/datovka_shared/records_management/json/stored_files.h b/src/datovka_shared/records_management/json/stored_files.h new file mode 100644 index 00000000..374515c3 --- /dev/null +++ b/src/datovka_shared/records_management/json/stored_files.h @@ -0,0 +1,340 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +#include "src/datovka_shared/records_management/json/entry_error.h" + +class QJsonValue; /* Forward declaration. */ + +/*! + * @brief Encapsulates the stored_files request. + */ +class StoredFilesReq { +private: + /*! + * @brief Constructor. Creates an invalid structure. + */ + StoredFilesReq(void); + +public: + /*! + * @brief Constructor. + * + * @param[in] dmIds Data message identifiers. + * @param[in] diIds Delivery info identifiers. + */ + StoredFilesReq(const QList &dmIds, const QList &diIds); + + /*! + * @brief Copy constructor. + * + * @param[in] sfr Stored files request. + */ + StoredFilesReq(const StoredFilesReq &sfr); + + /*! + * @brief Return data message identifiers. + * + * @return Data message identifiers as used in ISDS. + */ + const QList &dmIds(void) const; + + /*! + * @brief Return delivery info identifiers. + * + * @return Delivery info identifiers as used in ISDS. + */ + const QList &diIds(void) const; + + /*! + * @brief Check whether content is valid. + * + * @return True if content is valid. + */ + bool isValid(void) const; + + /*! + * @brief Creates a stored files request structure from supplied JSON + * document. + * + * @param[in] json JSON document. + * @param[out] ok Set to true on success. + * @return Invalid structure on error a valid structure else. + */ + static + StoredFilesReq fromJson(const QByteArray &json, bool *ok = Q_NULLPTR); + + /*! + * @brief Converts stored files structure into a JSON document. + * + * @note Unspecified values are stores as null into the JSON document. + * + * @return JSON document containing stored data. + */ + QByteArray toJson(void) const; + +private: + QList m_dmIds; /*!< Data message identifiers. */ + QList m_diIds; /*!< Delivery info identifiers. */ +}; + +/*! + * @brief Encapsulates stored_files data message entry structure. + */ +class DmEntry { +public: + /*! + * @brief Constructor. Constructs invalid entry. + */ + DmEntry(void); + + /*! + * @brief Constructor. + * + * @param[in] dmId Message identifier. + * @param[in] locations List of locations. + */ + DmEntry(qint64 dmId, const QStringList &locations); + + /*! + * @brief Copy constructor. + * + * @param[in] me Message entry. + */ + DmEntry(const DmEntry &me); + + /*! + * @brief Return message identifier. + * + * @return Data message identifier. + */ + qint64 dmId(void) const; + + /*! + * @brief Return list of locations. + * + * @return Locations. + */ + const QStringList &locations(void) const; + + /*! + * @brief Check whether content is valid. + * + * @return True if content is valid. + */ + bool isValid(void) const; + + /*! + * @brief Set content according to JSON value. + * + * @note Method does not modify value of error entry if false returned. + * JSON null values are converted into no-error entries. + * + * @param[in] jsonVal Value to read from. + * @return True on success, false else. + */ + bool fromJsonVal(const QJsonValue *jsonVal); + + /*! + * @brief Set content of supplied JSON value. + * + * @note No-error entries are converted into JSON null values. + * + * @param[out] jsonVal Value to store to. + * @return True on success, false else. + */ + bool toJsonVal(QJsonValue *jsonVal) const; + +private: + qint64 m_dmId; /*!< Data message identifier. */ + QStringList m_locations; /*!< Where the uploaded file is located in the service. */ +}; + +/*! + * @brief Encapsulates stored_files delivery info entry structure. + */ +class DiEntry { +public: + /*! + * @brief Constructor. Constructs invalid entry. + */ + DiEntry(void); + + /*! + * @brief Constructor. + * + * @param[in] diId Info identifier. + * @param[in] locations List of locations. + */ + DiEntry(qint64 diId, const QStringList &locations); + + /*! + * @brief Copy constructor. + * + * @param[in] ie Info entry. + */ + DiEntry(const DiEntry &ie); + + /*! + * @brief Return info identifier. + * + * @return Data info identifier. + */ + qint64 diId(void) const; + + /*! + * @brief Return list of locations. + * + * @return Locations. + */ + const QStringList &locations(void) const; + + /*! + * @brief Check whether content is valid. + * + * @return True if content is valid. + */ + bool isValid(void) const; + + /*! + * @brief Set content according to JSON value. + * + * @note Method does not modify value of error entry if false returned. + * JSON null values are converted into no-error entries. + * + * @param[in] jsonVal Value to read from. + * @return True on success, false else. + */ + bool fromJsonVal(const QJsonValue *jsonVal); + + /*! + * @brief Set content of supplied JSON value. + * + * @note No-error entries are converted into JSON null values. + * + * @param[out] jsonVal Value to store to. + * @return True on success, false else. + */ + bool toJsonVal(QJsonValue *jsonVal) const; + +private: + qint64 m_diId; /*!< Delivery info identifier. */ + QStringList m_locations; /*!< Where the uploaded file is located in the service. */ +}; + +/*! + * @brief Encapsulates the stored_files response. + */ +class StoredFilesResp { +private: + /*! + * @brief Constructor. Creates an invalid structure. + */ + StoredFilesResp(void); + + /*! + * @brief Constructor. + * + * @param[in] dms List of data message entries. + * @param[in] dis List of delivery information entries. + * @param[in] limit Request limit. + * @param[in] error Error entry. + */ + StoredFilesResp(const QList &dms, const QList &dis, + int limit, const ErrorEntry &error); + +public: + /*! + * @brief Copy constructor. + * + * @param[in] sfr Stored files response. + */ + StoredFilesResp(const StoredFilesResp &sfr); + + /*! + * @brief Return list of data message entries. + * + * @return List of held data message entries. + */ + const QList &dms(void) const; + + /*! + * @brief Return list of delivery info entries. + * + * @return List of held delivery info entries. + */ + const QList &dis(void) const; + + /*! + * @brief Return request limit. + * + * @return Request limit as obtained from the service. + */ + int limit(void) const; + + /*! + * @brief Return error entry. + * + * @return Error entry. + */ + const ErrorEntry &error(void) const; + + /*! + * @brief Check whether content is valid. + * + * @return True if content is valid. + */ + bool isValid(void) const; + + /*! + * @brief Creates a stored files response structure from supplied JSON + * document. + * + * @param[in] json JSON document. + * @param[out] ok Set to true on success. + * @return Invalid structure on error a valid structure else. + */ + static + StoredFilesResp fromJson(const QByteArray &json, + bool *ok = Q_NULLPTR); + + /*! + * @brief Converts stored files response structure into a JSON document. + * + * @note Unspecified values are stores as null into the JSON document. + * + * @return JSON document containing stored data. + */ + QByteArray toJson(void) const; + +private: + QList m_dms; /*!< List of received data message entries. */ + QList m_dis; /*!< List of received delivery info entries. */ + int m_limit; /*!< Request limit, must be greater than zero. */ + ErrorEntry m_error; /*!< Encountered error. */ +}; diff --git a/src/datovka_shared/records_management/json/upload_file.cpp b/src/datovka_shared/records_management/json/upload_file.cpp new file mode 100644 index 00000000..6f5e3403 --- /dev/null +++ b/src/datovka_shared/records_management/json/upload_file.cpp @@ -0,0 +1,260 @@ +/* + * 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 "src/datovka_shared/records_management/json/entry_error.h" +#include "src/datovka_shared/records_management/json/helper.h" +#include "src/datovka_shared/records_management/json/upload_file.h" + +static +const QString keyIds("ids"); +static +const QString keyFileName("file_name"); +static +const QString keyFileContent("file_content"); + +static +const QString keyId("id"); +static +const QString keyError("error"); +static +const QString keyLocations("locations"); + +UploadFileReq::UploadFileReq(void) + : m_ids(), + m_fileName(), + m_fileContent() +{ +} + +UploadFileReq::UploadFileReq(const QStringList &ids, const QString &fileName, + const QByteArray &fileContent) + : m_ids(ids), + m_fileName(fileName), + m_fileContent(fileContent) +{ +} + +UploadFileReq::UploadFileReq(const UploadFileReq &ufr) + : m_ids(ufr.m_ids), + m_fileName(ufr.m_fileName), + m_fileContent(ufr.m_fileContent) +{ +} + +const QStringList &UploadFileReq::ids(void) const +{ + return m_ids; +} + +const QString &UploadFileReq::fileName(void) const +{ + return m_fileName; +} + +const QByteArray &UploadFileReq::fileContent(void) const +{ + return m_fileContent; +} + +bool UploadFileReq::isValid(void) const +{ + bool valid = !m_ids.isEmpty() && !m_fileName.isEmpty() && + !m_fileContent.isEmpty(); + if (!valid) { + return false; + } + + foreach (const QString &id, m_ids) { + if (id.isEmpty()) { + return false; + } + } + + return true; +} + +UploadFileReq UploadFileReq::fromJson(const QByteArray &json, bool *ok) +{ + QJsonObject jsonObj; + if (!JsonHelper::readRootObject(json, jsonObj)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return UploadFileReq(); + } + + UploadFileReq ufr; + + if (!JsonHelper::readStringList(jsonObj, keyIds, ufr.m_ids, false)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return UploadFileReq(); + } + if (!JsonHelper::readString(jsonObj, keyFileName, ufr.m_fileName, + false)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return UploadFileReq(); + } + + { + QString valStr; + if (!JsonHelper::readString(jsonObj, keyFileContent, valStr, + false)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return UploadFileReq(); + } + + ufr.m_fileContent = QByteArray::fromBase64(valStr.toUtf8()); + } + + if (ok != Q_NULLPTR) { + *ok = true; + } + return ufr; +} + +QByteArray UploadFileReq::toJson(void) const +{ + QJsonObject jsonObj; + jsonObj.insert(keyIds, QJsonArray::fromStringList(m_ids)); + jsonObj.insert(keyFileName, !m_fileName.isNull() ? + m_fileName : QJsonValue()); + jsonObj.insert(keyFileContent, !m_fileContent.isNull() ? + QString::fromUtf8(m_fileContent.toBase64()) : QJsonValue()); + + return QJsonDocument(jsonObj).toJson(QJsonDocument::Indented); +} + +UploadFileResp::UploadFileResp(void) + : m_id(), + m_error(), + m_locations() +{ +} + +UploadFileResp::UploadFileResp(const QString &id, const ErrorEntry &error, + const QStringList &locations) + : m_id(id), + m_error(error), + m_locations(locations) +{ +} + +UploadFileResp::UploadFileResp(const UploadFileResp &ufr) + : m_id(ufr.m_id), + m_error(ufr.m_error), + m_locations(ufr.m_locations) +{ +} + +const QString &UploadFileResp::id(void) const +{ + return m_id; +} + +const ErrorEntry &UploadFileResp::error(void) const +{ + return m_error; +} + +const QStringList &UploadFileResp::locations(void) const +{ + return m_locations; +} + +bool UploadFileResp::isValid(void) const +{ + return (!m_id.isEmpty() && !m_locations.isEmpty()) || + (m_error.code() != ErrorEntry::ERR_NO_ERROR); +} + +UploadFileResp UploadFileResp::fromJson(const QByteArray &json, bool *ok) +{ + QJsonObject jsonObj; + if (!JsonHelper::readRootObject(json, jsonObj)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return UploadFileResp(); + } + + UploadFileResp ufr; + + if (!JsonHelper::readString(jsonObj, keyId, ufr.m_id, true)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return UploadFileResp(); + } + + { + QJsonValue jsonVal; + if (!JsonHelper::readValue(jsonObj, keyError, jsonVal)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return UploadFileResp(); + } + if (!ufr.m_error.fromJsonVal(&jsonVal)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return UploadFileResp(); + } + } + + if (!JsonHelper::readStringList(jsonObj, keyLocations, ufr.m_locations, + false)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return UploadFileResp(); + } + + if (ok != Q_NULLPTR) { + *ok = true; + } + return ufr; +} + +QByteArray UploadFileResp::toJson(void) const +{ + QJsonObject jsonObj; + jsonObj.insert(keyId, !m_id.isNull() ? m_id : QJsonValue()); + QJsonValue jsonVal; + m_error.toJsonVal(&jsonVal); + jsonObj.insert(keyError, jsonVal); + jsonObj.insert(keyLocations, QJsonArray::fromStringList(m_locations)); + + return QJsonDocument(jsonObj).toJson(QJsonDocument::Indented); +} diff --git a/src/datovka_shared/records_management/json/upload_file.h b/src/datovka_shared/records_management/json/upload_file.h new file mode 100644 index 00000000..f73545f1 --- /dev/null +++ b/src/datovka_shared/records_management/json/upload_file.h @@ -0,0 +1,196 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include + +#include "src/datovka_shared/records_management/json/entry_error.h" + +/*! + * @brief Encapsulates the upload_file request. + */ +class UploadFileReq { +private: + /*! + * @brief Constructor. Creates an invalid structure. + */ + UploadFileReq(void); + +public: + /*! + * @brief Constructor. + * + * @param[in] ids Location identifiers as obtained from upload_hierarchy. + * @param[in] fileName File name. + * @param[in] fileContent Raw file content. + */ + UploadFileReq(const QStringList &ids, const QString &fileName, + const QByteArray &fileContent); + + /*! + * @brief Copy constructor. + * + * @param[in] ufr Upload file request. + */ + UploadFileReq(const UploadFileReq &ufr); + + /*! + * @brief Return location identifier. + * + * @return Location identifier. + */ + const QStringList &ids(void) const; + + /*! + * @brief Return file name. + * + * @return File name. + */ + const QString &fileName(void) const; + + /*! + * @brief Return file content. + * + * @return Raw file content. + */ + const QByteArray &fileContent(void) const; + + /*! + * @brief Check whether content is valid. + * + * @return True if content is valid. + */ + bool isValid(void) const; + + /*! + * @brief Creates a upload file request structure from supplied JSON + * document. + * + * @param[in] json JSON document. + * @param[out] ok Set to true on success. + * @return Invalid structure on error a valid structure else. + */ + static + UploadFileReq fromJson(const QByteArray &json, bool *ok = Q_NULLPTR); + + /*! + * @brief Converts upload file structure into a JSON document. + * + * @note Unspecified values are stores as null into the JSON document. + * + * @return JSON document containing stored data. + */ + QByteArray toJson(void) const; + +private: + QStringList m_ids; /*!< Location identifiers as obtained from upload_hierarchy. */ + QString m_fileName; /*!< Uploaded file name. */ + QByteArray m_fileContent; /*!< Raw content of uploaded file. */ +}; + +/*! + * @brief Encapsulates the upload_file response. + */ +class UploadFileResp { +private: + /*! + * @brief Constructor. Creates an invalid structure. + */ + UploadFileResp(void); + + /*! + * @brief Constructor. + * + * @param[in] id File identifier. + * @param[in] error Error entry. + * @param[in] locations List of locations. + */ + UploadFileResp(const QString &id, const ErrorEntry &error, + const QStringList &locations); + +public: + /*! + * @brief Copy constructor. + * + * @param[in] ufr Upload file response. + */ + UploadFileResp(const UploadFileResp &ufr); + + /*! + * @brief Return file identifier. + * + * @return File identifier. + */ + const QString &id(void) const; + + /*! + * @brief Return error entry. + * + * @return Error entry. + */ + const ErrorEntry &error(void) const; + + /*! + * @brief Return location list. + * + * @return List of places where the file is stored in the service. + */ + const QStringList &locations(void) const; + + /*! + * @brief Check whether content is valid. + * + * @return True if content is valid. + */ + bool isValid(void) const; + + /*! + * @brief Creates a upload file response structure from supplied JSON + * document. + * + * @param[in] json JSON document. + * @param[out] ok Set to true on success. + * @return Invalid structure on error a valid structure else. + */ + static + UploadFileResp fromJson(const QByteArray &json, + bool *ok = Q_NULLPTR); + + /*! + * @brief Converts upload file response structure into a JSON document. + * + * @note Unspecified values are stores as null into the JSON document. + * + * @return JSON document containing stored data. + */ + QByteArray toJson(void) const; + +private: + QString m_id; /*!< Uploaded file identifier (not necessary from ISDS). */ + ErrorEntry m_error; /*!< Brief error entry. */ + QStringList m_locations; /*!< Where the uploaded file is located in the service. */ +}; diff --git a/src/datovka_shared/records_management/json/upload_hierarchy.cpp b/src/datovka_shared/records_management/json/upload_hierarchy.cpp new file mode 100644 index 00000000..9c3b2540 --- /dev/null +++ b/src/datovka_shared/records_management/json/upload_hierarchy.cpp @@ -0,0 +1,369 @@ +/* + * 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 "src/datovka_shared/records_management/json/helper.h" +#include "src/datovka_shared/records_management/json/upload_hierarchy.h" + +static +const QString keyName("name"); +static +const QString keyId("id"); +static +const QString keyMetadata("metadata"); +static +const QString keySub("sub"); + +UploadHierarchyResp::NodeEntry::NodeEntry(void) + : m_super(Q_NULLPTR), + m_name(), + m_id(), + m_metadata(), + m_sub() +{ +} + +const UploadHierarchyResp::NodeEntry *UploadHierarchyResp::NodeEntry::super(void) const +{ + return m_super; +} + +const QString &UploadHierarchyResp::NodeEntry::name(void) const +{ + return m_name; +} + +const QString &UploadHierarchyResp::NodeEntry::id(void) const +{ + return m_id; +} + +const QStringList &UploadHierarchyResp::NodeEntry::metadata(void) const +{ + return m_metadata; +} + +const QList &UploadHierarchyResp::NodeEntry::sub(void) const +{ + return m_sub; +} + +UploadHierarchyResp::NodeEntry *UploadHierarchyResp::NodeEntry::copyRecursive( + const NodeEntry *root) +{ + if (root == Q_NULLPTR) { + Q_ASSERT(0); + return Q_NULLPTR; + } + + NodeEntry *newRoot = new (std::nothrow) NodeEntry(); + if (newRoot == Q_NULLPTR) { + return Q_NULLPTR; + } + + newRoot->m_name = root->m_name; + newRoot->m_id = root->m_id; + newRoot->m_metadata = root->m_metadata; + + foreach (const NodeEntry *sub, root->m_sub) { + if (sub == Q_NULLPTR) { + Q_ASSERT(0); + deleteRecursive(newRoot); + return Q_NULLPTR; + } + NodeEntry *newSub = copyRecursive(sub); + if (newSub == Q_NULLPTR) { + deleteRecursive(newRoot); + return Q_NULLPTR; + } + + newSub->m_super = newRoot; + newRoot->m_sub.append(newSub); + } + + return newRoot; +} + +void UploadHierarchyResp::NodeEntry::deleteRecursive(NodeEntry *root) +{ + if (root == Q_NULLPTR) { + return; + } + + foreach (NodeEntry *entry, root->m_sub) { + deleteRecursive(entry); + } + + delete root; +} + +UploadHierarchyResp::NodeEntry *UploadHierarchyResp::NodeEntry::fromJsonRecursive( + const QJsonObject *jsonObj, bool &ok, bool acceptNullName) +{ + if (jsonObj == Q_NULLPTR) { + Q_ASSERT(0); + ok = false; + return Q_NULLPTR; + } + + NodeEntry *newEntry = new (std::nothrow) NodeEntry(); + if (newEntry == Q_NULLPTR) { + ok = false; + return Q_NULLPTR; + } + + if (!JsonHelper::readString(*jsonObj, keyName, newEntry->m_name, + acceptNullName)) { + ok = false; + deleteRecursive(newEntry); + return Q_NULLPTR; + } + if (!JsonHelper::readString(*jsonObj, keyId, newEntry->m_id, true)) { + ok = false; + deleteRecursive(newEntry); + return Q_NULLPTR; + } + if (!JsonHelper::readStringList(*jsonObj, keyMetadata, + newEntry->m_metadata, false)) { + ok = false; + deleteRecursive(newEntry); + return Q_NULLPTR; + } + + { + QJsonArray jsonArr; + if (!JsonHelper::readArray(*jsonObj, keySub, jsonArr, false)) { + ok = false; + deleteRecursive(newEntry); + return Q_NULLPTR; + } + + foreach (const QJsonValue &jsonVal, jsonArr) { + if (!jsonVal.isObject()) { + qCritical("%s", + "Sub-node array holds a non-object value."); + ok = false; + deleteRecursive(newEntry); + return Q_NULLPTR; + } + + QJsonObject jsonSubObj(jsonVal.toObject()); + NodeEntry *subNewEntry = + fromJsonRecursive(&jsonSubObj, ok, false); + if (!ok) { + /* ok = false; */ + deleteRecursive(newEntry); + return Q_NULLPTR; + } + Q_ASSERT(subNewEntry != Q_NULLPTR); + + subNewEntry->m_super = newEntry; + newEntry->m_sub.append(subNewEntry); + } + } + + ok = true; + return newEntry; +} + +/*! + * @brief Append a JSON object into sub-nodes. + * + * @param[in,out] jsonNode Not to append a sub-node to. + * @param[in] jsnoSubNode Sub-node to be appended. + * @return True on success, false else. + */ +static +bool jsonHierarchyAppendSub(QJsonObject &jsonNode, + const QJsonObject &jsonSubNode) +{ + QJsonObject::iterator it(jsonNode.find(keySub)); + if (it == jsonNode.end()) { + return false; + } + + QJsonValueRef arrRef(it.value()); + if (!arrRef.isArray()) { + return false; + } + + /* Sub-node must have a name. */ + { + QJsonObject::const_iterator sit(jsonSubNode.constFind(keyName)); + if ((sit == jsonSubNode.constEnd()) || + !sit.value().isString() || + sit.value().toString().isEmpty()) { + return false; + } + } + +#if 0 + /* QJsonArray::operator+() is not available in Qt 5.2. */ + arrRef = arrRef.toArray() + jsonSubNode; +#else + QJsonArray arr(arrRef.toArray()); + arr.append(jsonSubNode); + arrRef = arr; +#endif + return true; +} + +/*! + * @brief Creates a JSON object representing an upload hierarchy node with + * empty sub-node list. + * + * @param[in] name Node name. + * @param[in] id Node identifier. + * @param[in] metadata List of metadata. + * @return JSON object. + */ +static +QJsonObject jsonHierarchyNode(const QString &name, const QString &id, + const QStringList &metadata) +{ + QJsonObject jsonObj; + /* Intentionally using isEmpty() instead of isNull(). */ + jsonObj.insert(keyName, !name.isEmpty() ? name : QJsonValue()); + jsonObj.insert(keyId, !id.isEmpty() ? id : QJsonValue()); + jsonObj.insert(keyMetadata, QJsonArray::fromStringList(metadata)); + jsonObj.insert(keySub, QJsonArray()); + + return jsonObj; +} + +bool UploadHierarchyResp::NodeEntry::toJsonRecursive(QJsonObject *jsonObj, + const QList &uhrList) +{ + if (jsonObj == Q_NULLPTR) { + Q_ASSERT(0); + return false; + } + + foreach (const NodeEntry *entry, uhrList) { + if (entry == Q_NULLPTR) { + Q_ASSERT(0); + return false; + } + QJsonObject jsonSubObj(jsonHierarchyNode(entry->m_name, + entry->m_id, entry->m_metadata)); + if (!toJsonRecursive(&jsonSubObj, entry->m_sub)) { + return false; + } + if (!jsonHierarchyAppendSub(*jsonObj, jsonSubObj)) { + return false; + } + } + + return true; +} + +UploadHierarchyResp::UploadHierarchyResp(void) + : m_root(Q_NULLPTR) +{ +} + +UploadHierarchyResp::UploadHierarchyResp(const UploadHierarchyResp &uhr) + : m_root(Q_NULLPTR) +{ + if (uhr.m_root != Q_NULLPTR) { + m_root = NodeEntry::copyRecursive(uhr.m_root); + } +} + +UploadHierarchyResp::~UploadHierarchyResp(void) +{ + NodeEntry::deleteRecursive(m_root); +} + +const UploadHierarchyResp::NodeEntry *UploadHierarchyResp::root(void) const +{ + return m_root; +} + +bool UploadHierarchyResp::isValid(void) const +{ + return m_root != Q_NULLPTR; +} + +UploadHierarchyResp UploadHierarchyResp::fromJson(const QByteArray &json, + bool *ok) +{ + QJsonObject jsonObj; + if (!JsonHelper::readRootObject(json, jsonObj)) { + if (ok != Q_NULLPTR) { + *ok = false; + } + return UploadHierarchyResp(); + } + + bool intOk = false; + UploadHierarchyResp uhr; + + uhr.m_root = NodeEntry::fromJsonRecursive(&jsonObj, intOk, true); + if (ok != Q_NULLPTR) { + *ok = intOk; + } + Q_ASSERT(uhr.m_root != Q_NULLPTR); + return intOk ? uhr : UploadHierarchyResp(); +} + +QByteArray UploadHierarchyResp::toJson(void) const +{ + if (m_root == Q_NULLPTR) { + Q_ASSERT(0); + return QJsonDocument(QJsonObject()).toJson( + QJsonDocument::Indented); + } + + QJsonObject jsonObj(jsonHierarchyNode(m_root->m_name, + m_root->m_id, m_root->m_metadata)); + + if (!NodeEntry::toJsonRecursive(&jsonObj, m_root->m_sub)) { + return QByteArray(); + } + + return QJsonDocument(jsonObj).toJson(QJsonDocument::Indented); +} + +UploadHierarchyResp &UploadHierarchyResp::operator=( + const UploadHierarchyResp &other) Q_DECL_NOTHROW +{ + NodeEntry *tmpRoot = Q_NULLPTR; + + if (other.m_root != Q_NULLPTR) { + tmpRoot = NodeEntry::copyRecursive(other.m_root); + if (tmpRoot == Q_NULLPTR) { + /* Copying failed. */ + Q_ASSERT(0); + } + } + + NodeEntry::deleteRecursive(m_root); + m_root = tmpRoot; + + return *this; +} diff --git a/src/datovka_shared/records_management/json/upload_hierarchy.h b/src/datovka_shared/records_management/json/upload_hierarchy.h new file mode 100644 index 00000000..24e7a37d --- /dev/null +++ b/src/datovka_shared/records_management/json/upload_hierarchy.h @@ -0,0 +1,203 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include + +class QJsonObject; /* Forward declaration. */ + +/*! + * @brief Encapsulates the upload_hierarchy response. + */ +class UploadHierarchyResp { +public: + /*! + * @brief Node entry element. + */ + class NodeEntry { + private: + /*! + * @brief Constructor. + */ + NodeEntry(void); + + public: + /*! + * @brief Return superordinate node, + * + * @return Superordinate node. + */ + const NodeEntry *super(void) const; + + /*! + * @brief Returns node name. + * + * @return Node name. + */ + const QString &name(void) const; + + /*! + * @brief Returns node id. + * + * @return Node id. + */ + const QString &id(void) const; + + /*! + * @brief Returns metadata. + * + * @return Stored metadata. + */ + const QStringList &metadata(void) const; + + /*! + * @brief Returns list of subordinated nodes. + * + * @return List of subordinated nodes. + */ + const QList &sub(void) const; + + private: + /*! + * @brief Performs a recursive (deep) copy. + * + * @param[in] root Non-null tree root. + * @return Copied tree on success, null pointer on error. + */ + static + NodeEntry *copyRecursive(const NodeEntry *root); + + /*! + * @brief Recursively delete the tree of nodes. + * + * @param[in] root Root of the tree to delete. + */ + static + void deleteRecursive(NodeEntry *root); + + /*! + * @brief Recursively constructs a hierarchy. + * + * @param[in] jsonObj Object to be used as root of the hierarchy. + * @param[out] ok Set to true if no error encountered. + * @param[in] acceptNullName True if null values should be accepted. + * @return Parsed hierarchy if \a ok set to true. + */ + static + NodeEntry *fromJsonRecursive(const QJsonObject *jsonObj, + bool &ok, bool acceptNullName); + + /*! + * @brief Recursively constructs a JSON object hierarchy. + * + * @param[in,out] jsonObj JSON object to append sub-nodes to. + * @param[in] uhrList List of sub-nodes to convert to JSON nodes + * from and to add as sub-nodes. + * @return True on success. + */ + static + bool toJsonRecursive(QJsonObject *jsonObj, + const QList &uhrList); + + NodeEntry *m_super; /*!< Superordinate node. */ + QString m_name; /*!< Entry name. Root entry name may be null. */ + QString m_id; /*!< Entry identifier. May be null. */ + QStringList m_metadata; /*!< Metadata. List my be empty. */ + QList m_sub; /*!< Subordinated nodes. */ + + friend class UploadHierarchyResp; + }; + +public: + /*! + * @brief Constructor. Creates an invalid structure. + */ + UploadHierarchyResp(void); + + /*! + * @brief Copy constructor. + * + * @param[in] uhr Upload hierarchy response. + */ + UploadHierarchyResp(const UploadHierarchyResp &uhr); + + /*! + * @brief Destructor. + */ + ~UploadHierarchyResp(void); + + /*! + * @brief Returns root node. + * + * @return Root node. + */ + const NodeEntry *root(void) const; + + /*! + * @brief Check whether content is valid. + * + * @return True if content is valid. + */ + bool isValid(void) const; + + /*! + * @brief Creates a upload hierarchy structure from supplied JSON + * document. + * + * @param[in] json JSON document. + * @param[out] ok Set to true on success. + * @return Invalid structure on error a valid structure else. + */ + static + UploadHierarchyResp fromJson(const QByteArray &json, + bool *ok = Q_NULLPTR); + + /*! + * @brief Converts upload hierarchy structure into a JSON document. + * + * @note Unspecified values are stores as null into the JSON document. + * + * @return JSON document containing stored data. + */ + QByteArray toJson(void) const; + + /*! + * @brief Copy assignment. + * + * @param[in] other Source. + * @return Destination reference. + */ + UploadHierarchyResp &operator=( + const UploadHierarchyResp &other) Q_DECL_NOTHROW; + +#ifdef Q_COMPILER_RVALUE_REFS + /* TODO -- Move assignment. */ +#endif /* Q_COMPILER_RVALUE_REFS */ + +private: + NodeEntry *m_root; /*!< Tree root. */ +}; -- GitLab From 6dabedd7cacbbed5b0d55907e56d694168938fa1 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Mon, 29 Jan 2018 16:41:21 +0100 Subject: [PATCH 04/63] Added records management stored task --- mobile-datovka.pro | 5 + src/net/isds_wrapper.cpp | 30 +- src/net/isds_wrapper.h | 6 - src/records_management.cpp | 56 +++ src/records_management.h | 35 ++ src/sqlite/message_db.cpp | 26 +- src/sqlite/message_db.h | 14 +- src/worker/pool.cpp | 32 ++ src/worker/pool.h | 31 ++ ...ask_records_management_stored_messages.cpp | 366 ++++++++++++++++++ .../task_records_management_stored_messages.h | 113 ++++++ 11 files changed, 687 insertions(+), 27 deletions(-) create mode 100644 src/worker/pool.cpp create mode 100644 src/worker/pool.h create mode 100644 src/worker/task_records_management_stored_messages.cpp create mode 100644 src/worker/task_records_management_stored_messages.h diff --git a/mobile-datovka.pro b/mobile-datovka.pro index 7ef6ab28..fc0df9c1 100644 --- a/mobile-datovka.pro +++ b/mobile-datovka.pro @@ -145,6 +145,7 @@ SOURCES += \ src/sqlite/message_db.cpp \ src/sqlite/zfo_db.cpp \ src/worker/emitter.cpp \ + src/worker/pool.cpp \ src/worker/task.cpp \ src/worker/task_change_password.cpp \ src/worker/task_download_account_info.cpp \ @@ -155,6 +156,7 @@ SOURCES += \ src/worker/task_find_databox_fulltext.cpp \ src/worker/task_import_zfo.cpp \ src/worker/task_keep_alive.cpp \ + src/worker/task_records_management_stored_messages.cpp \ src/worker/task_send_message.cpp \ src/worker/task_send_sms.cpp \ src/zfo.cpp @@ -221,6 +223,7 @@ HEADERS += \ src/sqlite/message_db.h \ src/sqlite/zfo_db.h \ src/worker/emitter.h \ + src/worker/pool.h \ src/worker/task.h \ src/worker/task_change_password.h \ src/worker/task_download_account_info.h \ @@ -231,9 +234,11 @@ HEADERS += \ src/worker/task_find_databox_fulltext.h \ src/worker/task_import_zfo.h \ src/worker/task_keep_alive.h \ + src/worker/task_records_management_stored_messages.h \ src/worker/task_send_message.h \ src/worker/task_send_sms.h \ src/zfo.h + android { SOURCES += \ src/os_android.cpp diff --git a/src/net/isds_wrapper.cpp b/src/net/isds_wrapper.cpp index 4e3ae73e..7f6469fe 100644 --- a/src/net/isds_wrapper.cpp +++ b/src/net/isds_wrapper.cpp @@ -38,6 +38,7 @@ #include "src/settings.h" #include "src/sqlite/zfo_db.h" #include "src/worker/emitter.h" +#include "src/worker/pool.h" #include "src/worker/task_change_password.h" #include "src/worker/task_download_account_info.h" #include "src/worker/task_download_delivery_info.h" @@ -51,7 +52,6 @@ IsdsWrapper::IsdsWrapper(QObject *parent) : QObject(parent), - m_workPool(1), m_transactIds() /* * TODO -- To be able to run multiple therads in the pool a locking mechanism @@ -60,7 +60,7 @@ IsdsWrapper::IsdsWrapper(QObject *parent) * waiting. */ { - m_workPool.start(); + globWorkPool.start(); /* Worker-related processing signals. */ connect(&globMsgProcEmitter, @@ -94,8 +94,8 @@ IsdsWrapper::IsdsWrapper(QObject *parent) IsdsWrapper::~IsdsWrapper(void) { - m_workPool.wait(); - m_workPool.stop(); + globWorkPool.wait(); + globWorkPool.stop(); } QString IsdsWrapper::importZfoMessages(const QString &userName, @@ -155,7 +155,7 @@ QString IsdsWrapper::importZfoMessages(const QString &userName, &m_netLayer, &m_dbWrapper, userNameList, file, authenticate, zfoNumber, zfoTotal); task->setAutoDelete(true); - m_workPool.assignLo(task); + globWorkPool.assignLo(task); } return tr("ZFO import is running... Wait until import will finished!"); @@ -196,7 +196,7 @@ bool IsdsWrapper::changePassword(const QString &userName, const QString &oldPwd, m_isdsSession.isdsCtxMap[userName], &m_netLayer, otpType, oldPwd, newPwd); task->setAutoDelete(false); - m_workPool.runSingle(task); + globWorkPool.runSingle(task); success = TaskChangePassword::DL_SUCCESS == task->m_result; isdsText = task->m_isdsText; delete task; task = Q_NULLPTR; @@ -303,7 +303,7 @@ void IsdsWrapper::getAccountInfo(const QString &userName) task = new (std::nothrow) TaskDownloadAccountInfo( m_isdsSession.isdsCtxMap[userName], &m_netLayer, &m_dbWrapper); task->setAutoDelete(true); - m_workPool.assignHi(task); + globWorkPool.assignHi(task); } int IsdsWrapper::findDataboxFulltext(const QString &userName, @@ -347,7 +347,7 @@ int IsdsWrapper::findDataboxFulltext(const QString &userName, return 0; } task->setAutoDelete(false); - m_workPool.runSingle(task); + globWorkPool.runSingle(task); bool success = TaskFindDataboxFulltext::DL_SUCCESS == task->m_result; QList dbList = task->m_dbList; int totalCount = task->m_totalCount; @@ -399,7 +399,7 @@ QString IsdsWrapper::findDatabox(const QString &userName, const QString &dbID, return tr("Error creating task"); } task->setAutoDelete(false); - m_workPool.runSingle(task); + globWorkPool.runSingle(task); QString dbInfo = task->m_dbInfo; delete task; task = Q_NULLPTR; @@ -430,7 +430,7 @@ void IsdsWrapper::getDeliveryInfo(const QString &userName, qint64 msgId) m_isdsSession.isdsCtxMap[userName], &m_netLayer, &m_dbWrapper, msgId); task->setAutoDelete(true); - m_workPool.assignHi(task); + globWorkPool.assignHi(task); bool success = TaskDownloadDeliveryInfo::DL_SUCCESS == task->m_result; delete task; task = Q_NULLPTR; @@ -689,7 +689,7 @@ void IsdsWrapper::sendMessage(const QString &userName, qint64 dmID, &m_dbWrapper, msg, attachList, dmOVM, dmPublishOwnID, taskIdentifiers.at(i)); task->setAutoDelete(true); - m_workPool.assignHi(task); + globWorkPool.assignHi(task); } } @@ -725,7 +725,7 @@ bool IsdsWrapper::sendSMS(const QString &userName, const QString &oldPwd) TaskSendSMS *taskSMS = new (std::nothrow) TaskSendSMS( m_isdsSession.isdsCtxMap[userName], &m_netLayer); taskSMS->setAutoDelete(false); - m_workPool.runSingle(taskSMS); + globWorkPool.runSingle(taskSMS); success = TaskSendSMS::DL_SUCCESS == taskSMS->m_result; delete taskSMS; taskSMS = Q_NULLPTR; @@ -1040,7 +1040,7 @@ void IsdsWrapper::downloadMessage(MessageListModel *messageModel, msgId, messageType, messageModel, (globSet.zfoDbSizeMBs > 0), AccountListModel::globAccounts[userName].isTestAccount()); task->setAutoDelete(true); - m_workPool.assignLo(task); + globWorkPool.assignLo(task); } bool IsdsWrapper::hasCtxAllLoginData(const QString &isdsAction, @@ -1249,10 +1249,10 @@ void IsdsWrapper::syncSingleAccount(const QVariant &acntModelVariant, task = new (std::nothrow) TaskDownloadMessageList( m_isdsSession.isdsCtxMap[userName], &m_netLayer, &m_dbWrapper, msgDirect, (globSet.downloadOnlyNewMsgs) ? DOWNLOAD_NEW_MESSAGES : DOWNLOAD_ALL_MESSAGES, - 1, MESSAGE_LIST_LIMIT, &m_workPool, messageModel, accountModel, + 1, MESSAGE_LIST_LIMIT, &globWorkPool, messageModel, accountModel, globSet.downloadCompleteMsgs, globSet.dbsLocation, (globSet.zfoDbSizeMBs > 0), AccountListModel::globAccounts[userName].isTestAccount()); task->setAutoDelete(true); - m_workPool.assignHi(task); + globWorkPool.assignHi(task); } diff --git a/src/net/isds_wrapper.h b/src/net/isds_wrapper.h index eb791356..30c958ff 100644 --- a/src/net/isds_wrapper.h +++ b/src/net/isds_wrapper.h @@ -26,7 +26,6 @@ #include -#include "src/datovka_shared/worker/pool.h" #include "src/messages.h" #include "src/net/db_wrapper.h" #include "src/net/net_layer.h" @@ -627,11 +626,6 @@ private: */ IsdsSession m_isdsSession; - /*! - * @brief Worker pool instance. - */ - WorkerPool m_workPool; - /* Used to collect sending results. */ QSet m_transactIds; /*!< Temporary transaction identifiers. */ QList m_sentMsgResultList; /*!< Send status list. */ diff --git a/src/records_management.cpp b/src/records_management.cpp index 61e3287b..5160834c 100644 --- a/src/records_management.cpp +++ b/src/records_management.cpp @@ -25,11 +25,22 @@ #include "src/datovka_shared/io/records_management_db.h" #include "src/datovka_shared/records_management/json/service_info.h" +#include "src/models/accountmodel.h" #include "src/records_management.h" +#include "src/settings.h" +#include "src/worker/pool.h" +#include "src/worker/task_records_management_stored_messages.h" RecordsManagement::RecordsManagement(QObject *parent) : QObject(parent) { + globWorkPool.start(); +} + +RecordsManagement::~RecordsManagement(void) +{ + globWorkPool.wait(); + globWorkPool.stop(); } void RecordsManagement::callServiceInfo(const QString &urlStr, const QString &tokenStr) @@ -57,6 +68,51 @@ void RecordsManagement::callServiceInfo(const QString &urlStr, const QString &to } } +void RecordsManagement::getStoredMsgInfoFromRecordsManagement( + const QString &urlStr, const QString &tokenStr) +{ + + TaskRecordsManagementStoredMessages *task = + new (::std::nothrow) TaskRecordsManagementStoredMessages( + urlStr, tokenStr, + TaskRecordsManagementStoredMessages::RM_UPDATE_STORED, + Q_NULLPTR); + if (Q_NULLPTR == task) { + qCritical("%s", "Cannot create stored_files update task."); + return; + } + task->setAutoDelete(true); + /* Run in background. */ + globWorkPool.assignHi(task); + + foreach (const QString &userName, AccountListModel::globAccounts.keys()) { + MessageDb *msgDb = globMessageDbsPtr->accessMessageDb( + globSet.dbsLocation, userName, + AccountListModel::globAccounts[userName].storeToDisk()); + if (msgDb == Q_NULLPTR) { + qWarning("%s", "Cannot open message database."); + return; + } + + TaskRecordsManagementStoredMessages *task = + new (::std::nothrow) TaskRecordsManagementStoredMessages( + urlStr, tokenStr, + TaskRecordsManagementStoredMessages::RM_DOWNLOAD_ALL, + msgDb); + if (Q_NULLPTR == task) { + qWarning("Cannot create stored_files task for '%s'.", + userName.toUtf8().constData()); + continue; + } + task->setAutoDelete(true); + /* Run in background. */ + globWorkPool.assignHi(task); + + } + + /* TODO - update message model */ +} + void RecordsManagement::loadStoredServiceInfo(void) { if (Q_NULLPTR == globRecordsManagementDbPtr) { diff --git a/src/records_management.h b/src/records_management.h index 25e90855..43c3ec12 100644 --- a/src/records_management.h +++ b/src/records_management.h @@ -25,6 +25,8 @@ #include +#include "src/sqlite/message_db_container.h" + #include "src/datovka_shared/records_management/io/records_management_connection.h" #include "src/datovka_shared/settings/records_management.h" @@ -32,11 +34,33 @@ class RecordsManagement : public QObject { Q_OBJECT public: + + /*! + * @brief Describes account information. + */ + class AcntData { + public: + AcntData(const QString &aName, const QString &uName, + const MessageDb *msgDb) + : accountName(aName), userName(uName), msgDb(msgDb) + { + } + + QString accountName; /*!< Account name. */ + QString userName; /*!< User name (login). */ + const MessageDb *msgDb; /*!< Database set related to account. */ + }; + /*! * @brief Constructor. */ RecordsManagement(QObject *parent = Q_NULLPTR); + /*! + * @brief Destructor. + */ + ~RecordsManagement(void); + /*! * @brief Calls service info and displays results. * @@ -46,6 +70,17 @@ public: Q_INVOKABLE void callServiceInfo(const QString &urlStr, const QString &tokenStr); + /*! + * @brief Obtain information about stored messages from records + * management. + * + * @param[in] urlStr records management url string. + * @param[in] tokenStr records management token string. + */ + Q_INVOKABLE + void getStoredMsgInfoFromRecordsManagement(const QString &urlStr, + const QString &tokenStr); + /*! * @brief Loads service information from storage. */ diff --git a/src/sqlite/message_db.cpp b/src/sqlite/message_db.cpp index ce92c088..6a5b7d2a 100644 --- a/src/sqlite/message_db.cpp +++ b/src/sqlite/message_db.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2017 CZ.NIC + * 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 @@ -131,6 +131,30 @@ int MessageDb::getDbSizeInBytes(void) return fi.size(); } +QSet MessageDb::getAllMessageIDsFromDB(void) +{ + QSqlQuery query(m_db); + QSet msgIDList; + + QString queryStr = "SELECT dmID FROM messages"; + if (!query.prepare(queryStr)) { + qCritical() << "Cannot prepare SQL query:" << + query.lastError().text().toUtf8().constData(); + return QSet(); + } + if (query.exec() && query.isActive()) { + query.first(); + while (query.isValid()) { + msgIDList.insert(query.value(0).toLongLong()); + query.next(); + } + } else { + return QSet(); + } + + return msgIDList; +} + QList MessageDb::getExpireMsgListFromDb(int days) { QSqlQuery query(m_db); diff --git a/src/sqlite/message_db.h b/src/sqlite/message_db.h index 9f7002d3..d668cf82 100644 --- a/src/sqlite/message_db.h +++ b/src/sqlite/message_db.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2017 CZ.NIC + * 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 @@ -21,8 +21,7 @@ * the two. */ -#ifndef _MESSAGE_DB_H_ -#define _MESSAGE_DB_H_ +#pragma once #include #include @@ -102,6 +101,13 @@ public: */ int getDbSizeInBytes(void); + /*! + * @brief Get list of message IDs. + * + * @return Set of message IDs. + */ + QSet getAllMessageIDsFromDB(void); + /*! * @brief Get list of messages from db which are older than number of days. * @@ -273,5 +279,3 @@ private: static const QVector fileItemIds; }; - -#endif /* _MESSAGE_DB_H_ */ diff --git a/src/worker/pool.cpp b/src/worker/pool.cpp new file mode 100644 index 00000000..ce57f279 --- /dev/null +++ b/src/worker/pool.cpp @@ -0,0 +1,32 @@ +/* + * 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 "src/worker/pool.h" + +WorkerPool globWorkPool(1); /*!< Only one worker thread currently. */ +/* + * TODO -- To be able to run multiple therads in the pool a locking mechanism + * over libisds context structures must be implemented. + * Also, per-context queueing ought to be implemented to avoid unnecessary + * waiting. + */ diff --git a/src/worker/pool.h b/src/worker/pool.h new file mode 100644 index 00000000..77eea348 --- /dev/null +++ b/src/worker/pool.h @@ -0,0 +1,31 @@ +/* + * 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. + */ + +#pragma once + +#include "src/datovka_shared/worker/pool.h" + +/*! + * @brief Global instance of the structure. + */ +extern WorkerPool globWorkPool; diff --git a/src/worker/task_records_management_stored_messages.cpp b/src/worker/task_records_management_stored_messages.cpp new file mode 100644 index 00000000..ec2ce8ea --- /dev/null +++ b/src/worker/task_records_management_stored_messages.cpp @@ -0,0 +1,366 @@ +/* + * 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 "src/datovka_shared/io/records_management_db.h" +#include "src/datovka_shared/records_management/io/records_management_connection.h" +#include "src/datovka_shared/records_management/json/entry_error.h" +#include "src/datovka_shared/records_management/json/stored_files.h" +#include "src/log/log.h" +#include "src/worker/task_records_management_stored_messages.h" + +TaskRecordsManagementStoredMessages::TaskRecordsManagementStoredMessages( + const QString &urlStr, const QString &tokenStr, enum Operation operation, + MessageDb *msgDb, const QList &exludedDmIds) + : m_result(DS_DSM_ERR), + m_id(QString::number(QDateTime::currentMSecsSinceEpoch()) + + QStringLiteral(":") + QString::number((quint64)this)), + m_url(urlStr), + m_token(tokenStr), + m_operation(operation), + m_msgDb(msgDb), + m_exludedDmIds(exludedDmIds) +{ + Q_ASSERT(!m_url.isEmpty() && !m_token.isEmpty()); + Q_ASSERT((RM_UPDATE_STORED == operation) || (Q_NULLPTR != m_msgDb)); +} + +void TaskRecordsManagementStoredMessages::run(void) +{ + if ((RM_DOWNLOAD_ALL == m_operation) && (Q_NULLPTR == m_msgDb)) { + Q_ASSERT(0); + return; + } + + if (m_url.isEmpty() || m_token.isEmpty()) { + Q_ASSERT(0); + return; + } + + logDebugLv0NL( + "Starting download stored messages from records management service task '%s' in thread '%p'.", + m_id.toUtf8().constData(), (void *) QThread::currentThreadId()); + + /* ### Worker task begin. ### */ + + m_result = downloadStoredMessages(m_url, m_token, m_operation, m_msgDb, + m_exludedDmIds); + + /* ### Worker task end. ### */ + + logDebugLv0NL("Download stored messages from records management service task '%s' finished in thread '%p'.", + m_id.toUtf8().constData(), (void *) QThread::currentThreadId()); +} + +const QString &TaskRecordsManagementStoredMessages::id(void) const +{ + return m_id; +} + +/*! + * @brief Obtains all message identifiers from message database. + * + * @param[in] msgDb Database set to be used to obtain message identifiers. + * @patam[in] exludedDmIds Message identifiers that should not be queried. + * @return List of message identifiers. + */ +static +QList obtainDbSetDmIds(MessageDb *msgDb, + const QList &exludedDmIds) +{ + if (Q_NULLPTR == msgDb) { + Q_ASSERT(0); + return QList(); + } + + QSet dmIdSet = msgDb->getAllMessageIDsFromDB(); + + dmIdSet -= exludedDmIds.toSet(); + return dmIdSet.toList(); +} + +/*! + * @brief Obtains all message identifiers held in records management database. + * + * @patam[in] exludedDmIds Message identifiers that should not be queried. + * @return List of message identifiers. + */ +static +QList obtainHeldDmIds(const QList &exludedDmIds) +{ + if (Q_NULLPTR == globRecordsManagementDbPtr) { + Q_ASSERT(0); + return QList(); + } + + QSet dmIdSet = globRecordsManagementDbPtr->getAllDmIds().toSet(); + + dmIdSet -= exludedDmIds.toSet(); + return dmIdSet.toList(); +} + +/*! + * @brief Checks whether response contains all requested entries. + * + * @param[in] sfRes Stored files response structure + * @param[in] sentDmIds Set identifiers. + * @return True if response contains requested entries. + */ +static +bool receivedRequestedContent(const StoredFilesResp &sfRes, + const QList &sentDmIds) +{ + QSet sentDmIdSet(sentDmIds.toSet()); + + foreach (const DmEntry &entry, sfRes.dms()) { + if (!sentDmIdSet.remove(entry.dmId())) { + logErrorNL("%s", "Obtained response for message that has not been requested."); + return false; + } + } + + if (!sentDmIdSet.isEmpty()) { + logErrorNL("%s", "Did not obtain all requested message entries."); + return false; + } + + return true; +} + +/*! + * @brief Store response into database. + * + * @param[in] sfRes Stored files response structure. + * @param[in] clear If true then all messages not held within the service will + * be explicitly removed. + * @return True on success, false on error. + */ +static +bool storeStoredFilesResponseContent(const StoredFilesResp &sfRes, bool clear) +{ + /* Process only messages. */ + + if (Q_NULLPTR == globRecordsManagementDbPtr) { + return false; + } + + if (!globRecordsManagementDbPtr->beginTransaction()) { + return false; + } + + foreach (const DmEntry &entry, sfRes.dms()) { + if (entry.locations().isEmpty()) { + /* Not held within the records management service. */ + if (clear) { + globRecordsManagementDbPtr->deleteStoredMsg( + entry.dmId()); + } else { + continue; + } + } + + if (!globRecordsManagementDbPtr->updateStoredMsg(entry.dmId(), + entry.locations())) { + logErrorNL("%s", "Could not update information about message."); + goto fail; + } + } + + if (!globRecordsManagementDbPtr->commitTransaction()) { + goto fail; + } + return true; + +fail: + globRecordsManagementDbPtr->rollbackTransaction(); + return false; +} + +/*! + * @brief Process response. + * + * @param[in] clear If true then all messages not held within the service will + * be explicitly removed. + * @param[in] sfRes Stored files response structure. + * @param[in] sentDmIds Set identifiers. + * @param[out] limit Obtained limit. + * @return Number of processed responses, negative value on error. + */ +static +int processStoredFilesResponse(bool clear, const StoredFilesResp &sfRes, + const QList &sentDmIds, int &limit) +{ + /* Limit should always be received. */ + if (sfRes.limit() <= 0) { + Q_ASSERT(0); + return -1; + } + limit = sfRes.limit(); + + switch (sfRes.error().code()) { + case ErrorEntry::ERR_NO_ERROR: + break; + case ErrorEntry::ERR_LIMIT_EXCEEDED: + return 0; /* Nothing was processed. Should ask again. */ + break; + default: + logErrorNL("Received error '%s'.", + sfRes.error().trVerbose().toUtf8().constData()); + return -1; + break; + } + + if (!receivedRequestedContent(sfRes, sentDmIds)) { + return -1; + } + + if (!storeStoredFilesResponseContent(sfRes, clear)) { + return -1; + } + + return sfRes.dms().size(); +} + +/*! + * @brief Call records management service and process response. + * + * @param[in] clear If true then all messages not held within the service will + * be explicitly removed. + * @param[in] rmc Records management service connection. + * @param[in] dmIds Message identifiers. + * @param[out] limit Obtained limit. + * @return Number of processed responses, negative value on error. + */ +static +int callStoredFiles(bool clear, RecordsManagementConnection &rmc, + const QList &dmIds, int &limit) +{ + if (dmIds.isEmpty()) { + Q_ASSERT(0); + return -1; + } + + StoredFilesReq sfReq(dmIds, QList()); + if (!sfReq.isValid()) { + logErrorNL("%s", "Could not create stored_files request."); + return -1; + } + + QByteArray response; + + if (rmc.communicate(RecordsManagementConnection::SRVC_STORED_FILES, + sfReq.toJson(), response)) { + if (!response.isEmpty()) { + bool ok = false; + StoredFilesResp sfRes(StoredFilesResp::fromJson(response, &ok)); + if (!ok || !sfRes.isValid()) { + logErrorNL("%s", + "Communication error. Received invalid response to stored_files service."); + logErrorNL("Invalid response '%s'.", response.constData()); + return -1; + } + + return processStoredFilesResponse(clear, sfRes, dmIds, + limit); + } else { + logErrorNL("%s", + "Communication error. Received empty response to stored_files service."); + return -1; + } + } else { + logErrorNL("%s", + "Communication error when attempting to access stored_files service."); + return -1; + } +} + +/*! + * @brief Calls stored files service for all supplied message identifiers. + * + * @param[in] urlStr Records management URL. + * @param[in] tokenStr Records management access token. + * @param[in] dmIds Message identifiers. + * @param[in] clear If true then all messages not held within the service will + * be explicitly removed. + * @return Processing state. + */ +static +enum TaskRecordsManagementStoredMessages::Result updateMessages( + const QString &urlStr, const QString &tokenStr, const QList &dmIds, + bool clear) +{ + if (dmIds.isEmpty()) { + return TaskRecordsManagementStoredMessages::DS_DSM_SUCCESS; + } + + RecordsManagementConnection rmc( + RecordsManagementConnection::ignoreSslErrorsDflt); + rmc.setConnection(urlStr, tokenStr); + + int pos = 0; /* Position. */ + int currentLimit = 1; /* Start with smallest query to obtain maximal size. */ + int nextLimit = 0; + + /* While list is not processed. */ + while ((pos >= 0) && (pos < dmIds.size())) { + QList queryList(dmIds.mid(pos, currentLimit)); + + int ret = callStoredFiles(clear, rmc, queryList, nextLimit); + if (ret < 0) { + return TaskRecordsManagementStoredMessages::DS_DSM_ERR; + } + pos += ret; + currentLimit = nextLimit; /* Update limit. */ + } + + return TaskRecordsManagementStoredMessages::DS_DSM_ERR; +} + +enum TaskRecordsManagementStoredMessages::Result +TaskRecordsManagementStoredMessages::downloadStoredMessages( + const QString &urlStr, const QString &tokenStr, enum Operation operation, + MessageDb *msgDb, const QList &exludedDmIds) +{ + if ((RM_DOWNLOAD_ALL == operation) && (Q_NULLPTR == msgDb)) { + Q_ASSERT(0); + return DS_DSM_ERR; + } + + if (Q_NULLPTR == globRecordsManagementDbPtr) { + return DS_DSM_DB_INS_ERR; + } + + QList dmIds; + if (RM_DOWNLOAD_ALL == operation) { + dmIds = obtainDbSetDmIds(msgDb, exludedDmIds); + } else { + dmIds = obtainHeldDmIds(exludedDmIds); + } + return updateMessages(urlStr, tokenStr, dmIds, + RM_UPDATE_STORED == operation); +} diff --git a/src/worker/task_records_management_stored_messages.h b/src/worker/task_records_management_stored_messages.h new file mode 100644 index 00000000..67d80731 --- /dev/null +++ b/src/worker/task_records_management_stored_messages.h @@ -0,0 +1,113 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +#include "src/sqlite/message_db_container.h" +#include "src/worker/task.h" + +/*! + * @brief Task describing downloading information about stored messages. + */ +class TaskRecordsManagementStoredMessages : public Task { +public: + /*! + * @brief Return state describing what happened. + */ + enum Result { + DS_DSM_SUCCESS, /*!< Operation was successful. */ + DS_DSM_COM_ERROR, /*!< Error communicating with ISDS. */ + DS_DSM_DB_INS_ERR, /*!< Error inserting into database. */ + DS_DSM_ERR /*!< Other error. */ + }; + + /*! + * @brief Operation to be performed. + */ + enum Operation { + RM_UPDATE_STORED, /*!< Update only messages in records management database. */ + RM_DOWNLOAD_ALL /*!< Download all messages that are held in database set. */ + }; + + /*! + * @brief Constructor. + * + * @param[in] urlStr Records management URL. + * @param[in] tokenStr Records management access token. + * @param[in] operation Actual action to be performed. + * @param[in] msgDb Database set to be used to obtain message identifiers. + * @patam[in] exludedDmIds Message identifiers that should not be queried. + */ + explicit TaskRecordsManagementStoredMessages( + const QString &urlStr, const QString &tokenStr, + enum Operation operation, MessageDb *msgDb, + const QList &exludedDmIds = QList()); + + /*! + * @brief Performs actual message download. + */ + virtual + void run(void) Q_DECL_OVERRIDE; + + /*! + * @brief Returns task identifier. + * + * @return Task identifier string. + */ + const QString &id(void) const; + + enum Result m_result; /*!< Return state. */ + +private: + /*! + * @brief Download stored files information and save to records + * management database. + * + * @param[in] urlStr Records management URL. + * @param[in] tokenStr Records management access token. + * @param[in] operation Actual action to be performed. + * @param[in] msgDb Database set to be used to obtain message identifiers. + * @patam[in] exludedDmIds Message identifiers that should not be queried. + * @return Return state. + */ + static + enum Result downloadStoredMessages(const QString &urlStr, + const QString &tokenStr, enum Operation operation, + MessageDb *msgDb, const QList &exludedDmIds); + + const QString m_id; /*!< Task identifier. */ + + const QString m_url; /*!< String containing records management URL. */ + const QString m_token; /*!< Records management access token. */ + + const enum Operation m_operation; /*!< Operation to be performed. */ + + MessageDb *m_msgDb; /*!< Pointer to database container. */ + const QList m_exludedDmIds; /*!< + * List of messages that should + * not be queried. + */ +}; -- GitLab From 66308f62e0ea18754304b3880d10a7f3a3de9abd Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Tue, 30 Jan 2018 12:49:51 +0100 Subject: [PATCH 05/63] Added update hierarchy and upload file --- qml/pages/PageRecordsManagementSettings.qml | 11 +- src/records_management.cpp | 190 ++++++++++++++++++-- src/records_management.h | 66 ++++--- 3 files changed, 231 insertions(+), 36 deletions(-) diff --git a/qml/pages/PageRecordsManagementSettings.qml b/qml/pages/PageRecordsManagementSettings.qml index 798c486a..505097b0 100644 --- a/qml/pages/PageRecordsManagementSettings.qml +++ b/qml/pages/PageRecordsManagementSettings.qml @@ -70,7 +70,7 @@ Item { source: "qrc:/ui/checkbox-marked-circle.svg" accessibleName: qsTr("Accept changes") onClicked: { - recordsManagement.updateServiceInfo(urlTextField.text, lastUrlFromSettings, "", serviceName.text, tokenName.text) + recordsManagement.updateServiceInfo(urlTextField.text, lastUrlFromSettings, serviceName.text, tokenName.text) settings.setRmUrl(urlTextField.text) settings.setRmToken(tokenTextField.text) pageView.pop(StackView.Immediate) @@ -162,6 +162,15 @@ Item { recordsManagement.callServiceInfo(urlTextField.text, tokenTextField.text) } } + AccessibleButton { + id: updateButton + height: inputItemHeight + font.pointSize: defaultTextFont.font.pointSize + text: qsTr("Update") + onClicked: { + recordsManagement.getStoredMsgInfoFromRecordsManagement(urlTextField.text, tokenTextField.text) + } + } AccessibleButton { id: clearButton height: inputItemHeight diff --git a/src/records_management.cpp b/src/records_management.cpp index 5160834c..4d5e77d6 100644 --- a/src/records_management.cpp +++ b/src/records_management.cpp @@ -22,17 +22,76 @@ */ #include +#include +#include #include "src/datovka_shared/io/records_management_db.h" #include "src/datovka_shared/records_management/json/service_info.h" +#include "src/datovka_shared/records_management/json/upload_file.h" +#include "src/datovka_shared/records_management/json/upload_hierarchy.h" #include "src/models/accountmodel.h" #include "src/records_management.h" #include "src/settings.h" #include "src/worker/pool.h" #include "src/worker/task_records_management_stored_messages.h" +/*! + * @brief Process upload file service response. + * + * @note Stores location into database. + * + * @param[in] ifRes Response structure. + * @param[in] dmId Message identifier. + * @patam[in] parent Parent window for potential error dialogues. + * @return True if response could be processed and location has been saved. + */ +static +bool processUploadFileResponse(const UploadFileResp &ufRes, qint64 dmId, + QWidget *parent = Q_NULLPTR) +{ + if (ufRes.id().isEmpty()) { + QString errorMessage( + QObject::tr("Message '%1' could not be uploaded.") + .arg(dmId)); + errorMessage += QLatin1String("\n"); + errorMessage += QObject::tr("Received error") + + QLatin1String(": ") + ufRes.error().trVerbose(); + errorMessage += QLatin1String("\n"); + errorMessage += ufRes.error().description(); + + QMessageBox::critical(parent, QObject::tr("File Upload Error"), + errorMessage); + return false; + } + + QMessageBox::information(parent, QObject::tr("Successful File Upload"), + QObject::tr("Message '%1' was successfully uploaded into the records management service.").arg(dmId) + + QStringLiteral("\n") + + QObject::tr("It can be now found in the records management service in these locations:") + + QStringLiteral("\n") + + ufRes.locations().join(QStringLiteral("\n"))); + + if (!ufRes.locations().isEmpty()) { + qCritical("%s", "Message has been stored into records management service."); + if (Q_NULLPTR != globRecordsManagementDbPtr) { + return globRecordsManagementDbPtr->updateStoredMsg(dmId, + ufRes.locations()); + } else { + Q_ASSERT(0); + return true; + } + } else { + qCritical("%s", + "Received empty location list when uploading message."); + } + + return false; +} + RecordsManagement::RecordsManagement(QObject *parent) - : QObject(parent) + : QObject(parent), + m_rmc(RecordsManagementConnection::ignoreSslErrorsDflt, Q_NULLPTR), + m_logoSvg() { globWorkPool.start(); } @@ -46,21 +105,67 @@ RecordsManagement::~RecordsManagement(void) void RecordsManagement::callServiceInfo(const QString &urlStr, const QString &tokenStr) { QByteArray response; - RecordsManagementConnection m_rmc(RecordsManagementConnection::ignoreSslErrorsDflt, Q_NULLPTR); + m_rmc.setConnection(urlStr.trimmed(), tokenStr.trimmed()); - if (m_rmc.communicate(RecordsManagementConnection::SRVC_SERVICE_INFO, QByteArray(), response)) { + if (m_rmc.communicate(RecordsManagementConnection::SRVC_SERVICE_INFO, + QByteArray(), response)) { if (!response.isEmpty()) { bool ok = false; ServiceInfoResp siRes( ServiceInfoResp::fromJson(response, &ok)); if (!ok || !siRes.isValid()) { - QMessageBox::critical(Q_NULLPTR, tr("Communication Error"), tr("Received invalid response.")); + QMessageBox::critical(Q_NULLPTR, + tr("Communication Error"), + tr("Received invalid response.")); return; } - emit serviceInfo(siRes.logoSvg(), siRes.name(), siRes.tokenName()); + + m_logoSvg = siRes.logoSvg(); + QPixmap logoPx = fromSvgToPixmap(m_logoSvg); + + /* TODO - logoPx to QML */ + emit serviceInfo(m_logoSvg, siRes.name(), siRes.tokenName()); + } else { - QMessageBox::critical(Q_NULLPTR, tr("Communication Error"), tr("Received empty response.")); + QMessageBox::critical(Q_NULLPTR, + tr("Communication Error"), + tr("Received empty response.")); + return; + } + } else { + return; + } +} + +void RecordsManagement::callUploadHierarchy(const QString &urlStr, + const QString &tokenStr) +{ + QByteArray response; + + /* Clear model. */ + //m_uploadModel.setHierarchy(UploadHierarchyResp()); + + m_rmc.setConnection(urlStr.trimmed(), tokenStr.trimmed()); + + if (m_rmc.communicate(RecordsManagementConnection::SRVC_UPLOAD_HIERARCHY, + QByteArray(), response)) { + if (!response.isEmpty()) { + bool ok = false; + UploadHierarchyResp uhRes( + UploadHierarchyResp::fromJson(response, &ok)); + if (!ok || !uhRes.isValid()) { + QMessageBox::critical(Q_NULLPTR, + tr("Communication Error"), + tr("Received invalid response.")); + return; + } + +// m_uploadModel.setHierarchy(uhRes); + + } else { + QMessageBox::critical(Q_NULLPTR, tr("Communication Error"), + tr("Received empty response.")); return; } } else { @@ -125,25 +230,25 @@ void RecordsManagement::loadStoredServiceInfo(void) return; } - emit serviceInfo(entry.logoSvg, entry.name, entry.tokenName); + m_logoSvg = entry.logoSvg; + emit serviceInfo(m_logoSvg, entry.name, entry.tokenName); } bool RecordsManagement::updateServiceInfo(const QString &urlStr, - const QString &urlStrSettings, const QString &srLogo, - const QString &srName, const QString &srToken) + const QString &urlStrSettings, const QString &srName, const QString &srToken) { if (Q_NULLPTR == globRecordsManagementDbPtr) { return false; } if (!urlStr.trimmed().isEmpty()) { - Q_ASSERT(!urlStr.trimmed().isEmpty()); + Q_ASSERT(!urlStr.trimmed().isEmpty()); RecordsManagementDb::ServiceInfoEntry entry; entry.url = urlStr.trimmed(); entry.name = srName; entry.tokenName = srToken; - entry.logoSvg = srLogo.toUtf8(); + entry.logoSvg = m_logoSvg; globRecordsManagementDbPtr->updateServiceInfo(entry); if (urlStrSettings != urlStr) { @@ -155,3 +260,66 @@ bool RecordsManagement::updateServiceInfo(const QString &urlStr, return true; } + +bool RecordsManagement::uploadMessage(const QString &urlStr, + const QString &tokenStr, qint64 dmId, const QString &msgFileName, + const QByteArray &msgData) +{ + if (msgFileName.isEmpty() || msgData.isEmpty()) { + Q_ASSERT(0); + return false; + } + + QStringList uploadIds; + + UploadFileReq ufReq(uploadIds, msgFileName, msgData); + if (!ufReq.isValid()) { + Q_ASSERT(0); + return false; + } + + QByteArray response; + + m_rmc.setConnection(urlStr.trimmed(), tokenStr.trimmed()); + + if (m_rmc.communicate(RecordsManagementConnection::SRVC_UPLOAD_FILE, + ufReq.toJson(), response)) { + if (!response.isEmpty()) { + bool ok = false; + UploadFileResp ufRes( + UploadFileResp::fromJson(response, &ok)); + if (!ok || !ufRes.isValid()) { + QMessageBox::critical(Q_NULLPTR, + tr("Communication Error"), + tr("Received invalid response.")); + qWarning("Received invalid response '%s'.", + QString(response).toUtf8().constData()); + return false; + } + return processUploadFileResponse(ufRes, dmId); + } else { + QMessageBox::critical(Q_NULLPTR, tr("Communication Error"), + tr("Received empty response.")); + return false; + } + } else { + return false; + } +} + +QPixmap RecordsManagement::fromSvgToPixmap(const QString &SvgContent) +{ + QSize ImageSize(100, 100); + + QSvgRenderer SvgRenderer(SvgContent); + QPixmap Image(ImageSize); + QPainter Painter; + + Image.fill(Qt::transparent); + + Painter.begin(&Image); + SvgRenderer.render(&Painter); + Painter.end(); + + return Image; +} diff --git a/src/records_management.h b/src/records_management.h index 43c3ec12..7cf7aafc 100644 --- a/src/records_management.h +++ b/src/records_management.h @@ -34,23 +34,6 @@ class RecordsManagement : public QObject { Q_OBJECT public: - - /*! - * @brief Describes account information. - */ - class AcntData { - public: - AcntData(const QString &aName, const QString &uName, - const MessageDb *msgDb) - : accountName(aName), userName(uName), msgDb(msgDb) - { - } - - QString accountName; /*!< Account name. */ - QString userName; /*!< User name (login). */ - const MessageDb *msgDb; /*!< Database set related to account. */ - }; - /*! * @brief Constructor. */ @@ -64,18 +47,27 @@ public: /*! * @brief Calls service info and displays results. * - * @param[in] urlStr records management url string. - * @param[in] tokenStr records management token string. + * @param[in] urlStr Records management url string. + * @param[in] tokenStr Records management token string. */ Q_INVOKABLE void callServiceInfo(const QString &urlStr, const QString &tokenStr); + /*! + * @brief Download upload hierarchy and set model. + * + * @param[in] urlStr Records management url string. + * @param[in] tokenStr Records management token string. + */ + Q_INVOKABLE + void callUploadHierarchy(const QString &urlStr, const QString &tokenStr); + /*! * @brief Obtain information about stored messages from records * management. * - * @param[in] urlStr records management url string. - * @param[in] tokenStr records management token string. + * @param[in] urlStr Records management url string. + * @param[in] tokenStr Records management token string. */ Q_INVOKABLE void getStoredMsgInfoFromRecordsManagement(const QString &urlStr, @@ -90,16 +82,15 @@ public: /*! * @brief Update record management settings. * - * @param[in] urlStr new records management url string. + * @param[in] urlStr New records management url string. * @param[in] urlStrSettings Records management url string from settings. - * @param[in] srLogo Service SVG logo. * @param[in] srName Service name. * @param[in] srToken Service token. * @return True when data have been updated, false else. */ Q_INVOKABLE bool updateServiceInfo(const QString &urlStr, const QString &urlStrSettings, - const QString &srLogo, const QString &srName, const QString &srToken); + const QString &srName, const QString &srToken); signals: @@ -111,4 +102,31 @@ signals: * @param[in] srToken Service token. */ void serviceInfo(QString srLogo, QString srName, QString srToken); + +private: + + /*! + * @brief Upload message into records management service. + * + * @param[in] urlStr Records management url string. + * @param[in] tokenStr Records management token string. + * @param[in] dmId Message identifier. + * @param[in] msgFileName Message file name. + * @param[in] msgData Message data. + * @return True when data have been updated, false else. + */ + bool uploadMessage(const QString &urlStr, + const QString &tokenStr, qint64 dmId, const QString &msgFileName, + const QByteArray &msgData); + + /*! + * @brief Convert svg to QPixmap. + * + * @param[in] SvgContent Svg content. + * @return Pixmap. + */ + QPixmap fromSvgToPixmap(const QString &SvgContent); + + RecordsManagementConnection m_rmc; + QByteArray m_logoSvg; /*!< Raw SVG data. */ }; -- GitLab From 25200c7b6dfb9591e8af1c86da4692258f012473 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Tue, 30 Jan 2018 13:07:15 +0100 Subject: [PATCH 06/63] Added upload hierarchy model --- mobile-datovka.pro | 4 + .../models/upload_hierarchy_model.cpp | 237 ++++++++++++++++++ .../models/upload_hierarchy_model.h | 161 ++++++++++++ .../models/upload_hierarchy_proxy_model.cpp | 88 +++++++ .../models/upload_hierarchy_proxy_model.h | 76 ++++++ src/records_management.cpp | 14 +- src/records_management.h | 7 +- 7 files changed, 580 insertions(+), 7 deletions(-) create mode 100644 src/datovka_shared/records_management/models/upload_hierarchy_model.cpp create mode 100644 src/datovka_shared/records_management/models/upload_hierarchy_model.h create mode 100644 src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.cpp create mode 100644 src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.h diff --git a/mobile-datovka.pro b/mobile-datovka.pro index fc0df9c1..58bd4da1 100644 --- a/mobile-datovka.pro +++ b/mobile-datovka.pro @@ -100,6 +100,8 @@ SOURCES += \ src/datovka_shared/records_management/json/stored_files.cpp \ src/datovka_shared/records_management/json/upload_file.cpp \ src/datovka_shared/records_management/json/upload_hierarchy.cpp \ + src/datovka_shared/records_management/models/upload_hierarchy_model.cpp \ + src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.cpp \ src/datovka_shared/settings/pin.cpp \ src/datovka_shared/settings/records_management.cpp \ src/datovka_shared/utility/strings.cpp \ @@ -178,6 +180,8 @@ HEADERS += \ src/datovka_shared/records_management/json/stored_files.h \ src/datovka_shared/records_management/json/upload_file.h \ src/datovka_shared/records_management/json/upload_hierarchy.h \ + src/datovka_shared/records_management/models/upload_hierarchy_model.h \ + src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.h \ src/datovka_shared/settings/pin.h \ src/datovka_shared/settings/records_management.h \ src/datovka_shared/utility/strings.h \ diff --git a/src/datovka_shared/records_management/models/upload_hierarchy_model.cpp b/src/datovka_shared/records_management/models/upload_hierarchy_model.cpp new file mode 100644 index 00000000..c579dc28 --- /dev/null +++ b/src/datovka_shared/records_management/models/upload_hierarchy_model.cpp @@ -0,0 +1,237 @@ +/* + * 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 "src/datovka_shared/records_management/models/upload_hierarchy_model.h" + +UploadHierarchyModel::UploadHierarchyModel(QObject *parent) + : QAbstractItemModel(parent), + m_hierarchy() +{ +} + +QModelIndex UploadHierarchyModel::index(int row, int column, + const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) { + return QModelIndex(); + } + + quintptr internalId = 0; + + if (!parent.isValid()) { + if (showRootName()) { + /* Root is shown. */ + internalId = (quintptr)m_hierarchy.root(); + } else { + /* Root is not shown. */ + internalId = (quintptr)m_hierarchy.root()->sub().at(row); + } + } else { + const UploadHierarchyResp::NodeEntry *entry = + (UploadHierarchyResp::NodeEntry *)parent.internalId(); + internalId = (quintptr)entry->sub().at(row); + } + + return createIndex(row, column, internalId); +} + +QModelIndex UploadHierarchyModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) { + return QModelIndex(); + } + + const UploadHierarchyResp::NodeEntry *iEntry = + (UploadHierarchyResp::NodeEntry *)index.internalId(); + const UploadHierarchyResp::NodeEntry *pEntry = iEntry->super(); + + if ((pEntry != Q_NULLPTR) && + ((pEntry != m_hierarchy.root()) || showRootName())) { + if (pEntry == m_hierarchy.root()) { + /* Root node is shown and parent is root. */ + return createIndex(0, 0, (quintptr)pEntry); + } else { + const UploadHierarchyResp::NodeEntry *ppEntry = + pEntry->super(); + int row = 0; + /* Find position of parent. */ + for ( ; row < ppEntry->sub().size(); ++row) { + if (pEntry == ppEntry->sub().at(row)) { + break; + } + } + Q_ASSERT(row < ppEntry->sub().size()); + return createIndex(row, 0, (quintptr)pEntry); + } + } else { + return QModelIndex(); + } +} + +int UploadHierarchyModel::rowCount(const QModelIndex &parent) const +{ + if (parent.column() > 0) { + return 0; + } + + if (!parent.isValid()) { + if (!m_hierarchy.isValid()) { + /* Invalid hierarchy. */ + return 0; + } + + /* Root. */ + if (showRootName()) { + return 1; + } else { + return m_hierarchy.root()->sub().size(); + } + } else { + const UploadHierarchyResp::NodeEntry *entry = + (UploadHierarchyResp::NodeEntry *)parent.internalId(); + return entry->sub().size(); + } +} + +int UploadHierarchyModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + + return 1; +} + +QVariant UploadHierarchyModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + const UploadHierarchyResp::NodeEntry *entry = + (UploadHierarchyResp::NodeEntry *)index.internalId(); + if (entry == Q_NULLPTR) { + Q_ASSERT(0); + return QVariant(); + } + + switch (role) { + case Qt::DisplayRole: + return entry->name(); + break; + case Qt::ToolTipRole: + return filterData(entry).join(QStringLiteral("\n")); + break; + case Qt::AccessibleTextRole: + return entry->name(); + break; + case ROLE_FILTER: + return filterDataRecursive(entry, true); + break; + case ROLE_ID: + return entry->id(); + break; + default: + return QVariant(); + break; + } +} + +QVariant UploadHierarchyModel::headerData(int section, + Qt::Orientation orientation, int role) const +{ + if ((Qt::Horizontal == orientation) && (Qt::DisplayRole == role) && + (0 == section)) { + return tr("Records Management Hierarchy"); + } + + return QVariant(); +} + +Qt::ItemFlags UploadHierarchyModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) { + return Qt::NoItemFlags; + } + + Qt::ItemFlags flags = + QAbstractItemModel::flags(index) & ~Qt::ItemIsEditable; + + const UploadHierarchyResp::NodeEntry *entry = + (UploadHierarchyResp::NodeEntry *)index.internalId(); + if (entry->id().isEmpty()) { + flags &= ~Qt::ItemIsSelectable; + } + + return flags; +} + +void UploadHierarchyModel::setHierarchy(const UploadHierarchyResp &uhr) +{ + beginResetModel(); + m_hierarchy = uhr; + endResetModel(); +} + +bool UploadHierarchyModel::showRootName(void) const +{ + return m_hierarchy.isValid() && !m_hierarchy.root()->name().isEmpty(); +} + +QStringList UploadHierarchyModel::filterData( + const UploadHierarchyResp::NodeEntry *entry) +{ + if (Q_UNLIKELY(entry == Q_NULLPTR)) { + Q_ASSERT(0); + return QStringList(); + } + + return QStringList(entry->name()) + entry->metadata(); +} + +QStringList UploadHierarchyModel::filterDataRecursive( + const UploadHierarchyResp::NodeEntry *entry, bool takeSuper) +{ + if (Q_UNLIKELY(entry == Q_NULLPTR)) { + Q_ASSERT(0); + return QStringList(); + } + + QStringList res(filterData(entry)); + foreach (const UploadHierarchyResp::NodeEntry *sub, entry->sub()) { + res += filterDataRecursive(sub, false); + } + if (takeSuper) { + /* + * Add also filter data from superordinate node. This has the + * effect that all sub-nodes (including those not matching the + * filter) of a node which matches the entered filter are + * going to be also displayed. + */ + const UploadHierarchyResp::NodeEntry *sup = entry->super(); + if (Q_UNLIKELY(sup == Q_NULLPTR)) { + return res; + } + res += filterData(sup); + } + + return res; +} diff --git a/src/datovka_shared/records_management/models/upload_hierarchy_model.h b/src/datovka_shared/records_management/models/upload_hierarchy_model.h new file mode 100644 index 00000000..f111368f --- /dev/null +++ b/src/datovka_shared/records_management/models/upload_hierarchy_model.h @@ -0,0 +1,161 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include "src/datovka_shared/records_management/json/upload_hierarchy.h" + +/*! + * @brief Upload hierarchy model. + */ +class UploadHierarchyModel : public QAbstractItemModel { + Q_OBJECT +public: + /*! + * @brief Custom roles. + */ + enum CustomRoles { + ROLE_FILTER = (Qt::UserRole + 1), /* Exposes metadata for filtering. */ + ROLE_ID /* Hierarchy entry identifier. */ + }; + + /*! + * @brief Constructor. + * + * @param[in] parent Pointer to parent object. + */ + explicit UploadHierarchyModel(QObject *parent = Q_NULLPTR); + + /*! + * @brief Return index specified by supplied parameters. + * + * @param[in] row Item row. + * @param[in] column Parent column. + * @param[in] parent Parent index. + * @return Index to desired element or invalid index on error. + */ + virtual + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + + /*! + * @brief Return parent index of the item with the given index. + * + * @param[in] index Child node index. + * @return Index of the parent node or invalid index on error. + */ + virtual + QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE; + + /*! + * @brief Return number of rows under the given parent. + * + * @param[in] parent Parent node index. + * @return Number of rows. + */ + virtual + int rowCount(const QModelIndex &parent = QModelIndex()) const + Q_DECL_OVERRIDE; + + /*! + * @brief Return the number of columns for the children of given parent. + * + * @param[in] parent Parent node index. + * @return Number of columns. + */ + virtual + int columnCount(const QModelIndex &parent = QModelIndex()) const + Q_DECL_OVERRIDE; + + /*! + * @brief Return data stored in given location under given role. + * + * @param[in] index Index specifying the item. + * @param[in] role Data role. + * @return Data from model. + */ + virtual + QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; + + /*! + * @brief Returns header data in given location under given role. + * + * @brief[in] section Header position. + * @brief[in] orientation Header orientation. + * @brief[in] role Data role. + * @return Header data from model. + */ + virtual + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; + + /*! + * @brief Returns item flags for given index. + * + * @brief[in] index Index specifying the item. + * @return Item flags. + */ + virtual + Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE; + + /*! + * @brief Set content data. + * + * @param[in] uhr Upload hierarchy response structure. + */ + void setHierarchy(const UploadHierarchyResp &uhr); + +private: + /*! + * @brief Check whether to show root name. + * + * @return True in root node contains a name. + */ + bool showRootName(void) const; + + /*! + * @brief Return all data related to node which can be used for + * filtering. + * + * @param[in] entry Node identifier. + * @return List of strings. + */ + static + QStringList filterData(const UploadHierarchyResp::NodeEntry *entry); + + /*! + * @brief Returns list of all (meta)data (including children). + * + * @param[in] entry Node identifying the root. + * @param[in] takeSuper Set true when data of superordinate node should + * be taken into account. + * @return List of all gathered data according to which can be filtered. + */ + static + QStringList filterDataRecursive( + const UploadHierarchyResp::NodeEntry *entry, bool takeSuper); + + UploadHierarchyResp m_hierarchy; /*!< Upload hierarchy structure. */ +}; diff --git a/src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.cpp b/src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.cpp new file mode 100644 index 00000000..16fc6d99 --- /dev/null +++ b/src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.cpp @@ -0,0 +1,88 @@ +/* + * 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 "src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.h" + +UploadHierarchyProxyModel::UploadHierarchyProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ +} + +bool UploadHierarchyProxyModel::filterAcceptsRow(int sourceRow, + const QModelIndex &sourceParent) const +{ + /* + * Adapted from + * qtbase/src/corelib/itemmodels/qsortfilterproxymodel.cpp . + */ + + if (filterRegExp().isEmpty()) { + return true; + } + + QModelIndex sourceIndex(sourceModel()->index(sourceRow, + filterKeyColumn(), sourceParent)); + return filterAcceptsItem(sourceIndex); +} + +bool UploadHierarchyProxyModel::lessThan(const QModelIndex &sourceLeft, + const QModelIndex &sourceRight) const +{ + QVariant leftData(sourceModel()->data(sourceLeft, filterRole())); + QVariant rightData(sourceModel()->data(sourceRight, filterRole())); + + if (Q_LIKELY(leftData.canConvert())) { + Q_ASSERT(rightData.canConvert()); + return (leftData.toString() < rightData.toString()); + } else { + return QSortFilterProxyModel::lessThan(sourceLeft, sourceRight); + } +} + +bool UploadHierarchyProxyModel::filterAcceptsItem( + const QModelIndex &sourceIdx) const +{ + if (!sourceIdx.isValid()) { + return false; + } + + QStringList filterData; + { + const QVariant data( + sourceModel()->data(sourceIdx, filterRole())); + if (data.canConvert()) { + filterData += data.toString(); + } else if (data.canConvert()) { + filterData += data.toStringList(); + } else { + return false; + } + } + + foreach (const QString &str, filterData) { + if (str.contains(filterRegExp())) { + return true; + } + } + return false; +} diff --git a/src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.h b/src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.h new file mode 100644 index 00000000..3dc1dce0 --- /dev/null +++ b/src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.h @@ -0,0 +1,76 @@ +/* + * 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. + */ + +#pragma once + +#include + +/*! + * @brief Enables filtering according to metadata. + */ +class UploadHierarchyProxyModel : public QSortFilterProxyModel { + Q_OBJECT +public: + /*! + * @brief Constructor. + * + * @param[in] parent Parent object. + */ + explicit UploadHierarchyProxyModel(QObject *parent = Q_NULLPTR); + +protected: + /*! + * @brief Returns true if the item in the row indicated by the + * given source row and source parent should be included in the + * model; otherwise returns false. + * + * @param[in] sourceRow Row number. + * @param[in] sourceParent Parent index. + * @return Whether the row indicated should be included in the model. + */ + virtual + bool filterAcceptsRow(int sourceRow, + const QModelIndex &sourceParent) const Q_DECL_OVERRIDE; + + /*! + * @brief Returns true if the value of the item referred to by the + * given left index is less than the value of the item referred to + * by the given right index, otherwise returns false. + * + * @param[in] sourceLeft Left index. + * @param[in] sourceRight Right index. + * @return Whether the left index precedes the right index. + */ + virtual + bool lessThan(const QModelIndex &sourceLeft, + const QModelIndex &sourceRight) const Q_DECL_OVERRIDE; + +private: + /*! + * @brief Returns true if the item should be included in the model. + * + * @param[in] sourceIdx Source index. + * @return Whether the item meets the criteria. + */ + bool filterAcceptsItem(const QModelIndex &sourceIdx) const; +}; diff --git a/src/records_management.cpp b/src/records_management.cpp index 4d5e77d6..0be1ccfc 100644 --- a/src/records_management.cpp +++ b/src/records_management.cpp @@ -90,7 +90,9 @@ bool processUploadFileResponse(const UploadFileResp &ufRes, qint64 dmId, RecordsManagement::RecordsManagement(QObject *parent) : QObject(parent), - m_rmc(RecordsManagementConnection::ignoreSslErrorsDflt, Q_NULLPTR), + m_uploadModel(), + m_uploadProxyModel(), + m_rmc(RecordsManagementConnection::ignoreSslErrorsDflt, this), m_logoSvg() { globWorkPool.start(); @@ -102,7 +104,8 @@ RecordsManagement::~RecordsManagement(void) globWorkPool.stop(); } -void RecordsManagement::callServiceInfo(const QString &urlStr, const QString &tokenStr) +void RecordsManagement::callServiceInfo(const QString &urlStr, + const QString &tokenStr) { QByteArray response; @@ -125,7 +128,8 @@ void RecordsManagement::callServiceInfo(const QString &urlStr, const QString &to QPixmap logoPx = fromSvgToPixmap(m_logoSvg); /* TODO - logoPx to QML */ - emit serviceInfo(m_logoSvg, siRes.name(), siRes.tokenName()); + emit serviceInfo(m_logoSvg, siRes.name(), + siRes.tokenName()); } else { QMessageBox::critical(Q_NULLPTR, @@ -144,7 +148,7 @@ void RecordsManagement::callUploadHierarchy(const QString &urlStr, QByteArray response; /* Clear model. */ - //m_uploadModel.setHierarchy(UploadHierarchyResp()); + m_uploadModel.setHierarchy(UploadHierarchyResp()); m_rmc.setConnection(urlStr.trimmed(), tokenStr.trimmed()); @@ -161,7 +165,7 @@ void RecordsManagement::callUploadHierarchy(const QString &urlStr, return; } -// m_uploadModel.setHierarchy(uhRes); + m_uploadModel.setHierarchy(uhRes); } else { QMessageBox::critical(Q_NULLPTR, tr("Communication Error"), diff --git a/src/records_management.h b/src/records_management.h index 7cf7aafc..bb4dbebc 100644 --- a/src/records_management.h +++ b/src/records_management.h @@ -26,7 +26,8 @@ #include #include "src/sqlite/message_db_container.h" - +#include "src/datovka_shared/records_management/models/upload_hierarchy_model.h" +#include "src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.h" #include "src/datovka_shared/records_management/io/records_management_connection.h" #include "src/datovka_shared/settings/records_management.h" @@ -127,6 +128,8 @@ private: */ QPixmap fromSvgToPixmap(const QString &SvgContent); - RecordsManagementConnection m_rmc; + UploadHierarchyModel m_uploadModel; /*!< Upload hierarchy model. */ + UploadHierarchyProxyModel m_uploadProxyModel; /*!< Used for filtering. */ + RecordsManagementConnection m_rmc; /*!< Connection to records management service. */ QByteArray m_logoSvg; /*!< Raw SVG data. */ }; -- GitLab From 8643293bcdfbdcd03fd0ad2546dbb9028b6420cf Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Tue, 30 Jan 2018 13:46:35 +0100 Subject: [PATCH 07/63] Added svg graphics --- mobile-datovka.pro | 2 + qml/pages/PageRecordsManagementSettings.qml | 1 - src/datovka_shared/graphics/graphics.cpp | 49 +++++++++++ src/datovka_shared/graphics/graphics.h | 49 +++++++++++ src/records_management.cpp | 95 +++++++++++---------- src/records_management.h | 41 ++++----- 6 files changed, 171 insertions(+), 66 deletions(-) create mode 100644 src/datovka_shared/graphics/graphics.cpp create mode 100644 src/datovka_shared/graphics/graphics.h diff --git a/mobile-datovka.pro b/mobile-datovka.pro index 58bd4da1..898a9aec 100644 --- a/mobile-datovka.pro +++ b/mobile-datovka.pro @@ -88,6 +88,7 @@ SOURCES += \ src/accounts.cpp \ src/auxiliaries/attachment_helper.cpp \ src/auxiliaries/email_helper.cpp \ + src/datovka_shared/graphics/graphics.cpp \ src/datovka_shared/io/records_management_db.cpp \ src/datovka_shared/io/sqlite/db.cpp \ src/datovka_shared/io/sqlite/db_single.cpp \ @@ -168,6 +169,7 @@ HEADERS += \ src/auxiliaries/attachment_helper.h \ src/auxiliaries/email_helper.h \ src/common.h \ + src/datovka_shared/graphics/graphics.h \ src/datovka_shared/io/records_management_db.h \ src/datovka_shared/io/sqlite/db.h \ src/datovka_shared/io/sqlite/db_single.h \ diff --git a/qml/pages/PageRecordsManagementSettings.qml b/qml/pages/PageRecordsManagementSettings.qml index 505097b0..4317966a 100644 --- a/qml/pages/PageRecordsManagementSettings.qml +++ b/qml/pages/PageRecordsManagementSettings.qml @@ -237,7 +237,6 @@ Item { serviceLogo.visible = serviceInfo1.visible serviceName.text = srName tokenName.text = srToken - //serviceLogo.source = srLogo } } } // Item diff --git a/src/datovka_shared/graphics/graphics.cpp b/src/datovka_shared/graphics/graphics.cpp new file mode 100644 index 00000000..762f6256 --- /dev/null +++ b/src/datovka_shared/graphics/graphics.cpp @@ -0,0 +1,49 @@ +/* + * 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 "src/datovka_shared/graphics/graphics.h" + +QPixmap Graphics::pixmapFromSvg(const QByteArray &svgData, int edgeLen) +{ + if (svgData.isEmpty()) { + return QPixmap(); + } + + QSvgRenderer renderer; + if (!renderer.load(svgData)) { + return QPixmap(); + } + + QImage image(edgeLen, edgeLen, QImage::Format_ARGB32); + QPainter painter(&image); + renderer.render(&painter); + + QPixmap pixmap; + pixmap.convertFromImage(image); + + return pixmap; +} diff --git a/src/datovka_shared/graphics/graphics.h b/src/datovka_shared/graphics/graphics.h new file mode 100644 index 00000000..c3a30b92 --- /dev/null +++ b/src/datovka_shared/graphics/graphics.h @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +/*! + * @brief Encapsulates some graphics-related routines. + */ +class Graphics { +public: + /*! + * @brief Draws a pixmap from supplied SVG data. + * + * @param[in] svgData SVG image data. + * @param[in] edgeLen Edge size of the drawn image square. + * @return Null pixmap on error. + */ + static + QPixmap pixmapFromSvg(const QByteArray &svgData, int edgeLen); + +private: + /*! + * @brief Private constructor. + */ + Graphics(void); +}; diff --git a/src/records_management.cpp b/src/records_management.cpp index 0be1ccfc..64c72d3b 100644 --- a/src/records_management.cpp +++ b/src/records_management.cpp @@ -22,9 +22,8 @@ */ #include -#include -#include +#include "src/datovka_shared/graphics/graphics.h" #include "src/datovka_shared/io/records_management_db.h" #include "src/datovka_shared/records_management/json/service_info.h" #include "src/datovka_shared/records_management/json/upload_file.h" @@ -125,11 +124,8 @@ void RecordsManagement::callServiceInfo(const QString &urlStr, } m_logoSvg = siRes.logoSvg(); - QPixmap logoPx = fromSvgToPixmap(m_logoSvg); - - /* TODO - logoPx to QML */ - emit serviceInfo(m_logoSvg, siRes.name(), - siRes.tokenName()); + loadRecordsManagementPixmap(m_logoSvg); + emit serviceInfo(siRes.name(), siRes.tokenName()); } else { QMessageBox::critical(Q_NULLPTR, @@ -235,34 +231,8 @@ void RecordsManagement::loadStoredServiceInfo(void) } m_logoSvg = entry.logoSvg; - emit serviceInfo(m_logoSvg, entry.name, entry.tokenName); -} - -bool RecordsManagement::updateServiceInfo(const QString &urlStr, - const QString &urlStrSettings, const QString &srName, const QString &srToken) -{ - if (Q_NULLPTR == globRecordsManagementDbPtr) { - return false; - } - - if (!urlStr.trimmed().isEmpty()) { - - Q_ASSERT(!urlStr.trimmed().isEmpty()); - RecordsManagementDb::ServiceInfoEntry entry; - entry.url = urlStr.trimmed(); - entry.name = srName; - entry.tokenName = srToken; - entry.logoSvg = m_logoSvg; - globRecordsManagementDbPtr->updateServiceInfo(entry); - - if (urlStrSettings != urlStr) { - globRecordsManagementDbPtr->deleteAllStoredMsg(); - } - } else { - globRecordsManagementDbPtr->deleteAllEntries(); - } - - return true; + loadRecordsManagementPixmap(entry.logoSvg); + emit serviceInfo(entry.name, entry.tokenName); } bool RecordsManagement::uploadMessage(const QString &urlStr, @@ -311,19 +281,54 @@ bool RecordsManagement::uploadMessage(const QString &urlStr, } } -QPixmap RecordsManagement::fromSvgToPixmap(const QString &SvgContent) +bool RecordsManagement::updateServiceInfo(const QString &urlStr, + const QString &urlStrSettings, const QString &srName, const QString &srToken) +{ + if (Q_NULLPTR == globRecordsManagementDbPtr) { + return false; + } + + if (!urlStr.trimmed().isEmpty()) { + + Q_ASSERT(!urlStr.trimmed().isEmpty()); + RecordsManagementDb::ServiceInfoEntry entry; + entry.url = urlStr.trimmed(); + entry.name = srName; + entry.tokenName = srToken; + entry.logoSvg = m_logoSvg; + globRecordsManagementDbPtr->updateServiceInfo(entry); + + if (urlStrSettings != urlStr) { + globRecordsManagementDbPtr->deleteAllStoredMsg(); + } + } else { + globRecordsManagementDbPtr->deleteAllEntries(); + } + + return true; +} + +void RecordsManagement::loadRecordsManagementPixmap(const QByteArray &logoSvg) { - QSize ImageSize(100, 100); + if (Q_NULLPTR == globRecordsManagementDbPtr) { + return; + } - QSvgRenderer SvgRenderer(SvgContent); - QPixmap Image(ImageSize); - QPainter Painter; + QByteArray logo = logoSvg; - Image.fill(Qt::transparent); + if (logo.isNull()) { - Painter.begin(&Image); - SvgRenderer.render(&Painter); - Painter.end(); + RecordsManagementDb::ServiceInfoEntry entry( + globRecordsManagementDbPtr->serviceInfo()); + if (!entry.isValid() || entry.logoSvg.isEmpty()) { + return; + } + logo = entry.logoSvg; + } - return Image; + QPixmap pixmap(Graphics::pixmapFromSvg(logo, LOGO_EDGE)); + if (!pixmap.isNull()) { + /* TODO - set Pixmap into QML */ + //m_ui->pixmapLabel->setPixmap(pixmap); + } } diff --git a/src/records_management.h b/src/records_management.h index bb4dbebc..f02b7b1e 100644 --- a/src/records_management.h +++ b/src/records_management.h @@ -31,6 +31,8 @@ #include "src/datovka_shared/records_management/io/records_management_connection.h" #include "src/datovka_shared/settings/records_management.h" +#define LOGO_EDGE 64 + class RecordsManagement : public QObject { Q_OBJECT @@ -80,6 +82,21 @@ public: Q_INVOKABLE void loadStoredServiceInfo(void); + /*! + * @brief Upload message into records management service. + * + * @param[in] urlStr Records management url string. + * @param[in] tokenStr Records management token string. + * @param[in] dmId Message identifier. + * @param[in] msgFileName Message file name. + * @param[in] msgData Message data. + * @return True when data have been updated, false else. + */ + Q_INVOKABLE + bool uploadMessage(const QString &urlStr, + const QString &tokenStr, qint64 dmId, const QString &msgFileName, + const QByteArray &msgData); + /*! * @brief Update record management settings. * @@ -98,35 +115,19 @@ signals: /*! * @brief Send service info to QML. * - * @param[in] srLogo Service SVG logo. * @param[in] srName Service name. * @param[in] srToken Service token. */ - void serviceInfo(QString srLogo, QString srName, QString srToken); + void serviceInfo(QString srName, QString srToken); private: /*! - * @brief Upload message into records management service. - * - * @param[in] urlStr Records management url string. - * @param[in] tokenStr Records management token string. - * @param[in] dmId Message identifier. - * @param[in] msgFileName Message file name. - * @param[in] msgData Message data. - * @return True when data have been updated, false else. - */ - bool uploadMessage(const QString &urlStr, - const QString &tokenStr, qint64 dmId, const QString &msgFileName, - const QByteArray &msgData); - - /*! - * @brief Convert svg to QPixmap. + * @brief Loads records management service logo and sets the logo label. * - * @param[in] SvgContent Svg content. - * @return Pixmap. + * @param[in] logoSvg Service SVG logo. */ - QPixmap fromSvgToPixmap(const QString &SvgContent); + void loadRecordsManagementPixmap(const QByteArray &logoSvg); UploadHierarchyModel m_uploadModel; /*!< Upload hierarchy model. */ UploadHierarchyProxyModel m_uploadProxyModel; /*!< Used for filtering. */ -- GitLab From d7d6af8ba52c2c53ea4044c5526f1f204bde0d12 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Tue, 30 Jan 2018 17:26:13 +0100 Subject: [PATCH 08/63] Updated records management settings page --- qml/pages/PageRecordsManagementSettings.qml | 139 +++++++++++--------- 1 file changed, 80 insertions(+), 59 deletions(-) diff --git a/qml/pages/PageRecordsManagementSettings.qml b/qml/pages/PageRecordsManagementSettings.qml index 4317966a..2f69c142 100644 --- a/qml/pages/PageRecordsManagementSettings.qml +++ b/qml/pages/PageRecordsManagementSettings.qml @@ -35,20 +35,33 @@ Item { /* Remember last service url from settings */ property string lastUrlFromSettings: "" - /* Enable button if all required fields are filled */ - function areReguiredFieldsFilled() { + /* Enable info and clear buttons if url and token fields are filled */ + function areUrlandTokenFilled() { infoButton.enabled = (urlTextField.text.toString() !== "" && tokenTextField.text.toString() !== "") clearButton.enabled = infoButton.enabled - return infoButton.enabled + } + + /* Enable data update and anothers labels if service name and token are filled */ + function areServiceInfoAvailable() { + serviceInfo.visible = (serviceName.text.toString() !== "" && tokenName.text.toString() !== "") + infoButton.text = (updateRmDataButton.visible) ? qsTr("Update service info") : qsTr("Get service info") + } + + /* Enable data update and anothers labels if service name and token are filled */ + function clearAll() { + urlTextField.clear() + tokenTextField.clear() + serviceInfo.visible = false + areUrlandTokenFilled() + infoButton.text = qsTr("Get service info") } Component.onCompleted: { + serviceInfo.visible = false urlTextField.text = settings.rmUrl() lastUrlFromSettings = settings.rmUrl() tokenTextField.text = settings.rmToken() - serviceInfo1.visible = false - serviceInfo2.visible = serviceInfo1.visible - serviceLogo.visible = serviceInfo1.visible + areUrlandTokenFilled() recordsManagement.loadStoredServiceInfo() } @@ -98,7 +111,7 @@ Item { wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter width: parent.width - text: qsTr("Please fill service url, identification token and click button 'Get service info'") + text: qsTr("Please fill service URL and your identification token. Then click button 'Get service info' to login and register records management service. You should received service info.") } AccessibleText { color: datovkaPalette.text @@ -122,7 +135,7 @@ Item { } } onTextChanged: { - areReguiredFieldsFilled() + areUrlandTokenFilled() } } AccessibleText { @@ -147,7 +160,7 @@ Item { } } onTextChanged: { - areReguiredFieldsFilled() + areUrlandTokenFilled() } } Row { @@ -162,81 +175,89 @@ Item { recordsManagement.callServiceInfo(urlTextField.text, tokenTextField.text) } } - AccessibleButton { - id: updateButton - height: inputItemHeight - font.pointSize: defaultTextFont.font.pointSize - text: qsTr("Update") - onClicked: { - recordsManagement.getStoredMsgInfoFromRecordsManagement(urlTextField.text, tokenTextField.text) - } - } AccessibleButton { id: clearButton height: inputItemHeight font.pointSize: defaultTextFont.font.pointSize - text: qsTr("Clear") + text: qsTr("Clear service") onClicked: { - urlTextField.clear() - tokenTextField.clear() - serviceInfo1.visible = false - serviceInfo2.visible = serviceInfo1.visible - serviceLogo.visible = serviceInfo1.visible + clearAll() } } } // Row - Row { - id: serviceInfo1 + Column { + id: serviceInfo + width: parent.width spacing: formItemVerticalSpacing - visible: false AccessibleText { - height: inputItemHeight font.pointSize: defaultTextFont.font.pointSize - text: qsTr("Service name:") + text: qsTr("Records management info") + ":" } + Grid { + columns: 2 + spacing: formItemVerticalSpacing + AccessibleText { + font.pointSize: defaultTextFont.font.pointSize + text: qsTr("Name") + ":" + } + AccessibleText { + id: serviceName + font.pointSize: defaultTextFont.font.pointSize + font.bold: true + text: "" + } + AccessibleText { + font.pointSize: defaultTextFont.font.pointSize + text: qsTr("Token") + ":" + } + AccessibleText { + id: tokenName + font.pointSize: defaultTextFont.font.pointSize + font.bold: true + text: "" + } + AccessibleText { + height: imgHeightHeader + font.pointSize: defaultTextFont.font.pointSize + verticalAlignment: Text.AlignVCenter + text: qsTr("Logo") + ":" + } + AccessibleImageButton { + id: serviceLogo + width: imgHeightHeader + height: imgHeightHeader + source: "qrc:/ui/briefcase.svg" + accessibleName: qsTr("Records management logo") + } + } // Grid AccessibleText { - id: serviceName - height: inputItemHeight - font.pointSize: defaultTextFont.font.pointSize - text: "" - } - } // Row - Row { - id: serviceInfo2 - spacing: formItemVerticalSpacing - visible: false - AccessibleText { - height: inputItemHeight - font.pointSize: defaultTextFont.font.pointSize - text: qsTr("Token name:") + color: datovkaPalette.mid + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + width: parent.width + text: qsTr("Click button below for synchronization of information about uploaded files.") } - AccessibleText { - id: tokenName + AccessibleButton { + id: updateRmDataButton height: inputItemHeight font.pointSize: defaultTextFont.font.pointSize - text: "" + anchors.horizontalCenter: parent.horizontalCenter + text: qsTr("Update list of uploaded files") + onClicked: { + recordsManagement.getStoredMsgInfoFromRecordsManagement(urlTextField.text, tokenTextField.text) + } } - } // Row - AccessibleImageButton { - id: serviceLogo - anchors.horizontalCenter: parent.horizontalCenter - width: imgHeightHeader * 1.4 - height: imgHeightHeader * 1.4 - source: "qrc:/ui/briefcase.svg" - accessibleName: qsTr("Records management logo.") - } - } // Column layout + } // // Column + } // Column } // Pane ScrollIndicator.vertical: ScrollIndicator {} } // Flickable Connections { target: recordsManagement onServiceInfo: { - serviceInfo1.visible = true - serviceInfo2.visible = serviceInfo1.visible - serviceLogo.visible = serviceInfo1.visible serviceName.text = srName tokenName.text = srToken + areServiceInfoAvailable() } } } // Item -- GitLab From 0f2aefb7f7dc9d4385134322995ef2cebbad6dbd Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Wed, 31 Jan 2018 12:57:15 +0100 Subject: [PATCH 09/63] Show records management operations in the statusbar --- qml/components/ProgressBar.qml | 9 ++ qml/pages/PageRecordsManagementSettings.qml | 2 +- src/records_management.cpp | 108 +++++++++++------- src/records_management.h | 23 ++++ src/worker/emitter.h | 9 ++ ...ask_records_management_stored_messages.cpp | 10 +- .../task_records_management_stored_messages.h | 11 +- 7 files changed, 127 insertions(+), 45 deletions(-) diff --git a/qml/components/ProgressBar.qml b/qml/components/ProgressBar.qml index e57b1840..955b8772 100644 --- a/qml/components/ProgressBar.qml +++ b/qml/components/ProgressBar.qml @@ -75,6 +75,15 @@ Rectangle { progressBar.visible = busy } } + Connections { + target: recordsManagement + onStatusBarTextChanged: { + root.visible = true + statusBarText.text = txt + timer.running = !busy + progressBar.visible = busy + } + } } ProgressBar { id: progressBar diff --git a/qml/pages/PageRecordsManagementSettings.qml b/qml/pages/PageRecordsManagementSettings.qml index 2f69c142..a700f05b 100644 --- a/qml/pages/PageRecordsManagementSettings.qml +++ b/qml/pages/PageRecordsManagementSettings.qml @@ -47,7 +47,7 @@ Item { infoButton.text = (updateRmDataButton.visible) ? qsTr("Update service info") : qsTr("Get service info") } - /* Enable data update and anothers labels if service name and token are filled */ + /* Clear all data and info */ function clearAll() { urlTextField.clear() tokenTextField.clear() diff --git a/src/records_management.cpp b/src/records_management.cpp index 64c72d3b..42aadc1f 100644 --- a/src/records_management.cpp +++ b/src/records_management.cpp @@ -31,6 +31,7 @@ #include "src/models/accountmodel.h" #include "src/records_management.h" #include "src/settings.h" +#include "src/worker/emitter.h" #include "src/worker/pool.h" #include "src/worker/task_records_management_stored_messages.h" @@ -95,6 +96,10 @@ RecordsManagement::RecordsManagement(QObject *parent) m_logoSvg() { globWorkPool.start(); + + connect(&globMsgProcEmitter, + SIGNAL(rmSyncFinishedSignal(QString, int, int)), + this, SLOT(rmSyncFinished(QString, int, int))); } RecordsManagement::~RecordsManagement(void) @@ -107,6 +112,10 @@ void RecordsManagement::callServiceInfo(const QString &urlStr, const QString &tokenStr) { QByteArray response; + QString errTitle = tr("Communication error"); + QString errDesctiption = tr("Received invalid response."); + + emit statusBarTextChanged(tr("Get service info"), true, true); m_rmc.setConnection(urlStr.trimmed(), tokenStr.trimmed()); @@ -117,31 +126,32 @@ void RecordsManagement::callServiceInfo(const QString &urlStr, ServiceInfoResp siRes( ServiceInfoResp::fromJson(response, &ok)); if (!ok || !siRes.isValid()) { - QMessageBox::critical(Q_NULLPTR, - tr("Communication Error"), - tr("Received invalid response.")); - return; + goto fail; } - m_logoSvg = siRes.logoSvg(); loadRecordsManagementPixmap(m_logoSvg); emit serviceInfo(siRes.name(), siRes.tokenName()); - } else { - QMessageBox::critical(Q_NULLPTR, - tr("Communication Error"), - tr("Received empty response.")); - return; + errDesctiption = tr("Received empty response."); + goto fail; } - } else { - return; } + + emit statusBarTextChanged(tr("Done"), false, true); + return; +fail: + emit statusBarTextChanged(errTitle, false, true); + QMessageBox::critical(Q_NULLPTR, errTitle, errDesctiption); } void RecordsManagement::callUploadHierarchy(const QString &urlStr, const QString &tokenStr) { QByteArray response; + QString errTitle = tr("Communication error"); + QString errDesctiption = tr("Received invalid response."); + + emit statusBarTextChanged(tr("Upload hierarchy"), true, true); /* Clear model. */ m_uploadModel.setHierarchy(UploadHierarchyResp()); @@ -155,55 +165,60 @@ void RecordsManagement::callUploadHierarchy(const QString &urlStr, UploadHierarchyResp uhRes( UploadHierarchyResp::fromJson(response, &ok)); if (!ok || !uhRes.isValid()) { - QMessageBox::critical(Q_NULLPTR, - tr("Communication Error"), - tr("Received invalid response.")); - return; + goto fail; } - m_uploadModel.setHierarchy(uhRes); - } else { - QMessageBox::critical(Q_NULLPTR, tr("Communication Error"), - tr("Received empty response.")); - return; + errDesctiption = tr("Received empty response."); + goto fail; } - } else { - return; } + + emit statusBarTextChanged(tr("Done"), false, true); + return; +fail: + emit statusBarTextChanged(errTitle, false, true); + QMessageBox::critical(Q_NULLPTR, errTitle, errDesctiption); } void RecordsManagement::getStoredMsgInfoFromRecordsManagement( const QString &urlStr, const QString &tokenStr) { + emit statusBarTextChanged(tr("Sync service"), true, true); TaskRecordsManagementStoredMessages *task = new (::std::nothrow) TaskRecordsManagementStoredMessages( urlStr, tokenStr, TaskRecordsManagementStoredMessages::RM_UPDATE_STORED, - Q_NULLPTR); + Q_NULLPTR, QString(), 0, 0); if (Q_NULLPTR == task) { qCritical("%s", "Cannot create stored_files update task."); + emit statusBarTextChanged(tr("Sync failed"), false, true); return; } task->setAutoDelete(true); /* Run in background. */ globWorkPool.assignHi(task); + int accTotal = AccountListModel::globAccounts.keys().count(); + int accNumber = 0; + foreach (const QString &userName, AccountListModel::globAccounts.keys()) { + accNumber++; + MessageDb *msgDb = globMessageDbsPtr->accessMessageDb( globSet.dbsLocation, userName, AccountListModel::globAccounts[userName].storeToDisk()); if (msgDb == Q_NULLPTR) { qWarning("%s", "Cannot open message database."); - return; + continue; } TaskRecordsManagementStoredMessages *task = new (::std::nothrow) TaskRecordsManagementStoredMessages( urlStr, tokenStr, TaskRecordsManagementStoredMessages::RM_DOWNLOAD_ALL, - msgDb); + msgDb, userName, accNumber, accTotal); if (Q_NULLPTR == task) { qWarning("Cannot create stored_files task for '%s'.", userName.toUtf8().constData()); @@ -214,8 +229,6 @@ void RecordsManagement::getStoredMsgInfoFromRecordsManagement( globWorkPool.assignHi(task); } - - /* TODO - update message model */ } void RecordsManagement::loadStoredServiceInfo(void) @@ -253,9 +266,12 @@ bool RecordsManagement::uploadMessage(const QString &urlStr, } QByteArray response; + QString errTitle = tr("Communication error"); + QString errDesctiption = tr("Received invalid response."); - m_rmc.setConnection(urlStr.trimmed(), tokenStr.trimmed()); + emit statusBarTextChanged(tr("Upload message"), true, true); + m_rmc.setConnection(urlStr.trimmed(), tokenStr.trimmed()); if (m_rmc.communicate(RecordsManagementConnection::SRVC_UPLOAD_FILE, ufReq.toJson(), response)) { if (!response.isEmpty()) { @@ -263,22 +279,22 @@ bool RecordsManagement::uploadMessage(const QString &urlStr, UploadFileResp ufRes( UploadFileResp::fromJson(response, &ok)); if (!ok || !ufRes.isValid()) { - QMessageBox::critical(Q_NULLPTR, - tr("Communication Error"), - tr("Received invalid response.")); - qWarning("Received invalid response '%s'.", - QString(response).toUtf8().constData()); - return false; + goto fail; } + emit statusBarTextChanged(tr("Done"), false, true); return processUploadFileResponse(ufRes, dmId); } else { - QMessageBox::critical(Q_NULLPTR, tr("Communication Error"), - tr("Received empty response.")); - return false; + errDesctiption = tr("Received empty response."); + goto fail; } - } else { - return false; } + + emit statusBarTextChanged(tr("Done"), false, true); + return false; +fail: + emit statusBarTextChanged(errTitle, false, true); + QMessageBox::critical(Q_NULLPTR, errTitle, errDesctiption); + return false; } bool RecordsManagement::updateServiceInfo(const QString &urlStr, @@ -308,6 +324,18 @@ bool RecordsManagement::updateServiceInfo(const QString &urlStr, return true; } +void RecordsManagement::rmSyncFinished(const QString &userName, int accNumber, + int accTotal) +{ + if (accNumber < accTotal) { + emit statusBarTextChanged( + tr("Sync account '%1' (%2/%3)").arg(userName).arg(accNumber).arg(accTotal), + true, true); + } else { + emit statusBarTextChanged(tr("Sync done"), false, true); + } +} + void RecordsManagement::loadRecordsManagementPixmap(const QByteArray &logoSvg) { if (Q_NULLPTR == globRecordsManagementDbPtr) { diff --git a/src/records_management.h b/src/records_management.h index f02b7b1e..ae89082e 100644 --- a/src/records_management.h +++ b/src/records_management.h @@ -120,6 +120,29 @@ signals: */ void serviceInfo(QString srName, QString srToken); + /*! + * @brief Set new statusbar text and active busy indicator to QML. + * + * @param[in] txt Text message for statusbar. + * @param[in] busy True means the statusbar busy indicator is + * active and shown, false = disabled and hidden. + * @param[in] isVisible True means the statusbar is visible, + * false = hidden. + */ + void statusBarTextChanged(QString txt, bool busy, bool isVisible); + +private slots: + + /*! + * @brief Do some actions when records management sync has been finished. + * + * @param[in] userName Account username (may be NULL). + * @param[in] accNumber Account number in process. + * @param[in] accTotal Overall number of account for sync. + */ + void rmSyncFinished(const QString &userName, int accNumber, + int accTotal); + private: /*! diff --git a/src/worker/emitter.h b/src/worker/emitter.h index b2c7ebca..040767c9 100644 --- a/src/worker/emitter.h +++ b/src/worker/emitter.h @@ -104,6 +104,15 @@ signals: const QString &transactId, const QString &dbIDRecipient, const QString &dmRecipient, qint64 msgId, bool success, const QString &errTxt); + + /*! + * @brief Do some actions when records management sync has been finished. + * + * @param[in] userName Account username (may be NULL). + * @param[in] accNumber Account number in process. + * @param[in] accTotal Overall number of account for sync. + */ + void rmSyncFinishedSignal(QString userName, int accNumber, int accTotal); }; /*! diff --git a/src/worker/task_records_management_stored_messages.cpp b/src/worker/task_records_management_stored_messages.cpp index ec2ce8ea..f833e5c0 100644 --- a/src/worker/task_records_management_stored_messages.cpp +++ b/src/worker/task_records_management_stored_messages.cpp @@ -32,11 +32,13 @@ #include "src/datovka_shared/records_management/json/entry_error.h" #include "src/datovka_shared/records_management/json/stored_files.h" #include "src/log/log.h" +#include "src/worker/emitter.h" #include "src/worker/task_records_management_stored_messages.h" TaskRecordsManagementStoredMessages::TaskRecordsManagementStoredMessages( const QString &urlStr, const QString &tokenStr, enum Operation operation, - MessageDb *msgDb, const QList &exludedDmIds) + MessageDb *msgDb, const QString &userName, int accNumber, int accTotal, + const QList &exludedDmIds) : m_result(DS_DSM_ERR), m_id(QString::number(QDateTime::currentMSecsSinceEpoch()) + QStringLiteral(":") + QString::number((quint64)this)), @@ -44,6 +46,9 @@ TaskRecordsManagementStoredMessages::TaskRecordsManagementStoredMessages( m_token(tokenStr), m_operation(operation), m_msgDb(msgDb), + m_userName(userName), + m_accNumber(accNumber), + m_accTotal(accTotal), m_exludedDmIds(exludedDmIds) { Q_ASSERT(!m_url.isEmpty() && !m_token.isEmpty()); @@ -71,6 +76,9 @@ void TaskRecordsManagementStoredMessages::run(void) m_result = downloadStoredMessages(m_url, m_token, m_operation, m_msgDb, m_exludedDmIds); + emit globMsgProcEmitter.rmSyncFinishedSignal(m_userName, m_accNumber, + m_accTotal); + /* ### Worker task end. ### */ logDebugLv0NL("Download stored messages from records management service task '%s' finished in thread '%p'.", diff --git a/src/worker/task_records_management_stored_messages.h b/src/worker/task_records_management_stored_messages.h index 67d80731..5af3264f 100644 --- a/src/worker/task_records_management_stored_messages.h +++ b/src/worker/task_records_management_stored_messages.h @@ -59,11 +59,15 @@ public: * @param[in] tokenStr Records management access token. * @param[in] operation Actual action to be performed. * @param[in] msgDb Database set to be used to obtain message identifiers. + * @param[in] userName Account username. + * @param[in] accNumber Account number in process. + * @param[in] accTotal Overall number of account for sync. * @patam[in] exludedDmIds Message identifiers that should not be queried. */ explicit TaskRecordsManagementStoredMessages( const QString &urlStr, const QString &tokenStr, enum Operation operation, MessageDb *msgDb, + const QString &userName, int accNumber, int accTotal, const QList &exludedDmIds = QList()); /*! @@ -99,13 +103,14 @@ private: MessageDb *msgDb, const QList &exludedDmIds); const QString m_id; /*!< Task identifier. */ - const QString m_url; /*!< String containing records management URL. */ const QString m_token; /*!< Records management access token. */ - const enum Operation m_operation; /*!< Operation to be performed. */ - MessageDb *m_msgDb; /*!< Pointer to database container. */ + const QString m_userName; /*!< Account username. */ + int m_accNumber; /*!< Account number in process. */ + int m_accTotal; /*!< Overall number of account for sync. */ + const QList m_exludedDmIds; /*!< * List of messages that should * not be queried. -- GitLab From 42495254b8eeb90c4b9bcbbed79c42d76dfd22f0 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Wed, 31 Jan 2018 14:37:31 +0100 Subject: [PATCH 10/63] Added records management logo into message model --- qml/components/MessageList.qml | 19 ++++++++++++++----- src/models/messagemodel.cpp | 32 ++++++++++++++++++++++++++++++-- src/models/messagemodel.h | 14 ++++++++------ src/sqlite/message_db.cpp | 13 +++++++++++++ 4 files changed, 65 insertions(+), 13 deletions(-) diff --git a/qml/components/MessageList.qml b/qml/components/MessageList.qml index a3a67894..2de69fe1 100644 --- a/qml/components/MessageList.qml +++ b/qml/components/MessageList.qml @@ -97,12 +97,21 @@ ScrollableListView { color: datovkaPalette.windowText font.bold: (rMsgType == MessageType.TYPE_RECEIVED && !rReadLocally) } - Image { + Item { id: r3c1 - anchors.centerIn: parent - sourceSize.width: 1 - fillMode: Image.PreserveAspectFit - source: "qrc:/ui/datovka-msg-blank.png" + width: defaultMargin * 3 + Layout.fillHeight: true + Image { + id: r3c1p + anchors.centerIn: parent + sourceSize.height: parent.height + source: (rRecordsManagement) ? "qrc:/ui/briefcase.svg" : "qrc:/ui/datovka-msg-blank.png" + } + ColorOverlay { + anchors.fill: r3c1p + source: r3c1p + color: datovkaPalette.windowText + } } Text { id: r3c2 diff --git a/src/models/messagemodel.cpp b/src/models/messagemodel.cpp index 23277561..07fad2e7 100644 --- a/src/models/messagemodel.cpp +++ b/src/models/messagemodel.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2017 CZ.NIC + * 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 @@ -24,6 +24,7 @@ #include /* qmlRegisterType */ #include +#include "src/datovka_shared/io/records_management_db.h" #include "src/models/messagemodel.h" #include "src/sqlite/dbs.h" @@ -37,6 +38,7 @@ MessageModelEntry::MessageModelEntry(const MessageModelEntry &mme) m_acceptanceTime(mme.m_acceptanceTime), m_readLocally(mme.m_readLocally), m_attachmentsDownloaded(mme.m_attachmentsDownloaded), + m_recordsManagement(mme.m_recordsManagement), m_msgType(mme.m_msgType) { } @@ -44,7 +46,8 @@ MessageModelEntry::MessageModelEntry(const MessageModelEntry &mme) MessageModelEntry::MessageModelEntry(qint64 dmId, const QString &userName, const QString &from, const QString &to, const QString &annotation, const QString &dTime, const QString &aTime, - bool readLocally, bool attachmentsDownloaded, int msgType) + bool readLocally, bool attachmentsDownloaded, bool recordsManagement, + int msgType) : m_dmId(dmId), m_userName(userName), m_from(from), @@ -54,6 +57,7 @@ MessageModelEntry::MessageModelEntry(qint64 dmId, const QString &userName, m_acceptanceTime(aTime), m_readLocally(readLocally), m_attachmentsDownloaded(attachmentsDownloaded), + m_recordsManagement(recordsManagement), m_msgType(msgType) { } @@ -143,6 +147,16 @@ void MessageModelEntry::setAttachmentsDownloaded(bool attachmentsDownloaded) m_attachmentsDownloaded = attachmentsDownloaded; } +bool MessageModelEntry::recordsManagement(void) const +{ + return m_recordsManagement; +} + +void MessageModelEntry::setSecordsManagement(bool recordsManagement) +{ + m_recordsManagement = recordsManagement; +} + int MessageModelEntry::msgType(void) const { return m_msgType; @@ -190,6 +204,7 @@ QHash MessageListModel::roleNames(void) const roles[ROLE_ACCEPTANCE_TIME] = "rAcceptTime"; roles[ROLE_READ_LOCALLY] = "rReadLocally"; roles[ROLE_ATTACHMENTS_DOWNLOADED] = "rAttachmentsDownloaded"; + roles[ROLE_RECORDS_MANAGEMENT] = "rRecordsManagement"; roles[ROLE_MSG_TYPE] = "rMsgType"; } return roles; @@ -231,6 +246,9 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const case ROLE_ATTACHMENTS_DOWNLOADED: return message.attachmentsDownloaded(); break; + case ROLE_RECORDS_MANAGEMENT: + return message.recordsManagement(); + break; case ROLE_MSG_TYPE: return message.msgType(); break; @@ -279,8 +297,17 @@ int MessageListModel::setQuery(const QString &userName, QSqlQuery &query, m_messages.clear(); } + QStringList rmPathList; + query.first(); while (query.isActive() && query.isValid()) { + + rmPathList.clear(); + if (Q_NULLPTR != globRecordsManagementDbPtr) { + rmPathList = globRecordsManagementDbPtr-> + storedMsgLocations(query.value(0).toLongLong()); + } + m_messages.append(MessageModelEntry( query.value(0).toLongLong(), userName, @@ -293,6 +320,7 @@ int MessageListModel::setQuery(const QString &userName, QSqlQuery &query, DATETIME_QML_FORMAT), query.value(6).toBool(), query.value(7).toBool(), + !rmPathList.isEmpty(), query.value(8).toInt() )); msgCnt++; diff --git a/src/models/messagemodel.h b/src/models/messagemodel.h index a111e9fd..79fb15cb 100644 --- a/src/models/messagemodel.h +++ b/src/models/messagemodel.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2017 CZ.NIC + * 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 @@ -21,8 +21,7 @@ * the two. */ -#ifndef MESSAGEMODEL_H -#define MESSAGEMODEL_H +#pragma once #include #include @@ -34,7 +33,7 @@ public: const QString &from, const QString &to, const QString &annotation, const QString &dTime, const QString &aTime, bool readLocally, bool attachmentsDownloaded, - int msgType); + bool recordsManagement, int msgType); qint64 dmId(void) const; QString userName(void) const; @@ -53,6 +52,8 @@ public: void setReadLocally(bool readLocally); bool attachmentsDownloaded(void) const; void setAttachmentsDownloaded(bool attachmentsDownloaded); + bool recordsManagement(void) const; + void setSecordsManagement(bool recordsManagement); int msgType(void) const; void setMsgType(int msgType); @@ -66,6 +67,7 @@ private: QString m_acceptanceTime; /*!< Acceptance time. */ bool m_readLocally; /*!< Message has been viewed. */ bool m_attachmentsDownloaded; /*!< Attachments downloaded. */ + bool m_recordsManagement; /*!< Message is uploaded in records management. */ int m_msgType; /*!< Message orientation /sent/received/. */ }; @@ -86,6 +88,7 @@ public: ROLE_ACCEPTANCE_TIME, ROLE_READ_LOCALLY, ROLE_ATTACHMENTS_DOWNLOADED, + ROLE_RECORDS_MANAGEMENT, ROLE_MSG_TYPE }; Q_ENUM(Roles) @@ -235,11 +238,10 @@ public: MessageListModel *fromVariant(const QVariant &modelVariant); private: + QList m_messages; /*!< List of messages stored. */ }; /* QML passes its arguments via QVariant. */ Q_DECLARE_METATYPE(MessageListModel) Q_DECLARE_METATYPE(MessageListModel::Roles) - -#endif // MESSAGEMODEL_H diff --git a/src/sqlite/message_db.cpp b/src/sqlite/message_db.cpp index 6a5b7d2a..e29ca4c0 100644 --- a/src/sqlite/message_db.cpp +++ b/src/sqlite/message_db.cpp @@ -33,6 +33,7 @@ #include "src/auxiliaries/email_helper.h" #include "src/common.h" +#include "src/datovka_shared/io/records_management_db.h" #include "src/io/filesystem.h" #include "src/isds/isds_conversion.h" #include "src/models/messagemodel.h" @@ -372,6 +373,7 @@ bool MessageDb::getMessageDataForEmail(qint64 dmId, QString &body, QString MessageDb::getMessageDetailDataFromDb(qint64 dmId) const { QString html; + QStringList rmPathList; QSqlQuery query(m_db); QString queryStr; @@ -511,6 +513,17 @@ QString MessageDb::getMessageDetailDataFromDb(qint64 dmId) const goto fail; } + if (Q_NULLPTR != globRecordsManagementDbPtr) { + rmPathList = globRecordsManagementDbPtr->storedMsgLocations(dmId); + } + + if (!rmPathList.isEmpty()) { + html += "

" + QObject::tr("Records Management location") + "

"; + foreach (const QString &location, rmPathList) { + html += strongInfoLine(QObject::tr("Folder"), location); + } + } + html += divEnd; return html; -- GitLab From af5304851198fd5e946e5848e086615b8e1d69b2 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Thu, 1 Feb 2018 10:03:41 +0100 Subject: [PATCH 11/63] Added records management message upload page --- qml/main.qml | 1 + qml/pages/PageMenuMessageDetail.qml | 28 +++- qml/pages/PageRecordsManagementSettings.qml | 2 +- qml/pages/PageUploadMessageToRm.qml | 125 ++++++++++++++++++ res/qml.qrc | 1 + src/main.cpp | 1 + src/records_management.cpp | 135 +++++++++++++------- src/records_management.h | 45 ++++--- 8 files changed, 274 insertions(+), 64 deletions(-) create mode 100644 qml/pages/PageUploadMessageToRm.qml diff --git a/qml/main.qml b/qml/main.qml index 11f42c2a..5cc210da 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -84,6 +84,7 @@ ApplicationWindow { property Component pageSettingsPin: PageSettingsPin {} property Component pageSettingsStorage: PageSettingsStorage {} property Component pageSettingsSync: PageSettingsSync {} + property Component pageUploadMessageToRm: PageUploadMessageToRm {} // header background color property string mainHeaderBgColor: "#00539b" diff --git a/qml/pages/PageMenuMessageDetail.qml b/qml/pages/PageMenuMessageDetail.qml index cff0bac6..6e730dad 100644 --- a/qml/pages/PageMenuMessageDetail.qml +++ b/qml/pages/PageMenuMessageDetail.qml @@ -43,13 +43,20 @@ Component { property var attachmentModel: null /* The QVariant holds actually a pointer to the model. */ Component.onCompleted: { + var index if (msgType == MessageType.TYPE_SENT) { // Hide some menu items for sent messages // Note: remove argument is ListModel item index - var index = messageDetailMenuList.get_model_index(messageDetailMenuListModel, "funcName", "reply"); + index = messageDetailMenuList.get_model_index(messageDetailMenuListModel, "funcName", "reply"); + if (index >= 0) { + messageDetailMenuListModel.setProperty(index, "showEntry", false) + } + } + if (!recordsManagement.isValidRecordsManagement()) { + // Note: remove argument is ListModel item index + index = messageDetailMenuList.get_model_index(messageDetailMenuListModel, "funcName", "uploadRM"); if (index >= 0) { messageDetailMenuListModel.setProperty(index, "showEntry", false) - //messageDetailMenuListModel.remove(index) } } } @@ -101,6 +108,16 @@ Component { "action": "template" }, StackView.Immediate) }, + "uploadRM": function calluploadRM() { + pageView.replace(pageUploadMessageToRm, { + "pageView": pageView, + "statusBar": statusBar, + "acntName" : acntName, + "userName": userName, + "msgId": msgId, + "msgType": msgType + }, StackView.Immediate) + }, "sendEmail": function callSendEmail() { files.sendAttachmentsWithEmail(userName, msgId) pageView.pop(StackView.Immediate) @@ -149,6 +166,13 @@ Component { name: qsTr("Use as template") funcName: "useTemplate" } + ListElement { + image: "qrc:/ui/briefcase.svg" + showEntry: true + showNext: true + name: qsTr("Upload to Records Management") + funcName: "uploadRM" + } ListElement { image: "qrc:/ui/attach-to-mail.svg" showEntry: true diff --git a/qml/pages/PageRecordsManagementSettings.qml b/qml/pages/PageRecordsManagementSettings.qml index a700f05b..95ec2e38 100644 --- a/qml/pages/PageRecordsManagementSettings.qml +++ b/qml/pages/PageRecordsManagementSettings.qml @@ -244,7 +244,7 @@ Item { anchors.horizontalCenter: parent.horizontalCenter text: qsTr("Update list of uploaded files") onClicked: { - recordsManagement.getStoredMsgInfoFromRecordsManagement(urlTextField.text, tokenTextField.text) + recordsManagement.getStoredMsgInfoFromRecordsManagement() } } } // // Column diff --git a/qml/pages/PageUploadMessageToRm.qml b/qml/pages/PageUploadMessageToRm.qml new file mode 100644 index 00000000..83805d3c --- /dev/null +++ b/qml/pages/PageUploadMessageToRm.qml @@ -0,0 +1,125 @@ +/* + * 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. + */ + +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Controls 2.1 +import QtGraphicalEffects 1.0 +import cz.nic.mobileDatovka 1.0 +import cz.nic.mobileDatovka.models 1.0 + +Component { + id: uploadRm + Item { + id: mainLayout + + /* These properties must be set by caller. */ + property var pageView + property var statusBar + property string acntName + property string userName + property int msgType + property string msgId + /* Holsd selected folder ids */ + property var uploadIds: [] + + Component.onCompleted: { + } + + Component.onDestruction: { + statusBar.visible = false + } + + ListModel { + /* TODO */ + id: uploadHierarchyModel + Component.onCompleted: { + } + } + + PageHeader { + id: headerBar + title: qsTr("Upload message %1").arg(msgId) + onBackClicked: { + pageView.pop(StackView.Immediate) + } + } + + Flickable { + id: flickable + z: 0 + anchors.top: headerBar.bottom + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + contentHeight: flickContent.implicitHeight + Pane { + id: flickContent + anchors.fill: parent + Column { + anchors.right: parent.right + anchors.left: parent.left + spacing: formItemVerticalSpacing + AccessibleText { + color: datovkaPalette.mid + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + width: parent.width + text: qsTr("Here you can upload message into chosen folder in the records management. Update folder hierarchy and select target folder.") + } + AccessibleButton { + id: hierarchyButton + anchors.horizontalCenter: parent.horizontalCenter + height: inputItemHeight + font.pointSize: defaultTextFont.font.pointSize + text: qsTr("Update upload hierarchy") + onClicked: { + recordsManagement.callUploadHierarchy() + } + } + TreeView { + /* TODO */ + width: parent.width + TableViewColumn { + title: "Records management hierarchy" + role: "folderName" + width: parent.width + } + model: uploadHierarchyModel + } + AccessibleButton { + id: uplaodButton + anchors.horizontalCenter: parent.horizontalCenter + height: inputItemHeight + font.pointSize: defaultTextFont.font.pointSize + text: qsTr("Upload message") + onClicked: { + recordsManagement.uploadMessage(userName, msgId, msgType, uploadIds) + } + } + } // Column layout + } // Pane + ScrollIndicator.vertical: ScrollIndicator {} + } // Flickable + } +} diff --git a/res/qml.qrc b/res/qml.qrc index 6a96959f..3ccf1c54 100644 --- a/res/qml.qrc +++ b/res/qml.qrc @@ -148,6 +148,7 @@ ../qml/pages/PageSettingsPin.qml ../qml/pages/PageSettingsStorage.qml ../qml/pages/PageSettingsSync.qml + ../qml/pages/PageUploadMessageToRm.qml ../qml/main.qml diff --git a/src/main.cpp b/src/main.cpp index 37759080..f7802aee 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -118,6 +118,7 @@ const struct QmlTypeEntry qmlPages[] = { { "PageSettingsPin", 1, 0 }, { "PageSettingsStorage", 1, 0 }, { "PageSettingsSync", 1, 0 }, + { "PageUploadMessageToRm", 1, 0 }, { NULL, 0, 0 } }; diff --git a/src/records_management.cpp b/src/records_management.cpp index 42aadc1f..23e6b439 100644 --- a/src/records_management.cpp +++ b/src/records_management.cpp @@ -34,6 +34,7 @@ #include "src/worker/emitter.h" #include "src/worker/pool.h" #include "src/worker/task_records_management_stored_messages.h" +#include "src/sqlite/zfo_db.h" /*! * @brief Process upload file service response. @@ -144,8 +145,7 @@ fail: QMessageBox::critical(Q_NULLPTR, errTitle, errDesctiption); } -void RecordsManagement::callUploadHierarchy(const QString &urlStr, - const QString &tokenStr) +void RecordsManagement::callUploadHierarchy(void) { QByteArray response; QString errTitle = tr("Communication error"); @@ -156,7 +156,8 @@ void RecordsManagement::callUploadHierarchy(const QString &urlStr, /* Clear model. */ m_uploadModel.setHierarchy(UploadHierarchyResp()); - m_rmc.setConnection(urlStr.trimmed(), tokenStr.trimmed()); + m_rmc.setConnection(globRecordsManagementSet.url(), + globRecordsManagementSet.token()); if (m_rmc.communicate(RecordsManagementConnection::SRVC_UPLOAD_HIERARCHY, QByteArray(), response)) { @@ -181,14 +182,14 @@ fail: QMessageBox::critical(Q_NULLPTR, errTitle, errDesctiption); } -void RecordsManagement::getStoredMsgInfoFromRecordsManagement( - const QString &urlStr, const QString &tokenStr) +void RecordsManagement::getStoredMsgInfoFromRecordsManagement(void) { emit statusBarTextChanged(tr("Sync service"), true, true); TaskRecordsManagementStoredMessages *task = new (::std::nothrow) TaskRecordsManagementStoredMessages( - urlStr, tokenStr, + globRecordsManagementSet.url(), + globRecordsManagementSet.token(), TaskRecordsManagementStoredMessages::RM_UPDATE_STORED, Q_NULLPTR, QString(), 0, 0); if (Q_NULLPTR == task) { @@ -216,7 +217,8 @@ void RecordsManagement::getStoredMsgInfoFromRecordsManagement( TaskRecordsManagementStoredMessages *task = new (::std::nothrow) TaskRecordsManagementStoredMessages( - urlStr, tokenStr, + globRecordsManagementSet.url(), + globRecordsManagementSet.token(), TaskRecordsManagementStoredMessages::RM_DOWNLOAD_ALL, msgDb, userName, accNumber, accTotal); if (Q_NULLPTR == task) { @@ -231,6 +233,14 @@ void RecordsManagement::getStoredMsgInfoFromRecordsManagement( } } +bool RecordsManagement::isValidRecordsManagement(void) +{ + RecordsManagementDb::ServiceInfoEntry entry( + globRecordsManagementDbPtr->serviceInfo()); + return (entry.isValid() && !globRecordsManagementSet.url().isEmpty() + && !globRecordsManagementSet.token().isEmpty()); +} + void RecordsManagement::loadStoredServiceInfo(void) { if (Q_NULLPTR == globRecordsManagementDbPtr) { @@ -248,53 +258,43 @@ void RecordsManagement::loadStoredServiceInfo(void) emit serviceInfo(entry.name, entry.tokenName); } -bool RecordsManagement::uploadMessage(const QString &urlStr, - const QString &tokenStr, qint64 dmId, const QString &msgFileName, - const QByteArray &msgData) +bool RecordsManagement::uploadMessage(const QString &userName, + const QString &dmId, enum Messages::MessageType messageType, + const QStringList &uploadIds) { - if (msgFileName.isEmpty() || msgData.isEmpty()) { - Q_ASSERT(0); + bool ok = false; + qint64 msgId = dmId.toLongLong(&ok); + if (!ok || (msgId < 0)) { return false; } - QStringList uploadIds; + QByteArray msgData = + QByteArray::fromBase64(globZfoDbPtr->getZfoContentFromDb(msgId, + AccountListModel::globAccounts[userName].isTestAccount())); - UploadFileReq ufReq(uploadIds, msgFileName, msgData); - if (!ufReq.isValid()) { - Q_ASSERT(0); + if (msgData.isEmpty()) { return false; } - QByteArray response; - QString errTitle = tr("Communication error"); - QString errDesctiption = tr("Received invalid response."); + QString msgType; + switch (messageType) { + case Messages::TYPE_RECEIVED: + msgType = QLatin1String("DDZ"); + break; + case Messages::TYPE_SENT: + msgType = QLatin1String("ODZ"); + break; + default: + msgType = QLatin1String("DZ"); + break; + } - emit statusBarTextChanged(tr("Upload message"), true, true); + QString msgFileName = QString("%1_%2.zfo").arg(msgType).arg(dmId); - m_rmc.setConnection(urlStr.trimmed(), tokenStr.trimmed()); - if (m_rmc.communicate(RecordsManagementConnection::SRVC_UPLOAD_FILE, - ufReq.toJson(), response)) { - if (!response.isEmpty()) { - bool ok = false; - UploadFileResp ufRes( - UploadFileResp::fromJson(response, &ok)); - if (!ok || !ufRes.isValid()) { - goto fail; - } - emit statusBarTextChanged(tr("Done"), false, true); - return processUploadFileResponse(ufRes, dmId); - } else { - errDesctiption = tr("Received empty response."); - goto fail; - } - } + /* TODO - removed this return */ + return true; - emit statusBarTextChanged(tr("Done"), false, true); - return false; -fail: - emit statusBarTextChanged(errTitle, false, true); - QMessageBox::critical(Q_NULLPTR, errTitle, errDesctiption); - return false; + return uploadFile(msgId, msgFileName, msgData, uploadIds); } bool RecordsManagement::updateServiceInfo(const QString &urlStr, @@ -357,6 +357,53 @@ void RecordsManagement::loadRecordsManagementPixmap(const QByteArray &logoSvg) QPixmap pixmap(Graphics::pixmapFromSvg(logo, LOGO_EDGE)); if (!pixmap.isNull()) { /* TODO - set Pixmap into QML */ - //m_ui->pixmapLabel->setPixmap(pixmap); } } + +bool RecordsManagement::uploadFile(qint64 dmId, const QString &msgFileName, + const QByteArray &msgData, const QStringList &uploadIds) +{ + if (msgFileName.isEmpty() || msgData.isEmpty()) { + Q_ASSERT(0); + return false; + } + + UploadFileReq ufReq(uploadIds, msgFileName, msgData); + if (!ufReq.isValid()) { + Q_ASSERT(0); + return false; + } + + QByteArray response; + QString errTitle = tr("Communication error"); + QString errDesctiption = tr("Received invalid response."); + + emit statusBarTextChanged(tr("Upload message"), true, true); + + m_rmc.setConnection(globRecordsManagementSet.url(), + globRecordsManagementSet.token()); + + if (m_rmc.communicate(RecordsManagementConnection::SRVC_UPLOAD_FILE, + ufReq.toJson(), response)) { + if (!response.isEmpty()) { + bool ok = false; + UploadFileResp ufRes( + UploadFileResp::fromJson(response, &ok)); + if (!ok || !ufRes.isValid()) { + goto fail; + } + emit statusBarTextChanged(tr("Done"), false, true); + return processUploadFileResponse(ufRes, dmId); + } else { + errDesctiption = tr("Received empty response."); + goto fail; + } + } + + emit statusBarTextChanged(tr("Done"), false, true); + return false; +fail: + emit statusBarTextChanged(errTitle, false, true); + QMessageBox::critical(Q_NULLPTR, errTitle, errDesctiption); + return false; +} diff --git a/src/records_management.h b/src/records_management.h index ae89082e..b4c0cda9 100644 --- a/src/records_management.h +++ b/src/records_management.h @@ -58,23 +58,23 @@ public: /*! * @brief Download upload hierarchy and set model. - * - * @param[in] urlStr Records management url string. - * @param[in] tokenStr Records management token string. */ Q_INVOKABLE - void callUploadHierarchy(const QString &urlStr, const QString &tokenStr); + void callUploadHierarchy(void); /*! - * @brief Obtain information about stored messages from records - * management. + * @brief Obtain information about stored messages from records managnt. + */ + Q_INVOKABLE + void getStoredMsgInfoFromRecordsManagement(void); + + /*! + * @brief Test if records management is set, active and valid. * - * @param[in] urlStr Records management url string. - * @param[in] tokenStr Records management token string. + * @return True if records management is set, active and valid. */ Q_INVOKABLE - void getStoredMsgInfoFromRecordsManagement(const QString &urlStr, - const QString &tokenStr); + bool isValidRecordsManagement(void); /*! * @brief Loads service information from storage. @@ -85,17 +85,15 @@ public: /*! * @brief Upload message into records management service. * - * @param[in] urlStr Records management url string. - * @param[in] tokenStr Records management token string. + * @param[in] userName Account user name identifier. * @param[in] dmId Message identifier. - * @param[in] msgFileName Message file name. - * @param[in] msgData Message data. + * @param[in] messageType Message orientation. + * @param[in] uploadIds List of records management location ids. * @return True when data have been updated, false else. */ Q_INVOKABLE - bool uploadMessage(const QString &urlStr, - const QString &tokenStr, qint64 dmId, const QString &msgFileName, - const QByteArray &msgData); + bool uploadMessage(const QString &userName, const QString &dmId, + enum Messages::MessageType messageType, const QStringList &uploadIds); /*! * @brief Update record management settings. @@ -152,6 +150,19 @@ private: */ void loadRecordsManagementPixmap(const QByteArray &logoSvg); + /*! + * @brief Upload file into records management service. + * + * @param[in] dmId Message identifier. + * @param[in] msgFileName Message file name. + * @param[in] msgData Message data. + * @param[in] uploadIds List of records management location ids. + * @return True when data have been updated, false else. + */ + Q_INVOKABLE + bool uploadFile(qint64 dmId, const QString &msgFileName, + const QByteArray &msgData, const QStringList &uploadIds); + UploadHierarchyModel m_uploadModel; /*!< Upload hierarchy model. */ UploadHierarchyProxyModel m_uploadProxyModel; /*!< Used for filtering. */ RecordsManagementConnection m_rmc; /*!< Connection to records management service. */ -- GitLab From 78c815457dad0cb97dd90386b93551cd15502f78 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Thu, 1 Feb 2018 11:06:15 +0100 Subject: [PATCH 12/63] Renamed records management setting page to match already present naming convention. --- qml/main.qml | 2 +- qml/pages/PageMenuDatovkaSettings.qml | 2 +- ...ManagementSettings.qml => PageSettingsRecordsManagement.qml} | 2 +- res/qml.qrc | 2 +- src/main.cpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename qml/pages/{PageRecordsManagementSettings.qml => PageSettingsRecordsManagement.qml} (99%) diff --git a/qml/main.qml b/qml/main.qml index 5cc210da..04e5bbf6 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -77,11 +77,11 @@ ApplicationWindow { property Component pageMessageDetail: PageMessageDetail {} property Component pageMessageList: PageMessageList {} property Component pageMessageSearch: PageMessageSearch {} - property Component pageRecordsManagementSettings: PageRecordsManagementSettings {} property Component pageSendMessage: PageSendMessage {} property Component pageSettingsAccount: PageSettingsAccount {} property Component pageSettingsGeneral: PageSettingsGeneral {} property Component pageSettingsPin: PageSettingsPin {} + property Component pageSettingsRecordsManagement: PageSettingsRecordsManagement {} property Component pageSettingsStorage: PageSettingsStorage {} property Component pageSettingsSync: PageSettingsSync {} property Component pageUploadMessageToRm: PageUploadMessageToRm {} diff --git a/qml/pages/PageMenuDatovkaSettings.qml b/qml/pages/PageMenuDatovkaSettings.qml index 7898fb5b..2bab4795 100644 --- a/qml/pages/PageMenuDatovkaSettings.qml +++ b/qml/pages/PageMenuDatovkaSettings.qml @@ -106,7 +106,7 @@ Component { }, StackView.Immediate) }, "settRecMan": function callSettRecMan() { - pageView.replace(pageRecordsManagementSettings, { + pageView.replace(pageSettingsRecordsManagement, { "pageView": pageView, "statusBar": statusBar }, StackView.Immediate) diff --git a/qml/pages/PageRecordsManagementSettings.qml b/qml/pages/PageSettingsRecordsManagement.qml similarity index 99% rename from qml/pages/PageRecordsManagementSettings.qml rename to qml/pages/PageSettingsRecordsManagement.qml index 95ec2e38..13f2879c 100644 --- a/qml/pages/PageRecordsManagementSettings.qml +++ b/qml/pages/PageSettingsRecordsManagement.qml @@ -27,7 +27,7 @@ import QtQuick.Controls 2.2 import cz.nic.mobileDatovka 1.0 Item { - id: pageRecordsManagementSettings + id: pageSettingsRecordsManagement /* These properties must be set by caller. */ property var pageView diff --git a/res/qml.qrc b/res/qml.qrc index 3ccf1c54..8cd7ee71 100644 --- a/res/qml.qrc +++ b/res/qml.qrc @@ -141,11 +141,11 @@ ../qml/pages/PageMessageDetail.qml ../qml/pages/PageMessageList.qml ../qml/pages/PageMessageSearch.qml - ../qml/pages/PageRecordsManagementSettings.qml ../qml/pages/PageSendMessage.qml ../qml/pages/PageSettingsAccount.qml ../qml/pages/PageSettingsGeneral.qml ../qml/pages/PageSettingsPin.qml + ../qml/pages/PageSettingsRecordsManagement.qml ../qml/pages/PageSettingsStorage.qml ../qml/pages/PageSettingsSync.qml ../qml/pages/PageUploadMessageToRm.qml diff --git a/src/main.cpp b/src/main.cpp index f7802aee..4d769f16 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -111,11 +111,11 @@ const struct QmlTypeEntry qmlPages[] = { { "PageMessageDetail", 1, 0 }, { "PageMessageList", 1, 0 }, { "PageMessageSearch", 1, 0 }, - { "PageRecordsManagementSettings", 1, 0 }, { "PageSendMessage", 1, 0 }, { "PageSettingsAccount", 1, 0 }, { "PageSettingsGeneral", 1, 0 }, { "PageSettingsPin", 1, 0 }, + { "PageSettingsRecordsManagement", 1, 0 }, { "PageSettingsStorage", 1, 0 }, { "PageSettingsSync", 1, 0 }, { "PageUploadMessageToRm", 1, 0 }, -- GitLab From 17a0d4e91293ad45bf950de39bfc1ab5b5ba1f16 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Thu, 1 Feb 2018 11:26:19 +0100 Subject: [PATCH 13/63] Renamed upload to records management page. --- qml/main.qml | 2 +- qml/pages/PageMenuMessageDetail.qml | 4 ++-- ...eUploadMessageToRm.qml => PageRecordsManagementUpload.qml} | 0 res/qml.qrc | 2 +- src/main.cpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename qml/pages/{PageUploadMessageToRm.qml => PageRecordsManagementUpload.qml} (100%) diff --git a/qml/main.qml b/qml/main.qml index 04e5bbf6..92ea3225 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -77,6 +77,7 @@ ApplicationWindow { property Component pageMessageDetail: PageMessageDetail {} property Component pageMessageList: PageMessageList {} property Component pageMessageSearch: PageMessageSearch {} + property Component pageRecordsManagementUpload: PageRecordsManagementUpload {} property Component pageSendMessage: PageSendMessage {} property Component pageSettingsAccount: PageSettingsAccount {} property Component pageSettingsGeneral: PageSettingsGeneral {} @@ -84,7 +85,6 @@ ApplicationWindow { property Component pageSettingsRecordsManagement: PageSettingsRecordsManagement {} property Component pageSettingsStorage: PageSettingsStorage {} property Component pageSettingsSync: PageSettingsSync {} - property Component pageUploadMessageToRm: PageUploadMessageToRm {} // header background color property string mainHeaderBgColor: "#00539b" diff --git a/qml/pages/PageMenuMessageDetail.qml b/qml/pages/PageMenuMessageDetail.qml index 6e730dad..05a2fbaa 100644 --- a/qml/pages/PageMenuMessageDetail.qml +++ b/qml/pages/PageMenuMessageDetail.qml @@ -108,8 +108,8 @@ Component { "action": "template" }, StackView.Immediate) }, - "uploadRM": function calluploadRM() { - pageView.replace(pageUploadMessageToRm, { + "uploadRM": function callUploadRM() { + pageView.replace(pageRecordsManagementUpload, { "pageView": pageView, "statusBar": statusBar, "acntName" : acntName, diff --git a/qml/pages/PageUploadMessageToRm.qml b/qml/pages/PageRecordsManagementUpload.qml similarity index 100% rename from qml/pages/PageUploadMessageToRm.qml rename to qml/pages/PageRecordsManagementUpload.qml diff --git a/res/qml.qrc b/res/qml.qrc index 8cd7ee71..95a145e8 100644 --- a/res/qml.qrc +++ b/res/qml.qrc @@ -141,6 +141,7 @@ ../qml/pages/PageMessageDetail.qml ../qml/pages/PageMessageList.qml ../qml/pages/PageMessageSearch.qml + ../qml/pages/PageRecordsManagementUpload.qml ../qml/pages/PageSendMessage.qml ../qml/pages/PageSettingsAccount.qml ../qml/pages/PageSettingsGeneral.qml @@ -148,7 +149,6 @@ ../qml/pages/PageSettingsRecordsManagement.qml ../qml/pages/PageSettingsStorage.qml ../qml/pages/PageSettingsSync.qml - ../qml/pages/PageUploadMessageToRm.qml ../qml/main.qml diff --git a/src/main.cpp b/src/main.cpp index 4d769f16..68ef321d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -111,6 +111,7 @@ const struct QmlTypeEntry qmlPages[] = { { "PageMessageDetail", 1, 0 }, { "PageMessageList", 1, 0 }, { "PageMessageSearch", 1, 0 }, + { "PageRecordsManagementUpload", 1, 0 }, { "PageSendMessage", 1, 0 }, { "PageSettingsAccount", 1, 0 }, { "PageSettingsGeneral", 1, 0 }, @@ -118,7 +119,6 @@ const struct QmlTypeEntry qmlPages[] = { { "PageSettingsRecordsManagement", 1, 0 }, { "PageSettingsStorage", 1, 0 }, { "PageSettingsSync", 1, 0 }, - { "PageUploadMessageToRm", 1, 0 }, { NULL, 0, 0 } }; -- GitLab From 537804b50c3d6814fe1451560c4c9c1d85432a73 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Thu, 1 Feb 2018 13:24:29 +0100 Subject: [PATCH 14/63] Fixed wrong token decryption if PIN is used --- src/setwrapper.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/setwrapper.cpp b/src/setwrapper.cpp index 7328750b..90981c96 100644 --- a/src/setwrapper.cpp +++ b/src/setwrapper.cpp @@ -167,6 +167,8 @@ void GlobalSettingsQmlWrapper::verifyPin(const QString &pinValue) if (verResult) { /* Decrypt all keys in account model. */ AccountListModel::decryptAllPwds(globSet._pinVal); + /* Decrypt records management token in global settings */ + globRecordsManagementSet.decryptToken(globSet._pinVal); } emit sendPinReply(verResult); -- GitLab From 6b738b57bde61b36088f6bed076e45933b160973 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Thu, 1 Feb 2018 13:35:53 +0100 Subject: [PATCH 15/63] Removed unnecessary token decryption from settings --- src/settings.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/settings.cpp b/src/settings.cpp index d1d93a98..88c2f226 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -201,7 +201,6 @@ void Settings::loadFromSettings(const QSettings &settings) /* Records management settings. */ globRecordsManagementSet.loadFromSettings(settings); - globRecordsManagementSet.decryptToken(globSet._pinVal); } QString Settings::settingsPath(void) -- GitLab From aa337e8cb3451cfc98f1383de0117593169782e9 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Thu, 1 Feb 2018 14:02:27 +0100 Subject: [PATCH 16/63] Removed asserts when account model can be NULL --- src/accounts.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/accounts.cpp b/src/accounts.cpp index 75bd453c..77ba2639 100644 --- a/src/accounts.cpp +++ b/src/accounts.cpp @@ -54,8 +54,8 @@ void Accounts::updateAccountCounters(const QVariant &acntModelVariant) AccountListModel *accountModel = AccountListModel::fromVariant(acntModelVariant); if (accountModel == Q_NULLPTR) { - Q_ASSERT(0); - qCritical("%s", "Cannot access account model."); + /* accountModel can be NULL when app is shutdown */ + qWarning("%s", "Cannot access account model."); return; } @@ -70,8 +70,8 @@ void Accounts::updateNewMessageCounter(const QVariant &acntModelVariant, AccountListModel *accountModel = AccountListModel::fromVariant(acntModelVariant); if (accountModel == Q_NULLPTR) { - Q_ASSERT(0); - qCritical("%s", "Cannot access account model."); + /* accountModel can be NULL when app is shutdown */ + qWarning("%s", "Cannot access account model."); return; } -- GitLab From d163c2dcc64922553e2e2fe27fe99240aa49c355 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Thu, 1 Feb 2018 16:47:32 +0100 Subject: [PATCH 17/63] Added torso of upload hierarchy list model. --- mobile-datovka.pro | 2 + qml/pages/PageRecordsManagementUpload.qml | 142 +++++++++++++--------- src/main.cpp | 2 + src/records_management.cpp | 16 ++- src/records_management.h | 2 +- 5 files changed, 105 insertions(+), 59 deletions(-) diff --git a/mobile-datovka.pro b/mobile-datovka.pro index 898a9aec..f7536c25 100644 --- a/mobile-datovka.pro +++ b/mobile-datovka.pro @@ -136,6 +136,7 @@ SOURCES += \ src/qml_interaction/message_envelope.cpp \ src/qml_interaction/message_info.cpp \ src/qml_interaction/string_manipulation.cpp \ + src/records_management/models/upload_hierarchy_list_model.cpp \ src/records_management.cpp \ src/settings.cpp \ src/setwrapper.cpp \ @@ -217,6 +218,7 @@ HEADERS += \ src/qml_interaction/message_envelope.h \ src/qml_interaction/message_info.h \ src/qml_interaction/string_manipulation.h \ + src/records_management/models/upload_hierarchy_list_model.h \ src/records_management.h \ src/settings.h \ src/setwrapper.h \ diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index 83805d3c..ff4bf33d 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -50,9 +50,8 @@ Component { statusBar.visible = false } - ListModel { - /* TODO */ - id: uploadHierarchyModel + UploadHierarchyListModel { + id: uploadHierarchyListModel Component.onCompleted: { } } @@ -65,61 +64,94 @@ Component { } } - Flickable { - id: flickable - z: 0 + AccessibleText { + id: topText anchors.top: headerBar.bottom - anchors.right: parent.right - anchors.left: parent.left - anchors.bottom: parent.bottom - contentHeight: flickContent.implicitHeight - Pane { - id: flickContent - anchors.fill: parent - Column { - anchors.right: parent.right - anchors.left: parent.left - spacing: formItemVerticalSpacing - AccessibleText { - color: datovkaPalette.mid - wrapMode: Text.Wrap - horizontalAlignment: Text.AlignHCenter - width: parent.width - text: qsTr("Here you can upload message into chosen folder in the records management. Update folder hierarchy and select target folder.") - } - AccessibleButton { - id: hierarchyButton - anchors.horizontalCenter: parent.horizontalCenter - height: inputItemHeight - font.pointSize: defaultTextFont.font.pointSize - text: qsTr("Update upload hierarchy") - onClicked: { - recordsManagement.callUploadHierarchy() - } + color: datovkaPalette.mid + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + width: parent.width + text: qsTr("Here you can upload message into chosen folder in the records management. Update folder hierarchy and select target folder.") + } + Row { + id: buttonRow + spacing: formItemVerticalSpacing * 5 + anchors.top: topText.bottom + anchors.horizontalCenter: parent.horizontalCenter + AccessibleButton { + id: hierarchyButton + height: inputItemHeight + font.pointSize: defaultTextFont.font.pointSize + text: qsTr("Update upload hierarchy") + onClicked: { + recordsManagement.callUploadHierarchy(uploadHierarchyListModel) + } + } + AccessibleButton { + id: uplaodButton + height: inputItemHeight + font.pointSize: defaultTextFont.font.pointSize + text: qsTr("Upload message") + onClicked: { + recordsManagement.uploadMessage(userName, msgId, msgType, uploadIds) + } + } + } + ScrollableListView { + id: uploadHierarchyList + + delegateHeight: headerHeight + + anchors.top: buttonRow.bottom + anchors.bottom: bottomText.top + /* TODO */ + width: parent.width + model: uploadHierarchyListModel + + delegate: Rectangle { + id: uploadItem + + width: parent.width + height: uploadHierarchyList.delegateHeight + color: datovkaPalette.base + + Text { + id: uploadText + anchors.fill: parent + text: rName + color: datovkaPalette.mid + font.pointSize: textFontSizeSmall + renderType: Text.NativeRendering + } + + MouseArea { + function handleClick() { + console.log("Clicked hierarchy item '" + index + "'.") } - TreeView { - /* TODO */ - width: parent.width - TableViewColumn { - title: "Records management hierarchy" - role: "folderName" - width: parent.width - } - model: uploadHierarchyModel + + anchors.fill: parent + + Accessible.role: Accessible.Button + Accessible.name: qsTr("Open attachment '%1'.").arg(rName) + Accessible.onScrollDownAction: uploadHierarchyList.scrollDown() + Accessible.onScrollUpAction: uploadHierarchyList.scrollUp() + Accessible.onPressAction: { + handleClick() } - AccessibleButton { - id: uplaodButton - anchors.horizontalCenter: parent.horizontalCenter - height: inputItemHeight - font.pointSize: defaultTextFont.font.pointSize - text: qsTr("Upload message") - onClicked: { - recordsManagement.uploadMessage(userName, msgId, msgType, uploadIds) - } + onClicked: { + handleClick() } - } // Column layout - } // Pane - ScrollIndicator.vertical: ScrollIndicator {} - } // Flickable + } + } + } + AccessibleText { + id: bottomText + anchors.bottom: parent.bottom + color: datovkaPalette.mid + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + width: parent.width + text: "Test text" + } } } diff --git a/src/main.cpp b/src/main.cpp index 68ef321d..355bb1d7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -54,6 +54,7 @@ #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.h" #include "src/settings.h" #include "src/setwrapper.h" @@ -338,6 +339,7 @@ int main(int argc, char *argv[]) Messages::declareQML(); MsgEnvelope::declareQML(); MsgInfo::declareQML(); + UploadHierarchyListModel::declareQML(); InteractionZfoFile interactionZfoFile; diff --git a/src/records_management.cpp b/src/records_management.cpp index 23e6b439..28d3310f 100644 --- a/src/records_management.cpp +++ b/src/records_management.cpp @@ -29,6 +29,7 @@ #include "src/datovka_shared/records_management/json/upload_file.h" #include "src/datovka_shared/records_management/json/upload_hierarchy.h" #include "src/models/accountmodel.h" +#include "src/records_management/models/upload_hierarchy_list_model.h" #include "src/records_management.h" #include "src/settings.h" #include "src/worker/emitter.h" @@ -145,7 +146,8 @@ fail: QMessageBox::critical(Q_NULLPTR, errTitle, errDesctiption); } -void RecordsManagement::callUploadHierarchy(void) +void RecordsManagement::callUploadHierarchy( + const QVariant &hirerachyModelVariant) { QByteArray response; QString errTitle = tr("Communication error"); @@ -153,8 +155,16 @@ void RecordsManagement::callUploadHierarchy(void) emit statusBarTextChanged(tr("Upload hierarchy"), true, true); + UploadHierarchyListModel *hierarchyModel = + UploadHierarchyListModel::fromVariant(hirerachyModelVariant); + if (hierarchyModel == Q_NULLPTR) { + Q_ASSERT(0); + qCritical("%s", "Cannot access upload hierarchy model."); + return; + } + /* Clear model. */ - m_uploadModel.setHierarchy(UploadHierarchyResp()); + hierarchyModel->setHierarchy(UploadHierarchyResp()); m_rmc.setConnection(globRecordsManagementSet.url(), globRecordsManagementSet.token()); @@ -168,7 +178,7 @@ void RecordsManagement::callUploadHierarchy(void) if (!ok || !uhRes.isValid()) { goto fail; } - m_uploadModel.setHierarchy(uhRes); + hierarchyModel->setHierarchy(uhRes); } else { errDesctiption = tr("Received empty response."); goto fail; diff --git a/src/records_management.h b/src/records_management.h index b4c0cda9..c3b16655 100644 --- a/src/records_management.h +++ b/src/records_management.h @@ -60,7 +60,7 @@ public: * @brief Download upload hierarchy and set model. */ Q_INVOKABLE - void callUploadHierarchy(void); + void callUploadHierarchy(const QVariant &hirerachyModelVariant); /*! * @brief Obtain information about stored messages from records managnt. -- GitLab From b19aca0ed25bffd62820b83a2853ea050fb0347f Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Thu, 1 Feb 2018 16:51:56 +0100 Subject: [PATCH 18/63] Added missing sources. --- .../models/upload_hierarchy_list_model.cpp | 238 ++++++++++++++++++ .../models/upload_hierarchy_list_model.h | 174 +++++++++++++ 2 files changed, 412 insertions(+) create mode 100644 src/records_management/models/upload_hierarchy_list_model.cpp create mode 100644 src/records_management/models/upload_hierarchy_list_model.h diff --git a/src/records_management/models/upload_hierarchy_list_model.cpp b/src/records_management/models/upload_hierarchy_list_model.cpp new file mode 100644 index 00000000..883571a9 --- /dev/null +++ b/src/records_management/models/upload_hierarchy_list_model.cpp @@ -0,0 +1,238 @@ +/* + * 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 /* qmlRegisterType */ + +#include "src/records_management/models/upload_hierarchy_list_model.h" + +void UploadHierarchyListModel::declareQML(void) +{ + qmlRegisterType("cz.nic.mobileDatovka.models", 1, 0, "UploadHierarchyListModel"); + qRegisterMetaType(); +} + +UploadHierarchyListModel::UploadHierarchyListModel(QObject *parent) + : QAbstractListModel(parent), + m_hierarchy() +{ +} + +UploadHierarchyListModel::UploadHierarchyListModel( + const UploadHierarchyListModel &model, QObject *parent) + : QAbstractListModel(parent), + m_hierarchy() +{ + Q_UNUSED(model); +} + +QModelIndex UploadHierarchyListModel::index(int row, int column, + const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) { + return QModelIndex(); + } + + quintptr internalId = 0; + + if (!parent.isValid()) { + if (showRootName()) { + /* Root is shown. */ + internalId = (quintptr)m_hierarchy.root(); + } else { + /* Root is not shown. */ + internalId = (quintptr)m_hierarchy.root()->sub().at(row); + } + } else { + const UploadHierarchyResp::NodeEntry *entry = + (UploadHierarchyResp::NodeEntry *)parent.internalId(); + internalId = (quintptr)entry->sub().at(row); + } + + return createIndex(row, column, internalId); +} + +QModelIndex UploadHierarchyListModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) { + return QModelIndex(); + } + + const UploadHierarchyResp::NodeEntry *iEntry = + (UploadHierarchyResp::NodeEntry *)index.internalId(); + const UploadHierarchyResp::NodeEntry *pEntry = iEntry->super(); + + if ((pEntry != Q_NULLPTR) && + ((pEntry != m_hierarchy.root()) || showRootName())) { + if (pEntry == m_hierarchy.root()) { + /* Root node is shown and parent is root. */ + return createIndex(0, 0, (quintptr)pEntry); + } else { + const UploadHierarchyResp::NodeEntry *ppEntry = + pEntry->super(); + int row = 0; + /* Find position of parent. */ + for ( ; row < ppEntry->sub().size(); ++row) { + if (pEntry == ppEntry->sub().at(row)) { + break; + } + } + Q_ASSERT(row < ppEntry->sub().size()); + return createIndex(row, 0, (quintptr)pEntry); + } + } else { + return QModelIndex(); + } +} + +int UploadHierarchyListModel::rowCount(const QModelIndex &parent) const +{ + + if (parent.column() > 0) { + return 0; + } + + if (!parent.isValid()) { + if (!m_hierarchy.isValid()) { + /* Invalid hierarchy. */ + return 0; + } + + /* Root. */ + if (showRootName()) { + return 1; + } else { + return m_hierarchy.root()->sub().size(); + } + } else { + const UploadHierarchyResp::NodeEntry *entry = + (UploadHierarchyResp::NodeEntry *)parent.internalId(); + return entry->sub().size(); + } +} + +QHash UploadHierarchyListModel::roleNames(void) const +{ + static QHash roles; + if (roles.isEmpty()) { + roles[ROLE_NAME] = "rName"; + roles[ROLE_FILTER_DATA] = "rFilterData"; + roles[ROLE_FILTER_DATA_RECURSIVE] = "rFilterDataRecursive"; + roles[ROLE_ID] = "rId"; + } + return roles; +} + +QVariant UploadHierarchyListModel::data(const QModelIndex &index, + int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + const UploadHierarchyResp::NodeEntry *entry = + (UploadHierarchyResp::NodeEntry *)index.internalId(); + if (Q_UNLIKELY(entry == Q_NULLPTR)) { + Q_ASSERT(0); + return QVariant(); + } + + switch (role) { + case ROLE_NAME: + return entry->name(); + break; + case ROLE_FILTER_DATA: + return filterData(entry).join(QStringLiteral("\n")); + break; + case ROLE_FILTER_DATA_RECURSIVE: + return filterDataRecursive(entry, true); + break; + case ROLE_ID: + return entry->id(); + break; + default: + return QVariant(); + break; + } +} + +void UploadHierarchyListModel::setHierarchy(const UploadHierarchyResp &uhr) +{ + beginResetModel(); + m_hierarchy = uhr; + endResetModel(); +} + +UploadHierarchyListModel *UploadHierarchyListModel::fromVariant( + const QVariant &modelVariant) +{ + if (!modelVariant.canConvert()) { + return Q_NULLPTR; + } + QObject *obj = qvariant_cast(modelVariant); + return qobject_cast(obj); +} + +bool UploadHierarchyListModel::showRootName(void) const +{ + return m_hierarchy.isValid() && !m_hierarchy.root()->name().isEmpty(); +} + +QStringList UploadHierarchyListModel::filterData( + const UploadHierarchyResp::NodeEntry *entry) +{ + if (Q_UNLIKELY(entry == Q_NULLPTR)) { + Q_ASSERT(0); + return QStringList(); + } + + return QStringList(entry->name()) + entry->metadata(); +} + +QStringList UploadHierarchyListModel::filterDataRecursive( + const UploadHierarchyResp::NodeEntry *entry, bool takeSuper) +{ + if (Q_UNLIKELY(entry == Q_NULLPTR)) { + Q_ASSERT(0); + return QStringList(); + } + + QStringList res(filterData(entry)); + foreach (const UploadHierarchyResp::NodeEntry *sub, entry->sub()) { + res += filterDataRecursive(sub, false); + } + if (takeSuper) { + /* + * Add also filter data from superordinate node. This has the + * effect that all sub-nodes (including those not matching the + * filter) of a node which matches the entered filter are + * going to be also displayed. + */ + const UploadHierarchyResp::NodeEntry *sup = entry->super(); + if (Q_UNLIKELY(sup == Q_NULLPTR)) { + return res; + } + res += filterData(sup); + } + + return res; +} diff --git a/src/records_management/models/upload_hierarchy_list_model.h b/src/records_management/models/upload_hierarchy_list_model.h new file mode 100644 index 00000000..37485e0c --- /dev/null +++ b/src/records_management/models/upload_hierarchy_list_model.h @@ -0,0 +1,174 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include "src/datovka_shared/records_management/json/upload_hierarchy.h" + +/*! + * @brief Upload hierarchy list model. + */ +class UploadHierarchyListModel : public QAbstractListModel { + Q_OBJECT + +public: + /*! + * @brief Roles which this model supports. + */ + enum Roles { + ROLE_NAME = Qt::UserRole, + ROLE_FILTER_DATA, + ROLE_FILTER_DATA_RECURSIVE, + ROLE_ID, + ROLE_LEAF + }; + + /* Don't forget to declare various properties to the QML system. */ + static + void declareQML(void); + + /*! + * @brief Constructor. + * + * @param[in] parent Pointer to parent object. + */ + explicit UploadHierarchyListModel(QObject *parent = Q_NULLPTR); + + /*! + * @brief Copy constructor. + * + * @note Needed for QVariant conversion. + * + * @param[in] model Model to be copied. + * @param[in] parent Pointer to parent object. + */ + explicit UploadHierarchyListModel(const UploadHierarchyListModel &model, + QObject *parent = Q_NULLPTR); + + /*! + * @brief Return index specified by supplied parameters. + * + * @param[in] row Item row. + * @param[in] column Parent column. + * @param[in] parent Parent index. + * @return Index to desired element or invalid index on error. + */ + virtual + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + + /*! + * @brief Return parent index of the item with the given index. + * + * @param[in] index Child node index. + * @return Index of the parent node or invalid index on error. + */ + virtual + QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE; + + /*! + * @brief Return number of rows under the given parent. + * + * @param[in] parent Parent node index. + * @return Number of rows. + */ + virtual + int rowCount(const QModelIndex &parent = QModelIndex()) const + Q_DECL_OVERRIDE; + + /*! + * @brief Returns the model's role names. + * + * @return Model's role names. + */ + virtual + QHash roleNames(void) const Q_DECL_OVERRIDE; + + /*! + * @brief Return data stored in given location under given role. + * + * @param[in] index Index specifying the item. + * @param[in] role Data role. + * @return Data from model. + */ + virtual + QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; + + /*! + * @brief Set content data. + * + * @param[in] uhr Upload hierarchy response structure. + */ + void setHierarchy(const UploadHierarchyResp &uhr); + + /*! + * @brief Converts QVariant obtained from QML into model pointer. + * + * @note Some weird stuff happens in QML when passing attachment model + * directly as constant reference. Wrong constructors are called + * and no data are passed. That's because we are using a QVariant. + * This function does not allocate a new model. + * + * @param[in] modelVariant QVariant holding the model. + * @return Pointer to model if it could be acquired, Q_NULLPTR else. + */ + static + UploadHierarchyListModel *fromVariant(const QVariant &modelVariant); + +private: + /*! + * @brief Check whether to show root name. + * + * @return True in root node contains a name. + */ + bool showRootName(void) const; + + /*! + * @brief Return all data related to node which can be used for + * filtering. + * + * @param[in] entry Node identifier. + * @return List of strings. + */ + static + QStringList filterData(const UploadHierarchyResp::NodeEntry *entry); + + /*! + * @brief Returns list of all (meta)data (including children). + * + * @param[in] entry Node identifying the root. + * @param[in] takeSuper Set true when data of superordinate node should + * be taken into account. + * @return List of all gathered data according to which can be filtered. + */ + static + QStringList filterDataRecursive( + const UploadHierarchyResp::NodeEntry *entry, bool takeSuper); + + UploadHierarchyResp m_hierarchy; /*!< Upload hierarchy structure. */ +}; + +/* QML passes its arguments via QVariant. */ +Q_DECLARE_METATYPE(UploadHierarchyListModel) -- GitLab From 5199cad204391b469f3ce8f71116e681ee9d9166 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Thu, 1 Feb 2018 17:33:53 +0100 Subject: [PATCH 19/63] Records management settings page refactoring --- qml/pages/PageSettingsRecordsManagement.qml | 37 ++++-- src/records_management.cpp | 124 +++++++++----------- src/records_management.h | 18 ++- 3 files changed, 95 insertions(+), 84 deletions(-) diff --git a/qml/pages/PageSettingsRecordsManagement.qml b/qml/pages/PageSettingsRecordsManagement.qml index 13f2879c..f60b6e72 100644 --- a/qml/pages/PageSettingsRecordsManagement.qml +++ b/qml/pages/PageSettingsRecordsManagement.qml @@ -41,12 +41,6 @@ Item { clearButton.enabled = infoButton.enabled } - /* Enable data update and anothers labels if service name and token are filled */ - function areServiceInfoAvailable() { - serviceInfo.visible = (serviceName.text.toString() !== "" && tokenName.text.toString() !== "") - infoButton.text = (updateRmDataButton.visible) ? qsTr("Update service info") : qsTr("Get service info") - } - /* Clear all data and info */ function clearAll() { urlTextField.clear() @@ -63,6 +57,7 @@ Item { tokenTextField.text = settings.rmToken() areUrlandTokenFilled() recordsManagement.loadStoredServiceInfo() + updateRmDataButton.visible = recordsManagement.isValidRecordsManagement() } PageHeader { @@ -83,9 +78,9 @@ Item { source: "qrc:/ui/checkbox-marked-circle.svg" accessibleName: qsTr("Accept changes") onClicked: { - recordsManagement.updateServiceInfo(urlTextField.text, lastUrlFromSettings, serviceName.text, tokenName.text) settings.setRmUrl(urlTextField.text) settings.setRmToken(tokenTextField.text) + recordsManagement.updateServiceInfo(urlTextField.text, lastUrlFromSettings, serviceName.text, tokenName.text) pageView.pop(StackView.Immediate) } } @@ -172,7 +167,8 @@ Item { font.pointSize: defaultTextFont.font.pointSize text: qsTr("Get service info") onClicked: { - recordsManagement.callServiceInfo(urlTextField.text, tokenTextField.text) + serviceInfo.visible = recordsManagement.callServiceInfo(urlTextField.text, tokenTextField.text) + serviceInfoError.visible = !serviceInfo.visible } } AccessibleButton { @@ -185,6 +181,14 @@ Item { } } } // Row + AccessibleText { + id: serviceInfoError + visible: false + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + width: parent.width + text: qsTr("Communication error. Cannot obtain records management info from server. Internet connection failed or service url and identification token can be wrong!") + } Column { id: serviceInfo width: parent.width @@ -239,12 +243,13 @@ Item { } AccessibleButton { id: updateRmDataButton + visible: false height: inputItemHeight font.pointSize: defaultTextFont.font.pointSize anchors.horizontalCenter: parent.horizontalCenter text: qsTr("Update list of uploaded files") onClicked: { - recordsManagement.getStoredMsgInfoFromRecordsManagement() + recordsManagement.getStoredMsgInfoFromRecordsManagement(urlTextField.text, tokenTextField.text) } } } // // Column @@ -255,9 +260,17 @@ Item { Connections { target: recordsManagement onServiceInfo: { - serviceName.text = srName - tokenName.text = srToken - areServiceInfoAvailable() + if (srName !== "" && srToken !== "") { + serviceInfo.visible = true + infoButton.text = (serviceInfo.visible) ? qsTr("Update service info") : qsTr("Get service info") + serviceName.text = srName + tokenName.text = srToken + updateRmDataButton.visible = serviceInfo.visible + serviceInfoError.visible = false + } else { + serviceInfo.visible = false + updateRmDataButton.visible = serviceInfo.visible + } } } } // Item diff --git a/src/records_management.cpp b/src/records_management.cpp index 28d3310f..79836fc7 100644 --- a/src/records_management.cpp +++ b/src/records_management.cpp @@ -110,12 +110,14 @@ RecordsManagement::~RecordsManagement(void) globWorkPool.stop(); } -void RecordsManagement::callServiceInfo(const QString &urlStr, - const QString &tokenStr) +bool RecordsManagement::callServiceInfo(const QString &urlStr, + const QString &tokenStr) { QByteArray response; - QString errTitle = tr("Communication error"); - QString errDesctiption = tr("Received invalid response."); + + if (urlStr.trimmed().isEmpty() || tokenStr.trimmed().isEmpty()) { + return false; + } emit statusBarTextChanged(tr("Get service info"), true, true); @@ -127,31 +129,29 @@ void RecordsManagement::callServiceInfo(const QString &urlStr, bool ok = false; ServiceInfoResp siRes( ServiceInfoResp::fromJson(response, &ok)); - if (!ok || !siRes.isValid()) { - goto fail; + if (ok && siRes.isValid()) { + m_logoSvg = siRes.logoSvg(); + loadRecordsManagementPixmap(m_logoSvg); + emit serviceInfo(siRes.name(), siRes.tokenName()); + emit statusBarTextChanged(tr("Done"), false, true); + return true; } - m_logoSvg = siRes.logoSvg(); - loadRecordsManagementPixmap(m_logoSvg); - emit serviceInfo(siRes.name(), siRes.tokenName()); - } else { - errDesctiption = tr("Received empty response."); - goto fail; } } - emit statusBarTextChanged(tr("Done"), false, true); - return; -fail: - emit statusBarTextChanged(errTitle, false, true); - QMessageBox::critical(Q_NULLPTR, errTitle, errDesctiption); + emit statusBarTextChanged(tr("Communication error"), false, true); + return false; } void RecordsManagement::callUploadHierarchy( const QVariant &hirerachyModelVariant) { QByteArray response; - QString errTitle = tr("Communication error"); - QString errDesctiption = tr("Received invalid response."); + + if (globRecordsManagementSet.url().isEmpty() || + globRecordsManagementSet.token().isEmpty()) { + return; + } emit statusBarTextChanged(tr("Upload hierarchy"), true, true); @@ -175,33 +175,35 @@ void RecordsManagement::callUploadHierarchy( bool ok = false; UploadHierarchyResp uhRes( UploadHierarchyResp::fromJson(response, &ok)); - if (!ok || !uhRes.isValid()) { - goto fail; + if (ok && uhRes.isValid()) { + /* Set model. */ + hierarchyModel->setHierarchy(uhRes); + emit statusBarTextChanged(tr("Done"), false, true); + return; } - hierarchyModel->setHierarchy(uhRes); - } else { - errDesctiption = tr("Received empty response."); - goto fail; } } - emit statusBarTextChanged(tr("Done"), false, true); - return; -fail: - emit statusBarTextChanged(errTitle, false, true); - QMessageBox::critical(Q_NULLPTR, errTitle, errDesctiption); + emit statusBarTextChanged(tr("Communication error"), false, true); } -void RecordsManagement::getStoredMsgInfoFromRecordsManagement(void) +void RecordsManagement::getStoredMsgInfoFromRecordsManagement( + const QString &urlStr, const QString &tokenStr) { + QString url = urlStr.trimmed(); + QString token = tokenStr.trimmed(); + + if (url.isEmpty() || token.isEmpty()) { + return; + } + emit statusBarTextChanged(tr("Sync service"), true, true); TaskRecordsManagementStoredMessages *task = new (::std::nothrow) TaskRecordsManagementStoredMessages( - globRecordsManagementSet.url(), - globRecordsManagementSet.token(), - TaskRecordsManagementStoredMessages::RM_UPDATE_STORED, - Q_NULLPTR, QString(), 0, 0); + url, token, + TaskRecordsManagementStoredMessages::RM_UPDATE_STORED, + Q_NULLPTR, QString(), 0, 0); if (Q_NULLPTR == task) { qCritical("%s", "Cannot create stored_files update task."); emit statusBarTextChanged(tr("Sync failed"), false, true); @@ -227,8 +229,7 @@ void RecordsManagement::getStoredMsgInfoFromRecordsManagement(void) TaskRecordsManagementStoredMessages *task = new (::std::nothrow) TaskRecordsManagementStoredMessages( - globRecordsManagementSet.url(), - globRecordsManagementSet.token(), + url, token, TaskRecordsManagementStoredMessages::RM_DOWNLOAD_ALL, msgDb, userName, accNumber, accTotal); if (Q_NULLPTR == task) { @@ -245,10 +246,7 @@ void RecordsManagement::getStoredMsgInfoFromRecordsManagement(void) bool RecordsManagement::isValidRecordsManagement(void) { - RecordsManagementDb::ServiceInfoEntry entry( - globRecordsManagementDbPtr->serviceInfo()); - return (entry.isValid() && !globRecordsManagementSet.url().isEmpty() - && !globRecordsManagementSet.token().isEmpty()); + return globRecordsManagementSet.isValid(); } void RecordsManagement::loadStoredServiceInfo(void) @@ -307,28 +305,27 @@ bool RecordsManagement::uploadMessage(const QString &userName, return uploadFile(msgId, msgFileName, msgData, uploadIds); } -bool RecordsManagement::updateServiceInfo(const QString &urlStr, - const QString &urlStrSettings, const QString &srName, const QString &srToken) +bool RecordsManagement::updateServiceInfo(const QString &newUrlStr, + const QString &oldUrlStr, const QString &srName, const QString &srToken) { if (Q_NULLPTR == globRecordsManagementDbPtr) { return false; } - if (!urlStr.trimmed().isEmpty()) { + QString cUrlStr = newUrlStr.trimmed(); - Q_ASSERT(!urlStr.trimmed().isEmpty()); + if (!cUrlStr.isEmpty()) { RecordsManagementDb::ServiceInfoEntry entry; - entry.url = urlStr.trimmed(); + entry.url = cUrlStr; entry.name = srName; entry.tokenName = srToken; entry.logoSvg = m_logoSvg; globRecordsManagementDbPtr->updateServiceInfo(entry); - - if (urlStrSettings != urlStr) { - globRecordsManagementDbPtr->deleteAllStoredMsg(); + if (oldUrlStr != cUrlStr) { + return globRecordsManagementDbPtr->deleteAllStoredMsg(); } } else { - globRecordsManagementDbPtr->deleteAllEntries(); + return globRecordsManagementDbPtr->deleteAllEntries(); } return true; @@ -373,21 +370,24 @@ void RecordsManagement::loadRecordsManagementPixmap(const QByteArray &logoSvg) bool RecordsManagement::uploadFile(qint64 dmId, const QString &msgFileName, const QByteArray &msgData, const QStringList &uploadIds) { + QByteArray response; + if (msgFileName.isEmpty() || msgData.isEmpty()) { Q_ASSERT(0); return false; } + if (globRecordsManagementSet.url().isEmpty() || + globRecordsManagementSet.token().isEmpty()) { + return false; + } + UploadFileReq ufReq(uploadIds, msgFileName, msgData); if (!ufReq.isValid()) { Q_ASSERT(0); return false; } - QByteArray response; - QString errTitle = tr("Communication error"); - QString errDesctiption = tr("Received invalid response."); - emit statusBarTextChanged(tr("Upload message"), true, true); m_rmc.setConnection(globRecordsManagementSet.url(), @@ -399,21 +399,13 @@ bool RecordsManagement::uploadFile(qint64 dmId, const QString &msgFileName, bool ok = false; UploadFileResp ufRes( UploadFileResp::fromJson(response, &ok)); - if (!ok || !ufRes.isValid()) { - goto fail; + if (ok && ufRes.isValid()) { + emit statusBarTextChanged(tr("Done"), false, true); + return processUploadFileResponse(ufRes, dmId); } - emit statusBarTextChanged(tr("Done"), false, true); - return processUploadFileResponse(ufRes, dmId); - } else { - errDesctiption = tr("Received empty response."); - goto fail; } } - emit statusBarTextChanged(tr("Done"), false, true); - return false; -fail: - emit statusBarTextChanged(errTitle, false, true); - QMessageBox::critical(Q_NULLPTR, errTitle, errDesctiption); + emit statusBarTextChanged(tr("Communication error"), false, true); return false; } diff --git a/src/records_management.h b/src/records_management.h index c3b16655..3d396a01 100644 --- a/src/records_management.h +++ b/src/records_management.h @@ -52,9 +52,10 @@ public: * * @param[in] urlStr Records management url string. * @param[in] tokenStr Records management token string. + * @return True if success. */ Q_INVOKABLE - void callServiceInfo(const QString &urlStr, const QString &tokenStr); + bool callServiceInfo(const QString &urlStr, const QString &tokenStr); /*! * @brief Download upload hierarchy and set model. @@ -64,9 +65,13 @@ public: /*! * @brief Obtain information about stored messages from records managnt. + * + * @param[in] urlStr Records management url string. + * @param[in] tokenStr Records management token string. */ Q_INVOKABLE - void getStoredMsgInfoFromRecordsManagement(void); + void getStoredMsgInfoFromRecordsManagement(const QString &urlStr, + const QString &tokenSt); /*! * @brief Test if records management is set, active and valid. @@ -98,15 +103,16 @@ public: /*! * @brief Update record management settings. * - * @param[in] urlStr New records management url string. - * @param[in] urlStrSettings Records management url string from settings. + * @param[in] newUrlStr New records management url string. + * @param[in] oldUrlStr Records management url string from settings. * @param[in] srName Service name. * @param[in] srToken Service token. * @return True when data have been updated, false else. */ Q_INVOKABLE - bool updateServiceInfo(const QString &urlStr, const QString &urlStrSettings, - const QString &srName, const QString &srToken); + bool updateServiceInfo(const QString &newUrlStr, + const QString &oldUrlStr, const QString &srName, + const QString &srToken); signals: -- GitLab From 60c436c1a0a0f730932fd84433fbbd939d3567c9 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Fri, 2 Feb 2018 09:43:01 +0100 Subject: [PATCH 20/63] Able to descend the upload hierarchy tree. --- qml/pages/PageRecordsManagementUpload.qml | 2 +- .../models/upload_hierarchy_list_model.cpp | 89 ++++++++++++++----- .../models/upload_hierarchy_list_model.h | 22 ++++- 3 files changed, 91 insertions(+), 22 deletions(-) diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index ff4bf33d..d679d632 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -126,7 +126,7 @@ Component { MouseArea { function handleClick() { - console.log("Clicked hierarchy item '" + index + "'.") + uploadHierarchyListModel.navigateSub(index); } anchors.fill: parent diff --git a/src/records_management/models/upload_hierarchy_list_model.cpp b/src/records_management/models/upload_hierarchy_list_model.cpp index 883571a9..c6f0eb0b 100644 --- a/src/records_management/models/upload_hierarchy_list_model.cpp +++ b/src/records_management/models/upload_hierarchy_list_model.cpp @@ -33,16 +33,17 @@ void UploadHierarchyListModel::declareQML(void) UploadHierarchyListModel::UploadHierarchyListModel(QObject *parent) : QAbstractListModel(parent), - m_hierarchy() + m_hierarchy(), + m_workingRoot(Q_NULLPTR) { } UploadHierarchyListModel::UploadHierarchyListModel( const UploadHierarchyListModel &model, QObject *parent) : QAbstractListModel(parent), - m_hierarchy() + m_hierarchy(model.m_hierarchy), + m_workingRoot(m_hierarchy.root()) { - Q_UNUSED(model); } QModelIndex UploadHierarchyListModel::index(int row, int column, @@ -55,13 +56,13 @@ QModelIndex UploadHierarchyListModel::index(int row, int column, quintptr internalId = 0; if (!parent.isValid()) { - if (showRootName()) { - /* Root is shown. */ - internalId = (quintptr)m_hierarchy.root(); - } else { - /* Root is not shown. */ - internalId = (quintptr)m_hierarchy.root()->sub().at(row); + /* List model has always invalid parent. */ + if (Q_UNLIKELY(m_workingRoot == Q_NULLPTR)) { + Q_ASSERT(0); + return QModelIndex(); } + + internalId = (quintptr)m_workingRoot->sub().at(row); } else { const UploadHierarchyResp::NodeEntry *entry = (UploadHierarchyResp::NodeEntry *)parent.internalId(); @@ -82,8 +83,8 @@ QModelIndex UploadHierarchyListModel::parent(const QModelIndex &index) const const UploadHierarchyResp::NodeEntry *pEntry = iEntry->super(); if ((pEntry != Q_NULLPTR) && - ((pEntry != m_hierarchy.root()) || showRootName())) { - if (pEntry == m_hierarchy.root()) { + ((pEntry != m_workingRoot) || showRootName())) { + if (pEntry == m_workingRoot) { /* Root node is shown and parent is root. */ return createIndex(0, 0, (quintptr)pEntry); } else { @@ -106,23 +107,19 @@ QModelIndex UploadHierarchyListModel::parent(const QModelIndex &index) const int UploadHierarchyListModel::rowCount(const QModelIndex &parent) const { - if (parent.column() > 0) { return 0; } if (!parent.isValid()) { - if (!m_hierarchy.isValid()) { + /* List model has always invalid parent. */ + + if ((!m_hierarchy.isValid()) || (m_workingRoot == Q_NULLPTR)) { /* Invalid hierarchy. */ return 0; } - /* Root. */ - if (showRootName()) { - return 1; - } else { - return m_hierarchy.root()->sub().size(); - } + return m_workingRoot->sub().size(); } else { const UploadHierarchyResp::NodeEntry *entry = (UploadHierarchyResp::NodeEntry *)parent.internalId(); @@ -138,6 +135,8 @@ QHash UploadHierarchyListModel::roleNames(void) const roles[ROLE_FILTER_DATA] = "rFilterData"; roles[ROLE_FILTER_DATA_RECURSIVE] = "rFilterDataRecursive"; roles[ROLE_ID] = "rId"; + roles[ROLE_LEAF] = "rLeaf"; + roles[ROLE_SELECTABLE] = "rSelectable"; } return roles; } @@ -169,19 +168,69 @@ QVariant UploadHierarchyListModel::data(const QModelIndex &index, case ROLE_ID: return entry->id(); break; + case ROLE_LEAF: + return entry->sub().size() <= 0; + break; + case ROLE_SELECTABLE: + return !entry->id().isEmpty(); + break; default: return QVariant(); break; } } +Qt::ItemFlags UploadHierarchyListModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) { + return Qt::NoItemFlags; + } + + Qt::ItemFlags flags = + QAbstractItemModel::flags(index) & ~Qt::ItemIsEditable; + + const UploadHierarchyResp::NodeEntry *entry = + (UploadHierarchyResp::NodeEntry *)index.internalId(); + if (Q_UNLIKELY(entry == Q_NULLPTR)) { + Q_ASSERT(0); + return Qt::NoItemFlags; + } + if (entry->id().isEmpty()) { + flags &= ~Qt::ItemIsSelectable; + } + + return flags; +} + void UploadHierarchyListModel::setHierarchy(const UploadHierarchyResp &uhr) { beginResetModel(); m_hierarchy = uhr; + m_workingRoot = m_hierarchy.root(); endResetModel(); } +void UploadHierarchyListModel::navigateSub(int row) +{ + if (Q_UNLIKELY(row < 0) || (row >= rowCount())) { + Q_ASSERT(0); + return; + } + + if (m_workingRoot != Q_NULLPTR) { + const UploadHierarchyResp::NodeEntry *sub = + m_workingRoot->sub().at(row); + /* + * Descend the hierarchy only if the target node is not a leaf. + */ + if ((sub != Q_NULLPTR) && (sub->sub().size() > 0)) { + beginResetModel(); + m_workingRoot = sub; + endResetModel(); + } + } +} + UploadHierarchyListModel *UploadHierarchyListModel::fromVariant( const QVariant &modelVariant) { @@ -194,7 +243,7 @@ UploadHierarchyListModel *UploadHierarchyListModel::fromVariant( bool UploadHierarchyListModel::showRootName(void) const { - return m_hierarchy.isValid() && !m_hierarchy.root()->name().isEmpty(); + return m_hierarchy.isValid() && (m_workingRoot != Q_NULLPTR) && !m_workingRoot->name().isEmpty(); } QStringList UploadHierarchyListModel::filterData( diff --git a/src/records_management/models/upload_hierarchy_list_model.h b/src/records_management/models/upload_hierarchy_list_model.h index 37485e0c..e658dfd0 100644 --- a/src/records_management/models/upload_hierarchy_list_model.h +++ b/src/records_management/models/upload_hierarchy_list_model.h @@ -42,7 +42,8 @@ public: ROLE_FILTER_DATA, ROLE_FILTER_DATA_RECURSIVE, ROLE_ID, - ROLE_LEAF + ROLE_LEAF, + ROLE_SELECTABLE }; /* Don't forget to declare various properties to the QML system. */ @@ -116,6 +117,15 @@ public: virtual QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; + /*! + * @brief Returns item flags for given index. + * + * @brief[in] index Index specifying the item. + * @return Item flags. + */ + virtual + Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE; + /*! * @brief Set content data. * @@ -123,6 +133,15 @@ public: */ void setHierarchy(const UploadHierarchyResp &uhr); + /*! + * @brief Descend the tree hierarchy. Set a sub-node to act as working + * root. + * + * @param[in] row Sub-node index to be selected. + */ + Q_INVOKABLE + void navigateSub(int row); + /*! * @brief Converts QVariant obtained from QML into model pointer. * @@ -168,6 +187,7 @@ private: const UploadHierarchyResp::NodeEntry *entry, bool takeSuper); UploadHierarchyResp m_hierarchy; /*!< Upload hierarchy structure. */ + const UploadHierarchyResp::NodeEntry *m_workingRoot; /*!< Node acting as working root. */ }; /* QML passes its arguments via QVariant. */ -- GitLab From fb332cadc34bb575b7f2e925180d5ad551b0ffa2 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Fri, 2 Feb 2018 10:34:52 +0100 Subject: [PATCH 21/63] Able to ascend the upload hierarchy tree. --- qml/pages/PageRecordsManagementUpload.qml | 30 ++++++++++++++++- .../models/upload_hierarchy_list_model.cpp | 33 +++++++++++++++++++ .../models/upload_hierarchy_list_model.h | 23 +++++++++++-- 3 files changed, 83 insertions(+), 3 deletions(-) diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index d679d632..195f94a2 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -52,6 +52,9 @@ Component { UploadHierarchyListModel { id: uploadHierarchyListModel + onModelReset: { + locationLabel.text = uploadHierarchyListModel.navigatedRootName(true, "/") + } Component.onCompleted: { } } @@ -97,12 +100,37 @@ Component { } } } + Row { + id: navigateUpRow + spacing: formItemVerticalSpacing * 5 + anchors.top: buttonRow.bottom + anchors.left: parent.left + anchors.right: parent.right + AccessibleButton { + id: upButton + anchors { + verticalCenter: parent.verticalCenter; + } + text: "<" + accessibleName: qsTr("Up") /* Needs to be specified as "<" is not read. */ + onClicked: { + uploadHierarchyListModel.navigateSuper() + } + } + AccessibleText { + id: locationLabel + anchors { + verticalCenter: parent.verticalCenter; + } + text: "/" + } + } ScrollableListView { id: uploadHierarchyList delegateHeight: headerHeight - anchors.top: buttonRow.bottom + anchors.top: navigateUpRow.bottom anchors.bottom: bottomText.top /* TODO */ width: parent.width diff --git a/src/records_management/models/upload_hierarchy_list_model.cpp b/src/records_management/models/upload_hierarchy_list_model.cpp index c6f0eb0b..8e74021f 100644 --- a/src/records_management/models/upload_hierarchy_list_model.cpp +++ b/src/records_management/models/upload_hierarchy_list_model.cpp @@ -210,6 +210,20 @@ void UploadHierarchyListModel::setHierarchy(const UploadHierarchyResp &uhr) endResetModel(); } +void UploadHierarchyListModel::navigateSuper(void) +{ + if (m_workingRoot != Q_NULLPTR) { + const UploadHierarchyResp::NodeEntry *sup = + m_workingRoot->super(); + /* Ascend the hierarchy only if the root node has a name. */ + if ((!m_workingRoot->name().isEmpty()) && (sup != Q_NULLPTR)) { + beginResetModel(); + m_workingRoot = sup; + endResetModel(); + } + } +} + void UploadHierarchyListModel::navigateSub(int row) { if (Q_UNLIKELY(row < 0) || (row >= rowCount())) { @@ -231,6 +245,25 @@ void UploadHierarchyListModel::navigateSub(int row) } } +QString UploadHierarchyListModel::navigatedRootName(bool takeSuper, + const QString &sep) const +{ + if ((!m_hierarchy.isValid() || (m_workingRoot == Q_NULLPTR))) { + return sep; + } + + QString rootName(m_workingRoot->name() + sep); + if (takeSuper) { + const UploadHierarchyResp::NodeEntry *node = + m_workingRoot->super(); + while ((node != Q_NULLPTR) && (!node->name().isEmpty())) { + rootName = node->name() + sep + rootName; + } + } + + return rootName; +} + UploadHierarchyListModel *UploadHierarchyListModel::fromVariant( const QVariant &modelVariant) { diff --git a/src/records_management/models/upload_hierarchy_list_model.h b/src/records_management/models/upload_hierarchy_list_model.h index e658dfd0..669c8533 100644 --- a/src/records_management/models/upload_hierarchy_list_model.h +++ b/src/records_management/models/upload_hierarchy_list_model.h @@ -134,14 +134,33 @@ public: void setHierarchy(const UploadHierarchyResp &uhr); /*! - * @brief Descend the tree hierarchy. Set a sub-node to act as working - * root. + * @brief Ascend the tree hierarchy. Set superordinate node to act + * as working root. + */ + Q_INVOKABLE + void navigateSuper(void); + + /*! + * @brief Descend the tree hierarchy. Set a subordinate node to act + * as working root. * * @param[in] row Sub-node index to be selected. */ Q_INVOKABLE void navigateSub(int row); + /*! + * @brief Return path to working root node. + * + * @param[in] takeSuper If true, then full path across all + * superordinate nodes is taken. + * @param[in] sep Separator to be used. It is always appended to + * the end of the returned string. + * @return Name or path of names to the working root node. + */ + Q_INVOKABLE + QString navigatedRootName(bool takeSuper, const QString &sep) const; + /*! * @brief Converts QVariant obtained from QML into model pointer. * -- GitLab From 97687eb617c083c78e739849dfc67e9a8ff756da Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Fri, 2 Feb 2018 13:09:29 +0100 Subject: [PATCH 22/63] Enabled upload hierarchy sorting. --- mobile-datovka.pro | 2 + qml/pages/PageRecordsManagementUpload.qml | 14 +++- src/main.cpp | 2 + .../models/upload_hierarchy_list_model.cpp | 3 + .../upload_hierarchy_qml_proxy_model.cpp | 60 ++++++++++++++ .../models/upload_hierarchy_qml_proxy_model.h | 79 +++++++++++++++++++ 6 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 src/records_management/models/upload_hierarchy_qml_proxy_model.cpp create mode 100644 src/records_management/models/upload_hierarchy_qml_proxy_model.h diff --git a/mobile-datovka.pro b/mobile-datovka.pro index f7536c25..3b7c421d 100644 --- a/mobile-datovka.pro +++ b/mobile-datovka.pro @@ -137,6 +137,7 @@ SOURCES += \ src/qml_interaction/message_info.cpp \ src/qml_interaction/string_manipulation.cpp \ src/records_management/models/upload_hierarchy_list_model.cpp \ + src/records_management/models/upload_hierarchy_qml_proxy_model.cpp \ src/records_management.cpp \ src/settings.cpp \ src/setwrapper.cpp \ @@ -219,6 +220,7 @@ HEADERS += \ src/qml_interaction/message_info.h \ src/qml_interaction/string_manipulation.h \ src/records_management/models/upload_hierarchy_list_model.h \ + src/records_management/models/upload_hierarchy_qml_proxy_model.h \ src/records_management.h \ src/settings.h \ src/setwrapper.h \ diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index 195f94a2..54e73906 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -44,6 +44,8 @@ Component { property var uploadIds: [] Component.onCompleted: { + uploadHierarchyProxyModel.setSourceModel(uploadHierarchyListModel) + uploadHierarchyProxyModel.sort() } Component.onDestruction: { @@ -53,12 +55,19 @@ Component { UploadHierarchyListModel { id: uploadHierarchyListModel onModelReset: { + uploadHierarchyProxyModel.sort() locationLabel.text = uploadHierarchyListModel.navigatedRootName(true, "/") } Component.onCompleted: { } } + UploadHierarchyQmlProxyModel { + id: uploadHierarchyProxyModel + Component.onCompleted: { + } + } + PageHeader { id: headerBar title: qsTr("Upload message %1").arg(msgId) @@ -134,7 +143,7 @@ Component { anchors.bottom: bottomText.top /* TODO */ width: parent.width - model: uploadHierarchyListModel + model: uploadHierarchyProxyModel delegate: Rectangle { id: uploadItem @@ -154,7 +163,8 @@ Component { MouseArea { function handleClick() { - uploadHierarchyListModel.navigateSub(index); + uploadHierarchyListModel.navigateSub( + uploadHierarchyProxyModel.mapToSource(index)); } anchors.fill: parent diff --git a/src/main.cpp b/src/main.cpp index 355bb1d7..e8f47dde 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -55,6 +55,7 @@ #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" @@ -340,6 +341,7 @@ int main(int argc, char *argv[]) MsgEnvelope::declareQML(); MsgInfo::declareQML(); UploadHierarchyListModel::declareQML(); + UploadHierarchyQmlProxyModel::declareQML(); InteractionZfoFile interactionZfoFile; diff --git a/src/records_management/models/upload_hierarchy_list_model.cpp b/src/records_management/models/upload_hierarchy_list_model.cpp index 8e74021f..10ad9b40 100644 --- a/src/records_management/models/upload_hierarchy_list_model.cpp +++ b/src/records_management/models/upload_hierarchy_list_model.cpp @@ -156,6 +156,9 @@ QVariant UploadHierarchyListModel::data(const QModelIndex &index, } switch (role) { + case Qt::DisplayRole: + return entry->name(); + break; case ROLE_NAME: return entry->name(); break; diff --git a/src/records_management/models/upload_hierarchy_qml_proxy_model.cpp b/src/records_management/models/upload_hierarchy_qml_proxy_model.cpp new file mode 100644 index 00000000..3d633637 --- /dev/null +++ b/src/records_management/models/upload_hierarchy_qml_proxy_model.cpp @@ -0,0 +1,60 @@ +/* + * 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 /* qmlRegisterType */ + +#include "src/records_management/models/upload_hierarchy_qml_proxy_model.h" + +void UploadHierarchyQmlProxyModel::declareQML(void) +{ + qmlRegisterType("cz.nic.mobileDatovka.models", 1, 0, "UploadHierarchyQmlProxyModel"); + qRegisterMetaType(); +} + +UploadHierarchyQmlProxyModel::UploadHierarchyQmlProxyModel(QObject *parent) + : UploadHierarchyProxyModel(parent) +{ +} + +UploadHierarchyQmlProxyModel::UploadHierarchyQmlProxyModel( + const UploadHierarchyQmlProxyModel &model, QObject *parent) + : UploadHierarchyProxyModel(parent) +{ + Q_UNUSED(model); +} + +void UploadHierarchyQmlProxyModel::setSourceModel(QAbstractItemModel *sourceModel) +{ + UploadHierarchyProxyModel::setSourceModel(sourceModel); +} + +int UploadHierarchyQmlProxyModel::mapToSource(int proxyRow) const +{ + const QModelIndex proxyIndex(index(proxyRow, 0, QModelIndex())); + return UploadHierarchyProxyModel::mapToSource(proxyIndex).row(); +} + +void UploadHierarchyQmlProxyModel::sort(void) +{ + UploadHierarchyProxyModel::sort(0, Qt::AscendingOrder); +} diff --git a/src/records_management/models/upload_hierarchy_qml_proxy_model.h b/src/records_management/models/upload_hierarchy_qml_proxy_model.h new file mode 100644 index 00000000..da6502d0 --- /dev/null +++ b/src/records_management/models/upload_hierarchy_qml_proxy_model.h @@ -0,0 +1,79 @@ +/* + * 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. + */ + +#pragma once + +#include "src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.h" + +/*! + * @brief Enables filtering from QML according to metadata. + */ +class UploadHierarchyQmlProxyModel : public UploadHierarchyProxyModel { + Q_OBJECT + +public: + /* Don't forget to declare various properties to the QML system. */ + static + void declareQML(void); + + /*! + * @brief Constructor. + * + * @param[in] parent Parent object. + */ + explicit UploadHierarchyQmlProxyModel(QObject *parent = Q_NULLPTR); + + /*! + * @brief Copy constructor. + * + * @note Needed for QVariant conversion. + * @note This is a dummy function. Calling this constructor causes an + * assertion failure. + * + * @param[in] model Model to be copied. + * @param[in] parent Pointer to parent object. + */ + explicit UploadHierarchyQmlProxyModel( + const UploadHierarchyQmlProxyModel &model, + QObject *parent = Q_NULLPTR); + + /*! + * @brief Set source model. + * + * @param[in] sourceModel Source model to be used. + */ + Q_INVOKABLE virtual + void setSourceModel(QAbstractItemModel *sourceModel) Q_DECL_OVERRIDE; + + Q_INVOKABLE + int mapToSource(int proxyRow) const; + + /*! + * @brief Sorts the model content. + */ + Q_INVOKABLE + void sort(void); +}; + +/* QML passes its arguments via QVariant. */ +Q_DECLARE_METATYPE(UploadHierarchyQmlProxyModel) -- GitLab From f3b2a1b640a9f582bd4e081d26b1c9f8b05dd188 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Fri, 2 Feb 2018 13:45:08 +0100 Subject: [PATCH 23/63] Using localised string collator to sort upload hierarchy. --- mobile-datovka.pro | 2 + .../localisation/localisation.cpp | 57 +++++++++++++++ .../localisation/localisation.h | 71 +++++++++++++++++++ .../models/upload_hierarchy_proxy_model.cpp | 4 +- src/main.cpp | 9 +-- src/settings.cpp | 8 +-- 6 files changed, 139 insertions(+), 12 deletions(-) create mode 100644 src/datovka_shared/localisation/localisation.cpp create mode 100644 src/datovka_shared/localisation/localisation.h diff --git a/mobile-datovka.pro b/mobile-datovka.pro index 3b7c421d..1828992c 100644 --- a/mobile-datovka.pro +++ b/mobile-datovka.pro @@ -93,6 +93,7 @@ SOURCES += \ src/datovka_shared/io/sqlite/db.cpp \ src/datovka_shared/io/sqlite/db_single.cpp \ src/datovka_shared/io/sqlite/table.cpp \ + src/datovka_shared/localisation/localisation.cpp \ src/datovka_shared/records_management/conversion.cpp \ src/datovka_shared/records_management/io/records_management_connection.cpp \ src/datovka_shared/records_management/json/entry_error.cpp \ @@ -176,6 +177,7 @@ HEADERS += \ src/datovka_shared/io/sqlite/db.h \ src/datovka_shared/io/sqlite/db_single.h \ src/datovka_shared/io/sqlite/table.h \ + src/datovka_shared/localisation/localisation.h \ src/datovka_shared/records_management/conversion.h \ src/datovka_shared/records_management/io/records_management_connection.h \ src/datovka_shared/records_management/json/entry_error.h \ diff --git a/src/datovka_shared/localisation/localisation.cpp b/src/datovka_shared/localisation/localisation.cpp new file mode 100644 index 00000000..ed02ddde --- /dev/null +++ b/src/datovka_shared/localisation/localisation.cpp @@ -0,0 +1,57 @@ +/* + * 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 "src/datovka_shared/localisation/localisation.h" + +QLocale Localisation::programLocale; +QCollator Localisation::stringCollator; + +const QString Localisation::langCs(QStringLiteral("cs")); +const QString Localisation::langEn(QStringLiteral("en")); +const QString Localisation::langSystem(QStringLiteral("system")); + +void Localisation::setProgramLocale(const QString &langCode) +{ + if (langCode == langCs) { + programLocale = QLocale(QLocale::Czech, QLocale::CzechRepublic); + } else if (langCode == langEn) { + programLocale = QLocale(QLocale::English, QLocale::UnitedKingdom); + } else { + /* Use system locale. */ + programLocale = QLocale::system(); + } + + stringCollator.setLocale(programLocale); +} + +QString Localisation::shortLangName(const QString &langCode) +{ + if (langCode == langCs) { + return langCs; + } else if (langCode == langEn) { + return langEn; + } else { + /* Use system locale. */ + return QLocale::system().name(); + } +} diff --git a/src/datovka_shared/localisation/localisation.h b/src/datovka_shared/localisation/localisation.h new file mode 100644 index 00000000..2b400b55 --- /dev/null +++ b/src/datovka_shared/localisation/localisation.h @@ -0,0 +1,71 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +/*! + * @brief Encapsulates localisation specific settings. + */ +class Localisation { + +private: + /*! + * @Brief Private constructor. + */ + Localisation(void); + +public: + /*! + * @brief Sets program locale according to supplied language code. + * + * @param[in] langCode Language code. + */ + static + void setProgramLocale(const QString &langCode); + + /*! + * @brief Returns short language code (eg. "cs", "en"). + * + * @param[in] langCode Language code. + * @return Short language ode or language code used by system if + * \a langCode is unknown. + */ + static + QString shortLangName(const QString &langCode); + + static + QLocale programLocale; /*!< Global locale instance. */ + static + QCollator stringCollator; /*!< Used for localised string collation. */ + + static + const QString langCs; /*!< Czech language code. */ + static + const QString langEn; /*!< English language code. */ + static + const QString langSystem; /*!< System-set language. */ +}; diff --git a/src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.cpp b/src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.cpp index 16fc6d99..8ac6d2c7 100644 --- a/src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.cpp +++ b/src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.cpp @@ -21,6 +21,7 @@ * the two. */ +#include "src/datovka_shared/localisation/localisation.h" #include "src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.h" UploadHierarchyProxyModel::UploadHierarchyProxyModel(QObject *parent) @@ -53,7 +54,8 @@ bool UploadHierarchyProxyModel::lessThan(const QModelIndex &sourceLeft, if (Q_LIKELY(leftData.canConvert())) { Q_ASSERT(rightData.canConvert()); - return (leftData.toString() < rightData.toString()); + return Localisation::stringCollator.compare(leftData.toString(), + rightData.toString()) < 0; } else { return QSortFilterProxyModel::lessThan(sourceLeft, sourceRight); } diff --git a/src/main.cpp b/src/main.cpp index e8f47dde..f75d3e2b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,6 +33,7 @@ #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" @@ -273,12 +274,8 @@ int main(int argc, char *argv[]) /* load datovka localization and qtbase localization */ QTranslator datovkaTrans; QTranslator qtbaseTrans; - QString lang = QLocale::system().name(); - if (globSet.language == "cs") { - lang = "cs"; - } else if (globSet.language == "en") { - lang = "en"; - } + QString lang(Localisation::shortLangName(globSet.language)); + Localisation::setProgramLocale(globSet.language); if (!datovkaTrans.load("datovka_" + lang, ":/locale/")) { qDebug() << "Could not load datovka localisation file..."; } diff --git a/src/settings.cpp b/src/settings.cpp index 88c2f226..d40f7aee 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -26,6 +26,7 @@ #include #include +#include "src/datovka_shared/localisation/localisation.h" #include "src/datovka_shared/settings/records_management.h" #include "src/io/filesystem.h" #include "src/log/log.h" @@ -34,9 +35,6 @@ /* Name of Datovka configuration file */ #define SETTINGS_FILE_NAME "datovka.conf" /* Supported languages. Default is system */ -#define LANGUAGE_SYSTEM "system" -#define LANGUAGE_CS "cs" -#define LANGUAGE_EN "en" /* Default value which define how long will have * attachment files stored in local database (in days)*/ #define DEFAULT_MSG_LIFETIME 90 @@ -69,7 +67,7 @@ Settings globSet; Settings::Settings(void) : PinSettings(), - language(LANGUAGE_SYSTEM), + language(Localisation::langSystem), fontSize(DEFAULT_FONT_SIZE), downloadOnlyNewMsgs(true), downloadCompleteMsgs(false), @@ -141,7 +139,7 @@ void Settings::loadFromSettings(const QSettings &settings) qDebug("%s()", __func__); language = settings.value(SETTINGS_GLOBAL_GROUP "/" SETTINGS_LANGUAGE, - LANGUAGE_SYSTEM).toString(); + Localisation::langSystem).toString(); fontSize = settings.value(SETTINGS_GLOBAL_GROUP "/" SETTINGS_FONTSIZE, DEFAULT_FONT_SIZE).toInt(); downloadOnlyNewMsgs = settings.value( -- GitLab From d08a80abf4257dfa6498ed28e05a93aa594cd821 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Fri, 2 Feb 2018 13:57:12 +0100 Subject: [PATCH 24/63] Removed UploadHierarchyModel. --- mobile-datovka.pro | 2 - .../models/upload_hierarchy_model.cpp | 237 ------------------ .../models/upload_hierarchy_model.h | 161 ------------ src/records_management.cpp | 2 - src/records_management.h | 4 - 5 files changed, 406 deletions(-) delete mode 100644 src/datovka_shared/records_management/models/upload_hierarchy_model.cpp delete mode 100644 src/datovka_shared/records_management/models/upload_hierarchy_model.h diff --git a/mobile-datovka.pro b/mobile-datovka.pro index 1828992c..6ef02c94 100644 --- a/mobile-datovka.pro +++ b/mobile-datovka.pro @@ -102,7 +102,6 @@ SOURCES += \ src/datovka_shared/records_management/json/stored_files.cpp \ src/datovka_shared/records_management/json/upload_file.cpp \ src/datovka_shared/records_management/json/upload_hierarchy.cpp \ - src/datovka_shared/records_management/models/upload_hierarchy_model.cpp \ src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.cpp \ src/datovka_shared/settings/pin.cpp \ src/datovka_shared/settings/records_management.cpp \ @@ -186,7 +185,6 @@ HEADERS += \ src/datovka_shared/records_management/json/stored_files.h \ src/datovka_shared/records_management/json/upload_file.h \ src/datovka_shared/records_management/json/upload_hierarchy.h \ - src/datovka_shared/records_management/models/upload_hierarchy_model.h \ src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.h \ src/datovka_shared/settings/pin.h \ src/datovka_shared/settings/records_management.h \ diff --git a/src/datovka_shared/records_management/models/upload_hierarchy_model.cpp b/src/datovka_shared/records_management/models/upload_hierarchy_model.cpp deleted file mode 100644 index c579dc28..00000000 --- a/src/datovka_shared/records_management/models/upload_hierarchy_model.cpp +++ /dev/null @@ -1,237 +0,0 @@ -/* - * 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 "src/datovka_shared/records_management/models/upload_hierarchy_model.h" - -UploadHierarchyModel::UploadHierarchyModel(QObject *parent) - : QAbstractItemModel(parent), - m_hierarchy() -{ -} - -QModelIndex UploadHierarchyModel::index(int row, int column, - const QModelIndex &parent) const -{ - if (!hasIndex(row, column, parent)) { - return QModelIndex(); - } - - quintptr internalId = 0; - - if (!parent.isValid()) { - if (showRootName()) { - /* Root is shown. */ - internalId = (quintptr)m_hierarchy.root(); - } else { - /* Root is not shown. */ - internalId = (quintptr)m_hierarchy.root()->sub().at(row); - } - } else { - const UploadHierarchyResp::NodeEntry *entry = - (UploadHierarchyResp::NodeEntry *)parent.internalId(); - internalId = (quintptr)entry->sub().at(row); - } - - return createIndex(row, column, internalId); -} - -QModelIndex UploadHierarchyModel::parent(const QModelIndex &index) const -{ - if (!index.isValid()) { - return QModelIndex(); - } - - const UploadHierarchyResp::NodeEntry *iEntry = - (UploadHierarchyResp::NodeEntry *)index.internalId(); - const UploadHierarchyResp::NodeEntry *pEntry = iEntry->super(); - - if ((pEntry != Q_NULLPTR) && - ((pEntry != m_hierarchy.root()) || showRootName())) { - if (pEntry == m_hierarchy.root()) { - /* Root node is shown and parent is root. */ - return createIndex(0, 0, (quintptr)pEntry); - } else { - const UploadHierarchyResp::NodeEntry *ppEntry = - pEntry->super(); - int row = 0; - /* Find position of parent. */ - for ( ; row < ppEntry->sub().size(); ++row) { - if (pEntry == ppEntry->sub().at(row)) { - break; - } - } - Q_ASSERT(row < ppEntry->sub().size()); - return createIndex(row, 0, (quintptr)pEntry); - } - } else { - return QModelIndex(); - } -} - -int UploadHierarchyModel::rowCount(const QModelIndex &parent) const -{ - if (parent.column() > 0) { - return 0; - } - - if (!parent.isValid()) { - if (!m_hierarchy.isValid()) { - /* Invalid hierarchy. */ - return 0; - } - - /* Root. */ - if (showRootName()) { - return 1; - } else { - return m_hierarchy.root()->sub().size(); - } - } else { - const UploadHierarchyResp::NodeEntry *entry = - (UploadHierarchyResp::NodeEntry *)parent.internalId(); - return entry->sub().size(); - } -} - -int UploadHierarchyModel::columnCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent); - - return 1; -} - -QVariant UploadHierarchyModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) { - return QVariant(); - } - - const UploadHierarchyResp::NodeEntry *entry = - (UploadHierarchyResp::NodeEntry *)index.internalId(); - if (entry == Q_NULLPTR) { - Q_ASSERT(0); - return QVariant(); - } - - switch (role) { - case Qt::DisplayRole: - return entry->name(); - break; - case Qt::ToolTipRole: - return filterData(entry).join(QStringLiteral("\n")); - break; - case Qt::AccessibleTextRole: - return entry->name(); - break; - case ROLE_FILTER: - return filterDataRecursive(entry, true); - break; - case ROLE_ID: - return entry->id(); - break; - default: - return QVariant(); - break; - } -} - -QVariant UploadHierarchyModel::headerData(int section, - Qt::Orientation orientation, int role) const -{ - if ((Qt::Horizontal == orientation) && (Qt::DisplayRole == role) && - (0 == section)) { - return tr("Records Management Hierarchy"); - } - - return QVariant(); -} - -Qt::ItemFlags UploadHierarchyModel::flags(const QModelIndex &index) const -{ - if (!index.isValid()) { - return Qt::NoItemFlags; - } - - Qt::ItemFlags flags = - QAbstractItemModel::flags(index) & ~Qt::ItemIsEditable; - - const UploadHierarchyResp::NodeEntry *entry = - (UploadHierarchyResp::NodeEntry *)index.internalId(); - if (entry->id().isEmpty()) { - flags &= ~Qt::ItemIsSelectable; - } - - return flags; -} - -void UploadHierarchyModel::setHierarchy(const UploadHierarchyResp &uhr) -{ - beginResetModel(); - m_hierarchy = uhr; - endResetModel(); -} - -bool UploadHierarchyModel::showRootName(void) const -{ - return m_hierarchy.isValid() && !m_hierarchy.root()->name().isEmpty(); -} - -QStringList UploadHierarchyModel::filterData( - const UploadHierarchyResp::NodeEntry *entry) -{ - if (Q_UNLIKELY(entry == Q_NULLPTR)) { - Q_ASSERT(0); - return QStringList(); - } - - return QStringList(entry->name()) + entry->metadata(); -} - -QStringList UploadHierarchyModel::filterDataRecursive( - const UploadHierarchyResp::NodeEntry *entry, bool takeSuper) -{ - if (Q_UNLIKELY(entry == Q_NULLPTR)) { - Q_ASSERT(0); - return QStringList(); - } - - QStringList res(filterData(entry)); - foreach (const UploadHierarchyResp::NodeEntry *sub, entry->sub()) { - res += filterDataRecursive(sub, false); - } - if (takeSuper) { - /* - * Add also filter data from superordinate node. This has the - * effect that all sub-nodes (including those not matching the - * filter) of a node which matches the entered filter are - * going to be also displayed. - */ - const UploadHierarchyResp::NodeEntry *sup = entry->super(); - if (Q_UNLIKELY(sup == Q_NULLPTR)) { - return res; - } - res += filterData(sup); - } - - return res; -} diff --git a/src/datovka_shared/records_management/models/upload_hierarchy_model.h b/src/datovka_shared/records_management/models/upload_hierarchy_model.h deleted file mode 100644 index f111368f..00000000 --- a/src/datovka_shared/records_management/models/upload_hierarchy_model.h +++ /dev/null @@ -1,161 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include - -#include "src/datovka_shared/records_management/json/upload_hierarchy.h" - -/*! - * @brief Upload hierarchy model. - */ -class UploadHierarchyModel : public QAbstractItemModel { - Q_OBJECT -public: - /*! - * @brief Custom roles. - */ - enum CustomRoles { - ROLE_FILTER = (Qt::UserRole + 1), /* Exposes metadata for filtering. */ - ROLE_ID /* Hierarchy entry identifier. */ - }; - - /*! - * @brief Constructor. - * - * @param[in] parent Pointer to parent object. - */ - explicit UploadHierarchyModel(QObject *parent = Q_NULLPTR); - - /*! - * @brief Return index specified by supplied parameters. - * - * @param[in] row Item row. - * @param[in] column Parent column. - * @param[in] parent Parent index. - * @return Index to desired element or invalid index on error. - */ - virtual - QModelIndex index(int row, int column, - const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; - - /*! - * @brief Return parent index of the item with the given index. - * - * @param[in] index Child node index. - * @return Index of the parent node or invalid index on error. - */ - virtual - QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE; - - /*! - * @brief Return number of rows under the given parent. - * - * @param[in] parent Parent node index. - * @return Number of rows. - */ - virtual - int rowCount(const QModelIndex &parent = QModelIndex()) const - Q_DECL_OVERRIDE; - - /*! - * @brief Return the number of columns for the children of given parent. - * - * @param[in] parent Parent node index. - * @return Number of columns. - */ - virtual - int columnCount(const QModelIndex &parent = QModelIndex()) const - Q_DECL_OVERRIDE; - - /*! - * @brief Return data stored in given location under given role. - * - * @param[in] index Index specifying the item. - * @param[in] role Data role. - * @return Data from model. - */ - virtual - QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; - - /*! - * @brief Returns header data in given location under given role. - * - * @brief[in] section Header position. - * @brief[in] orientation Header orientation. - * @brief[in] role Data role. - * @return Header data from model. - */ - virtual - QVariant headerData(int section, Qt::Orientation orientation, - int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; - - /*! - * @brief Returns item flags for given index. - * - * @brief[in] index Index specifying the item. - * @return Item flags. - */ - virtual - Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE; - - /*! - * @brief Set content data. - * - * @param[in] uhr Upload hierarchy response structure. - */ - void setHierarchy(const UploadHierarchyResp &uhr); - -private: - /*! - * @brief Check whether to show root name. - * - * @return True in root node contains a name. - */ - bool showRootName(void) const; - - /*! - * @brief Return all data related to node which can be used for - * filtering. - * - * @param[in] entry Node identifier. - * @return List of strings. - */ - static - QStringList filterData(const UploadHierarchyResp::NodeEntry *entry); - - /*! - * @brief Returns list of all (meta)data (including children). - * - * @param[in] entry Node identifying the root. - * @param[in] takeSuper Set true when data of superordinate node should - * be taken into account. - * @return List of all gathered data according to which can be filtered. - */ - static - QStringList filterDataRecursive( - const UploadHierarchyResp::NodeEntry *entry, bool takeSuper); - - UploadHierarchyResp m_hierarchy; /*!< Upload hierarchy structure. */ -}; diff --git a/src/records_management.cpp b/src/records_management.cpp index 79836fc7..6943fe40 100644 --- a/src/records_management.cpp +++ b/src/records_management.cpp @@ -92,8 +92,6 @@ bool processUploadFileResponse(const UploadFileResp &ufRes, qint64 dmId, RecordsManagement::RecordsManagement(QObject *parent) : QObject(parent), - m_uploadModel(), - m_uploadProxyModel(), m_rmc(RecordsManagementConnection::ignoreSslErrorsDflt, this), m_logoSvg() { diff --git a/src/records_management.h b/src/records_management.h index 3d396a01..a7078b01 100644 --- a/src/records_management.h +++ b/src/records_management.h @@ -26,8 +26,6 @@ #include #include "src/sqlite/message_db_container.h" -#include "src/datovka_shared/records_management/models/upload_hierarchy_model.h" -#include "src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.h" #include "src/datovka_shared/records_management/io/records_management_connection.h" #include "src/datovka_shared/settings/records_management.h" @@ -169,8 +167,6 @@ private: bool uploadFile(qint64 dmId, const QString &msgFileName, const QByteArray &msgData, const QStringList &uploadIds); - UploadHierarchyModel m_uploadModel; /*!< Upload hierarchy model. */ - UploadHierarchyProxyModel m_uploadProxyModel; /*!< Used for filtering. */ RecordsManagementConnection m_rmc; /*!< Connection to records management service. */ QByteArray m_logoSvg; /*!< Raw SVG data. */ }; -- GitLab From 6856c21d59b718661d7786a64a00a2228b7cf3e7 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Fri, 2 Feb 2018 15:02:41 +0100 Subject: [PATCH 25/63] Implemented upload hierarchy filtering. --- qml/pages/PageRecordsManagementUpload.qml | 48 ++++++++++++++++++- .../models/upload_hierarchy_proxy_model.cpp | 4 +- .../models/upload_hierarchy_list_model.cpp | 1 + .../models/upload_hierarchy_list_model.h | 2 + .../upload_hierarchy_qml_proxy_model.cpp | 11 +++++ .../models/upload_hierarchy_qml_proxy_model.h | 23 +++++++++ 6 files changed, 86 insertions(+), 3 deletions(-) diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index 54e73906..8a429bb1 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -65,6 +65,7 @@ Component { UploadHierarchyQmlProxyModel { id: uploadHierarchyProxyModel Component.onCompleted: { + setFilterRole(UploadHierarchyListModel.ROLE_FILTER_DATA_RECURSIVE) } } @@ -74,6 +75,25 @@ Component { onBackClicked: { pageView.pop(StackView.Immediate) } + Row { + anchors.verticalCenter: parent.verticalCenter + spacing: defaultMargin + anchors.right: parent.right + anchors.rightMargin: defaultMargin + AccessibleImageButton { + id: searchButton + anchors.verticalCenter: parent.verticalCenter + sourceSize.height: imgHeightHeader + source: "qrc:/ui/magnify.svg" + accessibleName: qsTr("Filter upload hierarchy.") + onClicked: { + uploadHierarchyList.anchors.top = filterBar.bottom + filterBar.visible = true + filterBar.filterField.forceActiveFocus() + Qt.inputMethod.show() + } + } + } } AccessibleText { @@ -134,12 +154,38 @@ Component { text: "/" } } + FilterBar { + id: filterBar + visible: false + z: 1 + anchors.top: navigateUpRow.bottom + width: parent.width + height: visible ? filterField.height : 0 + color: (filterField.text.length == 0) ? datovkaPalette.alternateBase : + (uploadHierarchyList.count > 0) ? "#afffaf" : "#ffafaf" + border.color: filterField.activeFocus ? "#0066ff" : "#bdbebf" + + placeholderText: qsTr("Set filter") + fontPointSize: defaultTextFont.font.pointSize + buttonImageHeight: imgHeight + buttonAccessibleName: qsTr("Clear and hide filter field") + + onTextChanged: { + uploadHierarchyProxyModel.setFilterRegExpStr(text) + } + + onClearClicked: { + filterBar.visible = false + uploadHierarchyList.anchors.top = navigateUpRow.bottom + Qt.inputMethod.hide() + } + } ScrollableListView { id: uploadHierarchyList delegateHeight: headerHeight - anchors.top: navigateUpRow.bottom + anchors.top: filterBar.bottom anchors.bottom: bottomText.top /* TODO */ width: parent.width diff --git a/src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.cpp b/src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.cpp index 8ac6d2c7..147cf552 100644 --- a/src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.cpp +++ b/src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.cpp @@ -49,8 +49,8 @@ bool UploadHierarchyProxyModel::filterAcceptsRow(int sourceRow, bool UploadHierarchyProxyModel::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const { - QVariant leftData(sourceModel()->data(sourceLeft, filterRole())); - QVariant rightData(sourceModel()->data(sourceRight, filterRole())); + QVariant leftData(sourceModel()->data(sourceLeft, sortRole())); + QVariant rightData(sourceModel()->data(sourceRight, sortRole())); if (Q_LIKELY(leftData.canConvert())) { Q_ASSERT(rightData.canConvert()); diff --git a/src/records_management/models/upload_hierarchy_list_model.cpp b/src/records_management/models/upload_hierarchy_list_model.cpp index 10ad9b40..638df643 100644 --- a/src/records_management/models/upload_hierarchy_list_model.cpp +++ b/src/records_management/models/upload_hierarchy_list_model.cpp @@ -29,6 +29,7 @@ void UploadHierarchyListModel::declareQML(void) { qmlRegisterType("cz.nic.mobileDatovka.models", 1, 0, "UploadHierarchyListModel"); qRegisterMetaType(); + qRegisterMetaType(); } UploadHierarchyListModel::UploadHierarchyListModel(QObject *parent) diff --git a/src/records_management/models/upload_hierarchy_list_model.h b/src/records_management/models/upload_hierarchy_list_model.h index 669c8533..b4c73eda 100644 --- a/src/records_management/models/upload_hierarchy_list_model.h +++ b/src/records_management/models/upload_hierarchy_list_model.h @@ -45,6 +45,7 @@ public: ROLE_LEAF, ROLE_SELECTABLE }; + Q_ENUM(Roles) /* Don't forget to declare various properties to the QML system. */ static @@ -211,3 +212,4 @@ private: /* QML passes its arguments via QVariant. */ Q_DECLARE_METATYPE(UploadHierarchyListModel) +Q_DECLARE_METATYPE(UploadHierarchyListModel::Roles) diff --git a/src/records_management/models/upload_hierarchy_qml_proxy_model.cpp b/src/records_management/models/upload_hierarchy_qml_proxy_model.cpp index 3d633637..275569a4 100644 --- a/src/records_management/models/upload_hierarchy_qml_proxy_model.cpp +++ b/src/records_management/models/upload_hierarchy_qml_proxy_model.cpp @@ -54,7 +54,18 @@ int UploadHierarchyQmlProxyModel::mapToSource(int proxyRow) const return UploadHierarchyProxyModel::mapToSource(proxyIndex).row(); } +void UploadHierarchyQmlProxyModel::setFilterRole(int role) +{ + UploadHierarchyProxyModel::setFilterRole(role); +} + void UploadHierarchyQmlProxyModel::sort(void) { UploadHierarchyProxyModel::sort(0, Qt::AscendingOrder); } + +void UploadHierarchyQmlProxyModel::setFilterRegExpStr(const QString &patternCore) +{ + setFilterRegExp(QRegExp(patternCore, + Qt::CaseInsensitive, QRegExp::FixedString)); +} diff --git a/src/records_management/models/upload_hierarchy_qml_proxy_model.h b/src/records_management/models/upload_hierarchy_qml_proxy_model.h index da6502d0..fb9759d9 100644 --- a/src/records_management/models/upload_hierarchy_qml_proxy_model.h +++ b/src/records_management/models/upload_hierarchy_qml_proxy_model.h @@ -65,14 +65,37 @@ public: Q_INVOKABLE virtual void setSourceModel(QAbstractItemModel *sourceModel) Q_DECL_OVERRIDE; + /*! + * @brief Map proxy model row onto source model row. + * + * @param[in] proxyRow Proxy model row. + * @return Corresponding source model row. + */ Q_INVOKABLE int mapToSource(int proxyRow) const; + /*! + * @brief Set the role where the key is used for filtering. + * + * @param[in] role Role number, -1 for all roles. + */ + Q_INVOKABLE + void setFilterRole(int role); + /*! * @brief Sorts the model content. */ Q_INVOKABLE void sort(void); + +public slots: + /*! + * @brief Sets regular expression according to expression core. + * + * @param[in] patternCore Regular expression core. + */ + Q_INVOKABLE + void setFilterRegExpStr(const QString &patternCore); }; /* QML passes its arguments via QVariant. */ -- GitLab From 887aa571ad18e7786370369854dda35f855a77f6 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Fri, 2 Feb 2018 15:15:22 +0100 Subject: [PATCH 26/63] Displaying notification about empty upload hierarchy model. --- qml/pages/PageRecordsManagementUpload.qml | 150 ++++++++++++---------- 1 file changed, 79 insertions(+), 71 deletions(-) diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index 8a429bb1..93ccc0f4 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -87,7 +87,6 @@ Component { source: "qrc:/ui/magnify.svg" accessibleName: qsTr("Filter upload hierarchy.") onClicked: { - uploadHierarchyList.anchors.top = filterBar.bottom filterBar.visible = true filterBar.filterField.forceActiveFocus() Qt.inputMethod.show() @@ -154,88 +153,97 @@ Component { text: "/" } } - FilterBar { - id: filterBar - visible: false - z: 1 + Rectangle { anchors.top: navigateUpRow.bottom + anchors.bottom: parent.bottom width: parent.width - height: visible ? filterField.height : 0 - color: (filterField.text.length == 0) ? datovkaPalette.alternateBase : - (uploadHierarchyList.count > 0) ? "#afffaf" : "#ffafaf" - border.color: filterField.activeFocus ? "#0066ff" : "#bdbebf" - - placeholderText: qsTr("Set filter") - fontPointSize: defaultTextFont.font.pointSize - buttonImageHeight: imgHeight - buttonAccessibleName: qsTr("Clear and hide filter field") - - onTextChanged: { - uploadHierarchyProxyModel.setFilterRegExpStr(text) - } - - onClearClicked: { - filterBar.visible = false - uploadHierarchyList.anchors.top = navigateUpRow.bottom - Qt.inputMethod.hide() - } - } - ScrollableListView { - id: uploadHierarchyList - - delegateHeight: headerHeight - - anchors.top: filterBar.bottom - anchors.bottom: bottomText.top - /* TODO */ - width: parent.width - model: uploadHierarchyProxyModel - - delegate: Rectangle { - id: uploadItem + color: datovkaPalette.window + FilterBar { + id: filterBar + visible: false + z: 1 + anchors.top: parent.top width: parent.width - height: uploadHierarchyList.delegateHeight - color: datovkaPalette.base - - Text { - id: uploadText - anchors.fill: parent - text: rName - color: datovkaPalette.mid - font.pointSize: textFontSizeSmall - renderType: Text.NativeRendering + height: visible ? filterField.height : 0 + color: (filterField.text.length == 0) ? datovkaPalette.alternateBase : + (uploadHierarchyList.count > 0) ? "#afffaf" : "#ffafaf" + border.color: filterField.activeFocus ? "#0066ff" : "#bdbebf" + + placeholderText: qsTr("Set filter") + fontPointSize: defaultTextFont.font.pointSize + buttonImageHeight: imgHeight + buttonAccessibleName: qsTr("Clear and hide filter field") + + onTextChanged: { + uploadHierarchyProxyModel.setFilterRegExpStr(text) } - MouseArea { - function handleClick() { - uploadHierarchyListModel.navigateSub( - uploadHierarchyProxyModel.mapToSource(index)); - } + onClearClicked: { + filterBar.visible = false + uploadHierarchyList.anchors.top = navigateUpRow.bottom + Qt.inputMethod.hide() + } + } + AccessibleText { + id: emptyHierarchy + visible: uploadHierarchyList.count <= 0 + color: datovkaPalette.text + anchors.centerIn: parent + width: parent.width + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.Wrap + text: (filterBar.filterField.text.length == 0) ? + qsTr("Upload hierarchy has not been downloaded yet.") : + qsTr("No hierarchy entry found that matches filter text '%1'.").arg(filterBar.filterField.text) + } + ScrollableListView { + id: uploadHierarchyList - anchors.fill: parent + delegateHeight: headerHeight - Accessible.role: Accessible.Button - Accessible.name: qsTr("Open attachment '%1'.").arg(rName) - Accessible.onScrollDownAction: uploadHierarchyList.scrollDown() - Accessible.onScrollUpAction: uploadHierarchyList.scrollUp() - Accessible.onPressAction: { - handleClick() + anchors.top: filterBar.visible ? filterBar.bottom : parent.top + anchors.bottom: parent.bottom + width: parent.width + model: uploadHierarchyProxyModel + + delegate: Rectangle { + id: uploadItem + + width: parent.width + height: uploadHierarchyList.delegateHeight + color: datovkaPalette.base + + Text { + id: uploadText + anchors.fill: parent + text: rName + color: datovkaPalette.mid + font.pointSize: textFontSizeSmall + renderType: Text.NativeRendering } - onClicked: { - handleClick() + + MouseArea { + function handleClick() { + uploadHierarchyListModel.navigateSub( + uploadHierarchyProxyModel.mapToSource(index)); + } + + anchors.fill: parent + + Accessible.role: Accessible.Button + Accessible.name: qsTr("Open attachment '%1'.").arg(rName) + Accessible.onScrollDownAction: uploadHierarchyList.scrollDown() + Accessible.onScrollUpAction: uploadHierarchyList.scrollUp() + Accessible.onPressAction: { + handleClick() + } + onClicked: { + handleClick() + } } } } } - AccessibleText { - id: bottomText - anchors.bottom: parent.bottom - color: datovkaPalette.mid - wrapMode: Text.Wrap - horizontalAlignment: Text.AlignHCenter - width: parent.width - text: "Test text" - } } } -- GitLab From bac9d6c80737b6bff6626ab6f89c916285202760 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Fri, 2 Feb 2018 15:25:58 +0100 Subject: [PATCH 27/63] Updated upload hierarchy view delegate. --- qml/pages/PageRecordsManagementUpload.qml | 34 ++++++++++++++++++----- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index 93ccc0f4..33303a89 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -210,17 +210,37 @@ Component { delegate: Rectangle { id: uploadItem - width: parent.width - height: uploadHierarchyList.delegateHeight color: datovkaPalette.base + height: uploadHierarchyList.delegateHeight + width: parent.width Text { - id: uploadText - anchors.fill: parent + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: defaultMargin + color: datovkaPalette.text text: rName - color: datovkaPalette.mid - font.pointSize: textFontSizeSmall - renderType: Text.NativeRendering + } + Rectangle { + visible: !rLeaf + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + height: parent.height + width: parent.width * 0.07 + color: parent.color + Image { + id: nextImage + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: defaultMargin + sourceSize.height: navImgHeight + source: "qrc:/ui/next.svg" + } + ColorOverlay { + anchors.fill: nextImage + source: nextImage + color: datovkaPalette.text + } } MouseArea { -- GitLab From 2722abec6f317e52a45b3fed925235fcceb4dd0c Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Fri, 2 Feb 2018 15:44:19 +0100 Subject: [PATCH 28/63] Added timer to download upload hierarchy. --- qml/pages/PageRecordsManagementUpload.qml | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index 33303a89..56d91118 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -21,10 +21,10 @@ * the two. */ +import QtGraphicalEffects 1.0 +import QtQml 2.2 import QtQuick 2.7 -import QtQuick.Controls 1.4 import QtQuick.Controls 2.1 -import QtGraphicalEffects 1.0 import cz.nic.mobileDatovka 1.0 import cz.nic.mobileDatovka.models 1.0 @@ -46,12 +46,29 @@ Component { Component.onCompleted: { uploadHierarchyProxyModel.setSourceModel(uploadHierarchyListModel) uploadHierarchyProxyModel.sort() + timer.start() } Component.onDestruction: { statusBar.visible = false } + function downloadUploadHierarchy() { + hierarchyButton.enabled = false + recordsManagement.callUploadHierarchy(uploadHierarchyListModel) + hierarchyButton.enabled = true + } + + Timer { + id: timer + interval: 500 // milliseconds + running: false + repeat: false + onTriggered: { + downloadUploadHierarchy() + } + } + UploadHierarchyListModel { id: uploadHierarchyListModel onModelReset: { @@ -115,7 +132,7 @@ Component { font.pointSize: defaultTextFont.font.pointSize text: qsTr("Update upload hierarchy") onClicked: { - recordsManagement.callUploadHierarchy(uploadHierarchyListModel) + downloadUploadHierarchy() } } AccessibleButton { -- GitLab From 6b57b8fccaac8b592a017bc928e00d319db079b4 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Fri, 2 Feb 2018 17:09:23 +0100 Subject: [PATCH 29/63] Added selection support into UploadHierarchyListModel. --- qml/pages/PageRecordsManagementUpload.qml | 11 ++- .../models/upload_hierarchy_list_model.cpp | 87 +++++++++++++++++-- .../models/upload_hierarchy_list_model.h | 38 +++++++- 3 files changed, 127 insertions(+), 9 deletions(-) diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index 56d91118..969951e7 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -227,7 +227,7 @@ Component { delegate: Rectangle { id: uploadItem - color: datovkaPalette.base + color: (!rSelected) ? datovkaPalette.base : readBgColor height: uploadHierarchyList.delegateHeight width: parent.width @@ -262,8 +262,13 @@ Component { MouseArea { function handleClick() { - uploadHierarchyListModel.navigateSub( - uploadHierarchyProxyModel.mapToSource(index)); + if (!rLeaf) { + uploadHierarchyListModel.navigateSub( + uploadHierarchyProxyModel.mapToSource(index)); + } else if (rSelectable) { + uploadHierarchyListModel.toggleNodeSelection( + uploadHierarchyProxyModel.mapToSource(index)); + } } anchors.fill: parent diff --git a/src/records_management/models/upload_hierarchy_list_model.cpp b/src/records_management/models/upload_hierarchy_list_model.cpp index 638df643..b7d9d238 100644 --- a/src/records_management/models/upload_hierarchy_list_model.cpp +++ b/src/records_management/models/upload_hierarchy_list_model.cpp @@ -35,7 +35,8 @@ void UploadHierarchyListModel::declareQML(void) UploadHierarchyListModel::UploadHierarchyListModel(QObject *parent) : QAbstractListModel(parent), m_hierarchy(), - m_workingRoot(Q_NULLPTR) + m_workingRoot(Q_NULLPTR), + m_selectedIds() { } @@ -43,7 +44,8 @@ UploadHierarchyListModel::UploadHierarchyListModel( const UploadHierarchyListModel &model, QObject *parent) : QAbstractListModel(parent), m_hierarchy(model.m_hierarchy), - m_workingRoot(m_hierarchy.root()) + m_workingRoot(m_hierarchy.root()), + m_selectedIds() { } @@ -81,13 +83,17 @@ QModelIndex UploadHierarchyListModel::parent(const QModelIndex &index) const const UploadHierarchyResp::NodeEntry *iEntry = (UploadHierarchyResp::NodeEntry *)index.internalId(); + if (Q_UNLIKELY(iEntry == Q_NULLPTR)) { + Q_ASSERT(0); + return QModelIndex(); + } const UploadHierarchyResp::NodeEntry *pEntry = iEntry->super(); if ((pEntry != Q_NULLPTR) && ((pEntry != m_workingRoot) || showRootName())) { if (pEntry == m_workingRoot) { /* Root node is shown and parent is root. */ - return createIndex(0, 0, (quintptr)pEntry); + return QModelIndex(); } else { const UploadHierarchyResp::NodeEntry *ppEntry = pEntry->super(); @@ -138,10 +144,16 @@ QHash UploadHierarchyListModel::roleNames(void) const roles[ROLE_ID] = "rId"; roles[ROLE_LEAF] = "rLeaf"; roles[ROLE_SELECTABLE] = "rSelectable"; + roles[ROLE_SELECTED] = "rSelected"; + roles[ROLE_SELECTED_RECURSIVE] = "rSelectedRecursive"; } return roles; } +/* Return true if entry is selectable. */ +#define isSelectable(entry) \ + (!(entry)->id().isEmpty()) + QVariant UploadHierarchyListModel::data(const QModelIndex &index, int role) const { @@ -176,7 +188,13 @@ QVariant UploadHierarchyListModel::data(const QModelIndex &index, return entry->sub().size() <= 0; break; case ROLE_SELECTABLE: - return !entry->id().isEmpty(); + return isSelectable(entry); + break; + case ROLE_SELECTED: + return idInSet(entry, m_selectedIds, false); + break; + case ROLE_SELECTED_RECURSIVE: + return idInSet(entry, m_selectedIds, true); break; default: return QVariant(); @@ -199,7 +217,7 @@ Qt::ItemFlags UploadHierarchyListModel::flags(const QModelIndex &index) const Q_ASSERT(0); return Qt::NoItemFlags; } - if (entry->id().isEmpty()) { + if (!isSelectable(entry)) { flags &= ~Qt::ItemIsSelectable; } @@ -268,6 +286,39 @@ QString UploadHierarchyListModel::navigatedRootName(bool takeSuper, return rootName; } +bool UploadHierarchyListModel::toggleNodeSelection(int row) +{ + if (Q_UNLIKELY(row < 0) || (row >= rowCount())) { + Q_ASSERT(0); + return false; + } + + if (m_workingRoot != Q_NULLPTR) { + const UploadHierarchyResp::NodeEntry *sub = + m_workingRoot->sub().at(row); + if ((sub != Q_NULLPTR) && isSelectable(sub)) { + if (idInSet(sub, m_selectedIds, false)) { + m_selectedIds.remove(sub->id()); + } else { + m_selectedIds.insert(sub->id()); + } + QModelIndex changedIdx(index(row, 0, QModelIndex())); + QVector roles(2); + roles[0] = ROLE_SELECTED; + roles[1] = ROLE_SELECTED_RECURSIVE; + emit dataChanged(changedIdx, changedIdx); + return true; + } + } + + return false; +} + +QStringList UploadHierarchyListModel::selectedIds(void) const +{ + return m_selectedIds.toList(); +} + UploadHierarchyListModel *UploadHierarchyListModel::fromVariant( const QVariant &modelVariant) { @@ -322,3 +373,29 @@ QStringList UploadHierarchyListModel::filterDataRecursive( return res; } + +bool UploadHierarchyListModel::idInSet( + const UploadHierarchyResp::NodeEntry *entry, const QSet &idSet, + bool takeSub) +{ + if (Q_UNLIKELY(entry == Q_NULLPTR)) { + Q_ASSERT(0); + return false; + } + + int present = false; + const QString &id(entry->id()); + if (!id.isEmpty()) { + present = idSet.contains(id); + } + + if (takeSub) { + int i = 0; + while ((!present) && (i < entry->sub().size())) { + present = idInSet(entry->sub().at(i), idSet, true); + ++i; + } + } + + return present; +} diff --git a/src/records_management/models/upload_hierarchy_list_model.h b/src/records_management/models/upload_hierarchy_list_model.h index b4c73eda..c04f5e14 100644 --- a/src/records_management/models/upload_hierarchy_list_model.h +++ b/src/records_management/models/upload_hierarchy_list_model.h @@ -24,6 +24,8 @@ #pragma once #include +#include +#include #include "src/datovka_shared/records_management/json/upload_hierarchy.h" @@ -43,7 +45,9 @@ public: ROLE_FILTER_DATA_RECURSIVE, ROLE_ID, ROLE_LEAF, - ROLE_SELECTABLE + ROLE_SELECTABLE, + ROLE_SELECTED, /* The actual node is selected. */ + ROLE_SELECTED_RECURSIVE /* Actual node or any sub-node is selected. */ }; Q_ENUM(Roles) @@ -162,6 +166,23 @@ public: Q_INVOKABLE QString navigatedRootName(bool takeSuper, const QString &sep) const; + /*! + * @brief Toggles the node selection. + * + * @param[in] row Sub-node index to be used. + * @return True if selection was toggled. + */ + Q_INVOKABLE + bool toggleNodeSelection(int row); + + /*! + * @brief return unsorted list of selected node identifiers. + * + * @return List of node identifiers. + */ + Q_INVOKABLE + QStringList selectedIds(void) const; + /*! * @brief Converts QVariant obtained from QML into model pointer. * @@ -206,8 +227,23 @@ private: QStringList filterDataRecursive( const UploadHierarchyResp::NodeEntry *entry, bool takeSuper); + /*! + * @brief Check whether identifier of node is in set. + * + * @param[in] entry Node entry. + * @param[in] idSet Identifier set to search in. + * @param[in] takeSub Whether sub-nodes are to be taken into + * consideration. + * @return true if node identifier is in set or any of its sub-nodes if + * takeSub enabled. + */ + static + bool idInSet(const UploadHierarchyResp::NodeEntry *entry, + const QSet &idSet, bool takeSub); + UploadHierarchyResp m_hierarchy; /*!< Upload hierarchy structure. */ const UploadHierarchyResp::NodeEntry *m_workingRoot; /*!< Node acting as working root. */ + QSet m_selectedIds; /*!< List of selected identifiers. */ }; /* QML passes its arguments via QVariant. */ -- GitLab From 7ab90bca5cd8df5872853f70b8b79550044bc2a2 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Fri, 2 Feb 2018 17:17:26 +0100 Subject: [PATCH 30/63] Indicating selection of some subordinate node in upload hierarchy. --- qml/pages/PageRecordsManagementUpload.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index 969951e7..ccdb250e 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -235,7 +235,8 @@ Component { anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: defaultMargin - color: datovkaPalette.text + /* Use lighter text if some sub-nodes are selected but not this actual node. */ + color: (rSelected || (!rSelectedRecursive)) ? datovkaPalette.text : datovkaPalette.mid text: rName } Rectangle { -- GitLab From e49ba1c2ac2fb94af0309c84df40c4c67c704ecf Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Tue, 6 Feb 2018 09:40:05 +0100 Subject: [PATCH 31/63] Fixed wrong filter anchor --- qml/pages/PageRecordsManagementUpload.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index ccdb250e..9f2c3c64 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -105,6 +105,7 @@ Component { accessibleName: qsTr("Filter upload hierarchy.") onClicked: { filterBar.visible = true + uploadHierarchyList.anchors.top = filterBar.bottom filterBar.filterField.forceActiveFocus() Qt.inputMethod.show() } @@ -198,7 +199,7 @@ Component { onClearClicked: { filterBar.visible = false - uploadHierarchyList.anchors.top = navigateUpRow.bottom + uploadHierarchyList.anchors.top = parent.top Qt.inputMethod.hide() } } @@ -219,7 +220,7 @@ Component { delegateHeight: headerHeight - anchors.top: filterBar.visible ? filterBar.bottom : parent.top + anchors.top: parent.top anchors.bottom: parent.bottom width: parent.width model: uploadHierarchyProxyModel -- GitLab From d54fe9c3a41d6e12b132f479a08429c63a714c77 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Tue, 6 Feb 2018 10:01:19 +0100 Subject: [PATCH 32/63] Send hierarchy model to upload message method --- qml/pages/PageRecordsManagementUpload.qml | 6 ++---- src/records_management.cpp | 16 ++++++++++++---- src/records_management.h | 7 +++++-- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index 9f2c3c64..11271a60 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -40,8 +40,6 @@ Component { property string userName property int msgType property string msgId - /* Holsd selected folder ids */ - property var uploadIds: [] Component.onCompleted: { uploadHierarchyProxyModel.setSourceModel(uploadHierarchyListModel) @@ -120,7 +118,7 @@ Component { wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter width: parent.width - text: qsTr("Here you can upload message into chosen folder in the records management. Update folder hierarchy and select target folder.") + text: qsTr("Here you can upload message into chosen folders in the records management. Update folder hierarchy and select target folders.") } Row { id: buttonRow @@ -142,7 +140,7 @@ Component { font.pointSize: defaultTextFont.font.pointSize text: qsTr("Upload message") onClicked: { - recordsManagement.uploadMessage(userName, msgId, msgType, uploadIds) + recordsManagement.uploadMessage(userName, msgId, msgType, uploadHierarchyListModel) } } } diff --git a/src/records_management.cpp b/src/records_management.cpp index 6943fe40..9f0b51b3 100644 --- a/src/records_management.cpp +++ b/src/records_management.cpp @@ -151,8 +151,6 @@ void RecordsManagement::callUploadHierarchy( return; } - emit statusBarTextChanged(tr("Upload hierarchy"), true, true); - UploadHierarchyListModel *hierarchyModel = UploadHierarchyListModel::fromVariant(hirerachyModelVariant); if (hierarchyModel == Q_NULLPTR) { @@ -161,6 +159,8 @@ void RecordsManagement::callUploadHierarchy( return; } + emit statusBarTextChanged(tr("Upload hierarchy"), true, true); + /* Clear model. */ hierarchyModel->setHierarchy(UploadHierarchyResp()); @@ -266,7 +266,7 @@ void RecordsManagement::loadStoredServiceInfo(void) bool RecordsManagement::uploadMessage(const QString &userName, const QString &dmId, enum Messages::MessageType messageType, - const QStringList &uploadIds) + const QVariant &hirerachyModelVariant) { bool ok = false; qint64 msgId = dmId.toLongLong(&ok); @@ -297,10 +297,18 @@ bool RecordsManagement::uploadMessage(const QString &userName, QString msgFileName = QString("%1_%2.zfo").arg(msgType).arg(dmId); + UploadHierarchyListModel *hierarchyModel = + UploadHierarchyListModel::fromVariant(hirerachyModelVariant); + if (hierarchyModel == Q_NULLPTR) { + Q_ASSERT(0); + qCritical("%s", "Cannot access upload hierarchy model."); + return false; + } + /* TODO - removed this return */ return true; - return uploadFile(msgId, msgFileName, msgData, uploadIds); + return uploadFile(msgId, msgFileName, msgData, hierarchyModel->selectedIds()); } bool RecordsManagement::updateServiceInfo(const QString &newUrlStr, diff --git a/src/records_management.h b/src/records_management.h index a7078b01..5aba7dde 100644 --- a/src/records_management.h +++ b/src/records_management.h @@ -57,6 +57,8 @@ public: /*! * @brief Download upload hierarchy and set model. + * + * @param[in] hirerachyModelVariant Model for hierarchy update. */ Q_INVOKABLE void callUploadHierarchy(const QVariant &hirerachyModelVariant); @@ -91,12 +93,13 @@ public: * @param[in] userName Account user name identifier. * @param[in] dmId Message identifier. * @param[in] messageType Message orientation. - * @param[in] uploadIds List of records management location ids. + * @param[in] hirerachyModelVariant Model for hierarchy selection. * @return True when data have been updated, false else. */ Q_INVOKABLE bool uploadMessage(const QString &userName, const QString &dmId, - enum Messages::MessageType messageType, const QStringList &uploadIds); + enum Messages::MessageType messageType, + const QVariant &hirerachyModelVariant); /*! * @brief Update record management settings. -- GitLab From 7178e6f1aef1d596405670bf46edd34c93d32678 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Tue, 6 Feb 2018 10:41:51 +0100 Subject: [PATCH 33/63] Enable upload button if target folder was selected --- qml/pages/PageRecordsManagementUpload.qml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index 11271a60..af48a0fe 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -135,7 +135,8 @@ Component { } } AccessibleButton { - id: uplaodButton + id: uploadButton + visible: false height: inputItemHeight font.pointSize: defaultTextFont.font.pointSize text: qsTr("Upload message") @@ -264,10 +265,12 @@ Component { function handleClick() { if (!rLeaf) { uploadHierarchyListModel.navigateSub( - uploadHierarchyProxyModel.mapToSource(index)); + uploadHierarchyProxyModel.mapToSource(index)) } else if (rSelectable) { uploadHierarchyListModel.toggleNodeSelection( - uploadHierarchyProxyModel.mapToSource(index)); + uploadHierarchyProxyModel.mapToSource(index)) + uploadButton.visible = (uploadHierarchyListModel.selectedIds().length > 0) + hierarchyButton.visible = !uploadButton.visible } } -- GitLab From 22aa86836269eef1eaad918f76f1537da4885403 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Tue, 6 Feb 2018 12:32:49 +0100 Subject: [PATCH 34/63] Update message model after upload to records management --- qml/pages/PageMenuMessageDetail.qml | 3 ++- qml/pages/PageRecordsManagementUpload.qml | 6 +++++- src/messages.cpp | 14 ++++++++++++++ src/messages.h | 12 ++++++++++++ src/models/messagemodel.cpp | 18 ++++++++++++++++++ src/models/messagemodel.h | 12 ++++++++++++ src/records_management.cpp | 3 --- 7 files changed, 63 insertions(+), 5 deletions(-) diff --git a/qml/pages/PageMenuMessageDetail.qml b/qml/pages/PageMenuMessageDetail.qml index 05a2fbaa..b282d58e 100644 --- a/qml/pages/PageMenuMessageDetail.qml +++ b/qml/pages/PageMenuMessageDetail.qml @@ -115,7 +115,8 @@ Component { "acntName" : acntName, "userName": userName, "msgId": msgId, - "msgType": msgType + "msgType": msgType, + "messageModel": messageModel }, StackView.Immediate) }, "sendEmail": function callSendEmail() { diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index af48a0fe..b1b4b6ad 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -40,6 +40,7 @@ Component { property string userName property int msgType property string msgId + property var messageModel: null Component.onCompleted: { uploadHierarchyProxyModel.setSourceModel(uploadHierarchyListModel) @@ -141,7 +142,10 @@ Component { font.pointSize: defaultTextFont.font.pointSize text: qsTr("Upload message") onClicked: { - recordsManagement.uploadMessage(userName, msgId, msgType, uploadHierarchyListModel) + if (recordsManagement.uploadMessage(userName, msgId, msgType, uploadHierarchyListModel)) { + messages.updateRmStatus(messageModel, msgId, true) + pageView.pop(StackView.Immediate) + } } } } diff --git a/src/messages.cpp b/src/messages.cpp index 20d7fede..3aa274c4 100644 --- a/src/messages.cpp +++ b/src/messages.cpp @@ -234,6 +234,20 @@ void Messages::markMessagesAsLocallyRead(const QVariant &msgModelVariant, messageModel->overrideReadAll(isRead); } +void Messages::updateRmStatus(const QVariant &msgModelVariant, qint64 dmId, + bool isUploadRm) +{ + qDebug("%s()", __func__); + + MessageListModel *messageModel = + MessageListModel::fromVariant(msgModelVariant); + if (messageModel == Q_NULLPTR) { + qWarning("%s", "Cannot access message model."); + return; + } + messageModel->updateRmStatus(dmId, isUploadRm); +} + void Messages::deleteMessageFromDbs(const QVariant &acntModelVariant, const QVariant &msgModelVariant, const QString &userName, qint64 msgId) { diff --git a/src/messages.h b/src/messages.h index d5f79195..95ced3ee 100644 --- a/src/messages.h +++ b/src/messages.h @@ -130,6 +130,18 @@ public: void markMessagesAsLocallyRead(const QVariant &msgModelVariant, const QString &userName, enum MessageType msgType, bool isRead); + /*! + * @brief Update records management icon after message upload. + * + * @param[in,out] msgModelVariant QVariant holding message model to be set. + * @param[in] dmId Message id. + * @param[in] isUploadRm Set whether to force records management + * upload state. + */ + Q_INVOKABLE static + void updateRmStatus(const QVariant &msgModelVariant, qint64 dmId, + bool isUploadRm); + /*! * @brief Delete selected message from databases. * diff --git a/src/models/messagemodel.cpp b/src/models/messagemodel.cpp index 07fad2e7..e06267fe 100644 --- a/src/models/messagemodel.cpp +++ b/src/models/messagemodel.cpp @@ -417,3 +417,21 @@ MessageListModel *MessageListModel::fromVariant(const QVariant &modelVariant) QObject *obj = qvariant_cast(modelVariant); return qobject_cast(obj); } + +bool MessageListModel::updateRmStatus(qint64 dmId, bool isUploadRm) +{ + QModelIndex msgIdx(messageIndex(dmId)); + + if (!msgIdx.isValid()) { + return false; + } + + int row = msgIdx.row(); + Q_ASSERT((row >= 0) && (row < m_messages.size())); + + m_messages[row].setSecordsManagement(isUploadRm); + + emit dataChanged(msgIdx, msgIdx); + + return true; +} diff --git a/src/models/messagemodel.h b/src/models/messagemodel.h index 79fb15cb..ced26e1d 100644 --- a/src/models/messagemodel.h +++ b/src/models/messagemodel.h @@ -237,6 +237,18 @@ public: static MessageListModel *fromVariant(const QVariant &modelVariant); + /*! + * @brief Update records management icon after message upload. + * + * @note Emits dataChanged signal. + * + * @param[in] dmId Message id. + * @param[in] isUploadRm Set whether to force records management + * upload state. + * @return True on success. + */ + bool updateRmStatus(qint64 dmId, bool isUploadRm); + private: QList m_messages; /*!< List of messages stored. */ diff --git a/src/records_management.cpp b/src/records_management.cpp index 9f0b51b3..58df39c5 100644 --- a/src/records_management.cpp +++ b/src/records_management.cpp @@ -305,9 +305,6 @@ bool RecordsManagement::uploadMessage(const QString &userName, return false; } - /* TODO - removed this return */ - return true; - return uploadFile(msgId, msgFileName, msgData, hierarchyModel->selectedIds()); } -- GitLab From 8adba6b0e423eccab1c5c849583b4beca2d74bd6 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Tue, 6 Feb 2018 13:24:05 +0100 Subject: [PATCH 35/63] Update message detail after upload to records management --- qml/pages/PageMessageDetail.qml | 7 +++++++ qml/pages/PageRecordsManagementUpload.qml | 2 +- src/messages.cpp | 5 +++-- src/messages.h | 16 +++++++++++++--- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/qml/pages/PageMessageDetail.qml b/qml/pages/PageMessageDetail.qml index c93b1a86..7257a554 100644 --- a/qml/pages/PageMessageDetail.qml +++ b/qml/pages/PageMessageDetail.qml @@ -116,6 +116,13 @@ Component { } } + Connections { + target: messages + onUpdateMessageDetail: { + msgDescrHtml = msgHtmlInfo + } + } + PageHeader { id: headerBar title: { diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index b1b4b6ad..5269e201 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -143,7 +143,7 @@ Component { text: qsTr("Upload message") onClicked: { if (recordsManagement.uploadMessage(userName, msgId, msgType, uploadHierarchyListModel)) { - messages.updateRmStatus(messageModel, msgId, true) + messages.updateRmStatus(messageModel, userName, msgId, true) pageView.pop(StackView.Immediate) } } diff --git a/src/messages.cpp b/src/messages.cpp index 3aa274c4..6cb750a9 100644 --- a/src/messages.cpp +++ b/src/messages.cpp @@ -234,8 +234,8 @@ void Messages::markMessagesAsLocallyRead(const QVariant &msgModelVariant, messageModel->overrideReadAll(isRead); } -void Messages::updateRmStatus(const QVariant &msgModelVariant, qint64 dmId, - bool isUploadRm) +void Messages::updateRmStatus(const QVariant &msgModelVariant, + const QString &userName, qint64 dmId, bool isUploadRm) { qDebug("%s()", __func__); @@ -246,6 +246,7 @@ void Messages::updateRmStatus(const QVariant &msgModelVariant, qint64 dmId, return; } messageModel->updateRmStatus(dmId, isUploadRm); + emit updateMessageDetail(getMessageDetail(userName, QString::number(dmId))); } void Messages::deleteMessageFromDbs(const QVariant &acntModelVariant, diff --git a/src/messages.h b/src/messages.h index 95ced3ee..c3b445b2 100644 --- a/src/messages.h +++ b/src/messages.h @@ -134,13 +134,14 @@ public: * @brief Update records management icon after message upload. * * @param[in,out] msgModelVariant QVariant holding message model to be set. + * @param[in] userName User name identifying the account. * @param[in] dmId Message id. * @param[in] isUploadRm Set whether to force records management * upload state. */ - Q_INVOKABLE static - void updateRmStatus(const QVariant &msgModelVariant, qint64 dmId, - bool isUploadRm); + Q_INVOKABLE + void updateRmStatus(const QVariant &msgModelVariant, + const QString &userName, qint64 dmId, bool isUploadRm); /*! * @brief Delete selected message from databases. @@ -191,6 +192,15 @@ public: QString dmEventDescr; }; +signals: + + /*! + * @brief Update message detail info. + * + * @param[in] msgHtmlInfo Message detail info string. + */ + void updateMessageDetail(QString msgHtmlInfo); + private: /*! * @brief Specifies whether the files should be moved or created from -- GitLab From fe8bdbefdb88ac3b2d28e0e5312f1e8f411e3fca Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Wed, 7 Feb 2018 10:18:49 +0100 Subject: [PATCH 36/63] Display service SVG logo in QML --- mobile-datovka.pro | 2 + qml/pages/PageSettingsRecordsManagement.qml | 2 + src/main.cpp | 2 + src/qml_interaction/image_provider.cpp | 50 ++++++++++++++++++++ src/qml_interaction/image_provider.h | 52 +++++++++++++++++++++ src/records_management.cpp | 38 +++------------ src/records_management.h | 15 ++---- 7 files changed, 119 insertions(+), 42 deletions(-) create mode 100644 src/qml_interaction/image_provider.cpp create mode 100644 src/qml_interaction/image_provider.h diff --git a/mobile-datovka.pro b/mobile-datovka.pro index 6ef02c94..7d82e683 100644 --- a/mobile-datovka.pro +++ b/mobile-datovka.pro @@ -131,6 +131,7 @@ SOURCES += \ src/net/net_layer.cpp \ src/net/xml_layer.cpp \ src/qml_interaction/attachment_data.cpp \ + src/qml_interaction/image_provider.cpp \ src/qml_interaction/interaction_filesystem.cpp \ src/qml_interaction/interaction_zfo_file.cpp \ src/qml_interaction/message_envelope.cpp \ @@ -214,6 +215,7 @@ HEADERS += \ src/net/net_layer.h \ src/net/xml_layer.h \ src/qml_interaction/attachment_data.h \ + src/qml_interaction/image_provider.h \ src/qml_interaction/interaction_filesystem.h \ src/qml_interaction/interaction_zfo_file.h \ src/qml_interaction/message_envelope.h \ diff --git a/qml/pages/PageSettingsRecordsManagement.qml b/qml/pages/PageSettingsRecordsManagement.qml index f60b6e72..01c8577c 100644 --- a/qml/pages/PageSettingsRecordsManagement.qml +++ b/qml/pages/PageSettingsRecordsManagement.qml @@ -231,6 +231,7 @@ Item { width: imgHeightHeader height: imgHeightHeader source: "qrc:/ui/briefcase.svg" + cache: false accessibleName: qsTr("Records management logo") } } // Grid @@ -267,6 +268,7 @@ Item { tokenName.text = srToken updateRmDataButton.visible = serviceInfo.visible serviceInfoError.visible = false + serviceLogo.source = "image://images/rmlogo.svg" } else { serviceInfo.visible = false updateRmDataButton.visible = serviceInfo.visible diff --git a/src/main.cpp b/src/main.cpp index f75d3e2b..c354cef9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -51,6 +51,7 @@ #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" @@ -317,6 +318,7 @@ int main(int argc, char *argv[]) /* get main handle of appliaction and QML */ QQmlApplicationEngine engine; QQmlContext *ctx = engine.rootContext(); + engine.addImageProvider(QLatin1String("images"), new ImageProvider); /* Register application pages to QML */ registerQmlTypes(uri, QML_PAGE_LOC, qmlPages); diff --git a/src/qml_interaction/image_provider.cpp b/src/qml_interaction/image_provider.cpp new file mode 100644 index 00000000..37202546 --- /dev/null +++ b/src/qml_interaction/image_provider.cpp @@ -0,0 +1,50 @@ +/* + * 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 "src/datovka_shared/graphics/graphics.h" +#include "src/qml_interaction/image_provider.h" +#include "src/records_management.h" + +#define LOGO_EDGE 64 + +ImageProvider::ImageProvider(void) + : QQuickImageProvider(QQuickImageProvider::Pixmap) +{ +} + +QPixmap ImageProvider::requestPixmap(const QString& id, QSize* size, + const QSize& requestedSize) +{ + Q_UNUSED(id); + + QPixmap pixmap(Graphics::pixmapFromSvg(globRmLogoSvg, LOGO_EDGE)); + QPixmap result; + + if (requestedSize.isValid()) { + result = pixmap.scaled(requestedSize, Qt::KeepAspectRatio); + } else { + result = pixmap; + } + *size = result.size(); + return result; +} diff --git a/src/qml_interaction/image_provider.h b/src/qml_interaction/image_provider.h new file mode 100644 index 00000000..7255777d --- /dev/null +++ b/src/qml_interaction/image_provider.h @@ -0,0 +1,52 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +/*! + * @brief Class provides an interface for supporting pixmaps + * and threaded image requests in QML. + */ +class ImageProvider : public QQuickImageProvider { + +public: + /*! + * @brief Constructor. + */ + ImageProvider(void); + + /*! + * @brief Transform image from stored bytearray and show it as QML image. + * + * @param[in] id String that identify image file. + * @param[in,out] size Output image size. + * @param[in] requestedSize Input image size (may be NULL). + * @return Plain pixmap image for QML. + */ + virtual + QPixmap requestPixmap(const QString &id, QSize *size, + const QSize& requestedSize); +}; diff --git a/src/records_management.cpp b/src/records_management.cpp index 58df39c5..a7930a95 100644 --- a/src/records_management.cpp +++ b/src/records_management.cpp @@ -23,7 +23,6 @@ #include -#include "src/datovka_shared/graphics/graphics.h" #include "src/datovka_shared/io/records_management_db.h" #include "src/datovka_shared/records_management/json/service_info.h" #include "src/datovka_shared/records_management/json/upload_file.h" @@ -92,8 +91,7 @@ bool processUploadFileResponse(const UploadFileResp &ufRes, qint64 dmId, RecordsManagement::RecordsManagement(QObject *parent) : QObject(parent), - m_rmc(RecordsManagementConnection::ignoreSslErrorsDflt, this), - m_logoSvg() + m_rmc(RecordsManagementConnection::ignoreSslErrorsDflt, this) { globWorkPool.start(); @@ -128,8 +126,7 @@ bool RecordsManagement::callServiceInfo(const QString &urlStr, ServiceInfoResp siRes( ServiceInfoResp::fromJson(response, &ok)); if (ok && siRes.isValid()) { - m_logoSvg = siRes.logoSvg(); - loadRecordsManagementPixmap(m_logoSvg); + globRmLogoSvg = siRes.logoSvg(); emit serviceInfo(siRes.name(), siRes.tokenName()); emit statusBarTextChanged(tr("Done"), false, true); return true; @@ -259,8 +256,7 @@ void RecordsManagement::loadStoredServiceInfo(void) return; } - m_logoSvg = entry.logoSvg; - loadRecordsManagementPixmap(entry.logoSvg); + globRmLogoSvg = entry.logoSvg; emit serviceInfo(entry.name, entry.tokenName); } @@ -322,7 +318,7 @@ bool RecordsManagement::updateServiceInfo(const QString &newUrlStr, entry.url = cUrlStr; entry.name = srName; entry.tokenName = srToken; - entry.logoSvg = m_logoSvg; + entry.logoSvg = globRmLogoSvg; globRecordsManagementDbPtr->updateServiceInfo(entry); if (oldUrlStr != cUrlStr) { return globRecordsManagementDbPtr->deleteAllStoredMsg(); @@ -346,30 +342,6 @@ void RecordsManagement::rmSyncFinished(const QString &userName, int accNumber, } } -void RecordsManagement::loadRecordsManagementPixmap(const QByteArray &logoSvg) -{ - if (Q_NULLPTR == globRecordsManagementDbPtr) { - return; - } - - QByteArray logo = logoSvg; - - if (logo.isNull()) { - - RecordsManagementDb::ServiceInfoEntry entry( - globRecordsManagementDbPtr->serviceInfo()); - if (!entry.isValid() || entry.logoSvg.isEmpty()) { - return; - } - logo = entry.logoSvg; - } - - QPixmap pixmap(Graphics::pixmapFromSvg(logo, LOGO_EDGE)); - if (!pixmap.isNull()) { - /* TODO - set Pixmap into QML */ - } -} - bool RecordsManagement::uploadFile(qint64 dmId, const QString &msgFileName, const QByteArray &msgData, const QStringList &uploadIds) { @@ -412,3 +384,5 @@ bool RecordsManagement::uploadFile(qint64 dmId, const QString &msgFileName, emit statusBarTextChanged(tr("Communication error"), false, true); return false; } + +QByteArray globRmLogoSvg; diff --git a/src/records_management.h b/src/records_management.h index 5aba7dde..9397963e 100644 --- a/src/records_management.h +++ b/src/records_management.h @@ -29,8 +29,6 @@ #include "src/datovka_shared/records_management/io/records_management_connection.h" #include "src/datovka_shared/settings/records_management.h" -#define LOGO_EDGE 64 - class RecordsManagement : public QObject { Q_OBJECT @@ -150,13 +148,6 @@ private slots: private: - /*! - * @brief Loads records management service logo and sets the logo label. - * - * @param[in] logoSvg Service SVG logo. - */ - void loadRecordsManagementPixmap(const QByteArray &logoSvg); - /*! * @brief Upload file into records management service. * @@ -171,5 +162,9 @@ private: const QByteArray &msgData, const QStringList &uploadIds); RecordsManagementConnection m_rmc; /*!< Connection to records management service. */ - QByteArray m_logoSvg; /*!< Raw SVG data. */ }; + +/*! + * @brief Global SVG data. + */ +extern QByteArray globRmLogoSvg; /*!< Raw SVG data. */ -- GitLab From f5a172338102ff4b8974dfcc783dc987d522a192 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Thu, 8 Feb 2018 14:09:03 +0100 Subject: [PATCH 37/63] Using image provider as a container for SVG images. Removed global record management log byte array. --- src/main.cpp | 8 +++- src/qml_interaction/image_provider.cpp | 51 +++++++++++++++++++++++--- src/qml_interaction/image_provider.h | 34 +++++++++++++++-- src/records_management.cpp | 18 ++++++--- src/records_management.h | 8 +--- 5 files changed, 96 insertions(+), 23 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index c354cef9..3257bad4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -318,7 +318,13 @@ int main(int argc, char *argv[]) /* get main handle of appliaction and QML */ QQmlApplicationEngine engine; QQmlContext *ctx = engine.rootContext(); - engine.addImageProvider(QLatin1String("images"), new ImageProvider); + + 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); diff --git a/src/qml_interaction/image_provider.cpp b/src/qml_interaction/image_provider.cpp index 37202546..90498c6c 100644 --- a/src/qml_interaction/image_provider.cpp +++ b/src/qml_interaction/image_provider.cpp @@ -28,16 +28,24 @@ #define LOGO_EDGE 64 ImageProvider::ImageProvider(void) - : QQuickImageProvider(QQuickImageProvider::Pixmap) + : QQuickImageProvider(QQuickImageProvider::Pixmap), + m_svgImgs() { } -QPixmap ImageProvider::requestPixmap(const QString& id, QSize* size, - const QSize& requestedSize) +QPixmap ImageProvider::requestPixmap(const QString &id, QSize *size, + const QSize &requestedSize) { - Q_UNUSED(id); + if (Q_UNLIKELY(id.isEmpty())) { + return QPixmap(); + } + + QByteArray svgData(svg(id)); + if (svgData.isEmpty()) { + return QPixmap(); + } - QPixmap pixmap(Graphics::pixmapFromSvg(globRmLogoSvg, LOGO_EDGE)); + QPixmap pixmap(Graphics::pixmapFromSvg(svgData, LOGO_EDGE)); QPixmap result; if (requestedSize.isValid()) { @@ -45,6 +53,37 @@ QPixmap ImageProvider::requestPixmap(const QString& id, QSize* size, } else { result = pixmap; } - *size = result.size(); + if (size != Q_NULLPTR) { + *size = result.size(); + } return result; } + +QByteArray ImageProvider::svg(const QString &id) const +{ + if (id.isEmpty()) { + return QByteArray(); + } + + QMap::const_iterator it(m_svgImgs.find(id)); + if (it != m_svgImgs.end()) { + return it.value(); + } + return QByteArray(); +} + +bool ImageProvider::setSvg(const QString &id, const QByteArray &svgData) +{ + if (Q_UNLIKELY(id.isEmpty())) { + return false; + } + + if (!svgData.isEmpty()) { + m_svgImgs[id] = svgData; + } else { + m_svgImgs.remove(id); + } + return true; +} + +ImageProvider *globImgProvPtr = Q_NULLPTR; diff --git a/src/qml_interaction/image_provider.h b/src/qml_interaction/image_provider.h index 7255777d..24644e52 100644 --- a/src/qml_interaction/image_provider.h +++ b/src/qml_interaction/image_provider.h @@ -26,6 +26,9 @@ #include #include +#define IMAGE_PROVIDER_ID QLatin1String("images") +#define RM_SVG_LOGO_ID QLatin1String("rmlogo.svg") /* Records management logo. */ + /*! * @brief Class provides an interface for supporting pixmaps * and threaded image requests in QML. @@ -39,14 +42,39 @@ public: ImageProvider(void); /*! - * @brief Transform image from stored bytearray and show it as QML image. + * @brief Transform image from stored byte array and return a pixmap. * - * @param[in] id String that identify image file. + * @param[in] id String identifying the image data. * @param[in,out] size Output image size. * @param[in] requestedSize Input image size (may be NULL). * @return Plain pixmap image for QML. */ virtual QPixmap requestPixmap(const QString &id, QSize *size, - const QSize& requestedSize); + const QSize &requestedSize) Q_DECL_OVERRIDE; + + /*! + * @brief Get SVG image data. + * + * @param[in] id Image data identifier. + * @return Non-empty image data if id present. + */ + QByteArray svg(const QString &id) const; + + /*! + * @brief Set SVG image data. + * + * @param[in] id Image data identifier. + * @param[in] svgData Image data in SVG format. + * @return True if value inserted. + */ + bool setSvg(const QString &id, const QByteArray &svgData); + +private: + QMap m_svgImgs; /*!< Map of images in SVG format. */ }; + +/*! + * @brief Globally accessible image provider. + */ +extern ImageProvider *globImgProvPtr; diff --git a/src/records_management.cpp b/src/records_management.cpp index a7930a95..06a7e6a6 100644 --- a/src/records_management.cpp +++ b/src/records_management.cpp @@ -27,14 +27,16 @@ #include "src/datovka_shared/records_management/json/service_info.h" #include "src/datovka_shared/records_management/json/upload_file.h" #include "src/datovka_shared/records_management/json/upload_hierarchy.h" +#include "src/datovka_shared/settings/records_management.h" #include "src/models/accountmodel.h" +#include "src/qml_interaction/image_provider.h" #include "src/records_management/models/upload_hierarchy_list_model.h" #include "src/records_management.h" #include "src/settings.h" +#include "src/sqlite/zfo_db.h" #include "src/worker/emitter.h" #include "src/worker/pool.h" #include "src/worker/task_records_management_stored_messages.h" -#include "src/sqlite/zfo_db.h" /*! * @brief Process upload file service response. @@ -126,7 +128,10 @@ bool RecordsManagement::callServiceInfo(const QString &urlStr, ServiceInfoResp siRes( ServiceInfoResp::fromJson(response, &ok)); if (ok && siRes.isValid()) { - globRmLogoSvg = siRes.logoSvg(); + if (globImgProvPtr != Q_NULLPTR) { + globImgProvPtr->setSvg(RM_SVG_LOGO_ID, + siRes.logoSvg()); + } emit serviceInfo(siRes.name(), siRes.tokenName()); emit statusBarTextChanged(tr("Done"), false, true); return true; @@ -256,7 +261,9 @@ void RecordsManagement::loadStoredServiceInfo(void) return; } - globRmLogoSvg = entry.logoSvg; + if (globImgProvPtr != Q_NULLPTR) { + globImgProvPtr->setSvg(RM_SVG_LOGO_ID, entry.logoSvg); + } emit serviceInfo(entry.name, entry.tokenName); } @@ -318,7 +325,8 @@ bool RecordsManagement::updateServiceInfo(const QString &newUrlStr, entry.url = cUrlStr; entry.name = srName; entry.tokenName = srToken; - entry.logoSvg = globRmLogoSvg; + entry.logoSvg = (globImgProvPtr != Q_NULLPTR) ? + globImgProvPtr->svg(RM_SVG_LOGO_ID) : QByteArray(); globRecordsManagementDbPtr->updateServiceInfo(entry); if (oldUrlStr != cUrlStr) { return globRecordsManagementDbPtr->deleteAllStoredMsg(); @@ -384,5 +392,3 @@ bool RecordsManagement::uploadFile(qint64 dmId, const QString &msgFileName, emit statusBarTextChanged(tr("Communication error"), false, true); return false; } - -QByteArray globRmLogoSvg; diff --git a/src/records_management.h b/src/records_management.h index 9397963e..c67263bb 100644 --- a/src/records_management.h +++ b/src/records_management.h @@ -25,9 +25,8 @@ #include -#include "src/sqlite/message_db_container.h" #include "src/datovka_shared/records_management/io/records_management_connection.h" -#include "src/datovka_shared/settings/records_management.h" +#include "src/messages.h" class RecordsManagement : public QObject { Q_OBJECT @@ -163,8 +162,3 @@ private: RecordsManagementConnection m_rmc; /*!< Connection to records management service. */ }; - -/*! - * @brief Global SVG data. - */ -extern QByteArray globRmLogoSvg; /*!< Raw SVG data. */ -- GitLab From 8f01f3d604f31b177f28f139baf1a4d3dd8b672e Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Thu, 8 Feb 2018 14:36:27 +0100 Subject: [PATCH 38/63] Minor modifications in ImageProvider::requestPixmap(). --- src/qml_interaction/image_provider.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/qml_interaction/image_provider.cpp b/src/qml_interaction/image_provider.cpp index 90498c6c..d283381e 100644 --- a/src/qml_interaction/image_provider.cpp +++ b/src/qml_interaction/image_provider.cpp @@ -25,7 +25,7 @@ #include "src/qml_interaction/image_provider.h" #include "src/records_management.h" -#define LOGO_EDGE 64 +#define DEFAULT_EDGE_LEN 128 ImageProvider::ImageProvider(void) : QQuickImageProvider(QQuickImageProvider::Pixmap), @@ -45,18 +45,18 @@ QPixmap ImageProvider::requestPixmap(const QString &id, QSize *size, return QPixmap(); } - QPixmap pixmap(Graphics::pixmapFromSvg(svgData, LOGO_EDGE)); - QPixmap result; - + int edgeLen = DEFAULT_EDGE_LEN; if (requestedSize.isValid()) { - result = pixmap.scaled(requestedSize, Qt::KeepAspectRatio); - } else { - result = pixmap; + int h = requestedSize.height(); + int w = requestedSize.width(); + edgeLen = (h < w) ? h : w; } + QPixmap pixmap(Graphics::pixmapFromSvg(svgData, edgeLen)); + if (size != Q_NULLPTR) { - *size = result.size(); + *size = pixmap.size(); } - return result; + return pixmap; } QByteArray ImageProvider::svg(const QString &id) const -- GitLab From b567df797ae633504db77f1b602cd33daba3b5ab Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Mon, 12 Feb 2018 15:49:05 +0100 Subject: [PATCH 39/63] Shorted some labels because ios --- qml/pages/PageSettingsRecordsManagement.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qml/pages/PageSettingsRecordsManagement.qml b/qml/pages/PageSettingsRecordsManagement.qml index 01c8577c..944e132d 100644 --- a/qml/pages/PageSettingsRecordsManagement.qml +++ b/qml/pages/PageSettingsRecordsManagement.qml @@ -62,7 +62,7 @@ Item { PageHeader { id: headerBar - title: qsTr("Records management settings") + title: qsTr("Records management") onBackClicked: { pageView.pop(StackView.Immediate) } @@ -175,7 +175,7 @@ Item { id: clearButton height: inputItemHeight font.pointSize: defaultTextFont.font.pointSize - text: qsTr("Clear service") + text: qsTr("Clear") onClicked: { clearAll() } -- GitLab From 432b26ba754e14ce8646458ad2af8a38a2e0e82f Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Tue, 13 Feb 2018 09:45:42 +0100 Subject: [PATCH 40/63] Backward compatibility with desktop datovka source structure --- mobile-datovka.pro | 3 +- .../io/records_management_db.cpp | 2 +- src/io/db_tables.h | 30 +++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 src/io/db_tables.h diff --git a/mobile-datovka.pro b/mobile-datovka.pro index 7d82e683..95a4fc68 100644 --- a/mobile-datovka.pro +++ b/mobile-datovka.pro @@ -198,7 +198,8 @@ HEADERS += \ src/dialogues/widget_input_dialogue.h \ src/dialogues/widget_message_dialogue.h \ src/files.h \ - src/io/filesystem.h \ + src/io/db_tables.h \ + src/io/filesystem.h \ src/isds/isds_conversion.h \ src/locker.h \ src/log/log.h \ diff --git a/src/datovka_shared/io/records_management_db.cpp b/src/datovka_shared/io/records_management_db.cpp index 8b4d0804..25ea5874 100644 --- a/src/datovka_shared/io/records_management_db.cpp +++ b/src/datovka_shared/io/records_management_db.cpp @@ -27,7 +27,7 @@ #include #include -#include "src/sqlite/db_tables.h" +#include "src/io/db_tables.h" #include "src/datovka_shared/io/records_management_db.h" #include "src/log/log.h" diff --git a/src/io/db_tables.h b/src/io/db_tables.h new file mode 100644 index 00000000..c0e6a373 --- /dev/null +++ b/src/io/db_tables.h @@ -0,0 +1,30 @@ +/* + * 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. + */ + +#pragma once + +/* + * Backward compatibility with desktop datovka source structure. + * File db_tables.h is located in different folder. + */ +#include "src/sqlite/db_tables.h" -- GitLab From e098f446bf993ed5b1e1af711e2c4e43c71d503f Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Tue, 13 Feb 2018 10:06:06 +0100 Subject: [PATCH 41/63] Minor records management settings refactoring --- qml/pages/PageSettingsRecordsManagement.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qml/pages/PageSettingsRecordsManagement.qml b/qml/pages/PageSettingsRecordsManagement.qml index 944e132d..6fbb9c0b 100644 --- a/qml/pages/PageSettingsRecordsManagement.qml +++ b/qml/pages/PageSettingsRecordsManagement.qml @@ -39,6 +39,7 @@ Item { function areUrlandTokenFilled() { infoButton.enabled = (urlTextField.text.toString() !== "" && tokenTextField.text.toString() !== "") clearButton.enabled = infoButton.enabled + acceptElement.visible = infoButton.enabled } /* Clear all data and info */ @@ -48,6 +49,7 @@ Item { serviceInfo.visible = false areUrlandTokenFilled() infoButton.text = qsTr("Get service info") + acceptElement.visible = true } Component.onCompleted: { @@ -73,6 +75,7 @@ Item { anchors.rightMargin: defaultMargin AccessibleImageButton { id: acceptElement + visible: false anchors.verticalCenter: parent.verticalCenter sourceSize.height: imgHeightHeader source: "qrc:/ui/checkbox-marked-circle.svg" @@ -106,7 +109,7 @@ Item { wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter width: parent.width - text: qsTr("Please fill service URL and your identification token. Then click button 'Get service info' to login and register records management service. You should received service info.") + text: qsTr("Please fill service URL and your identification token. Click button 'Get service info' to login into records management service. You should received service info. Finally, click to icon for saving into settings.") } AccessibleText { color: datovkaPalette.text -- GitLab From 06a2aa5f65b0ac6d3068480ceeffb623edf409b4 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Tue, 13 Feb 2018 11:10:32 +0100 Subject: [PATCH 42/63] Records management settings refactoring --- qml/pages/PageSettingsRecordsManagement.qml | 59 ++++++++++++--------- src/records_management.cpp | 4 +- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/qml/pages/PageSettingsRecordsManagement.qml b/qml/pages/PageSettingsRecordsManagement.qml index 6fbb9c0b..afc08c56 100644 --- a/qml/pages/PageSettingsRecordsManagement.qml +++ b/qml/pages/PageSettingsRecordsManagement.qml @@ -39,17 +39,18 @@ Item { function areUrlandTokenFilled() { infoButton.enabled = (urlTextField.text.toString() !== "" && tokenTextField.text.toString() !== "") clearButton.enabled = infoButton.enabled - acceptElement.visible = infoButton.enabled } /* Clear all data and info */ function clearAll() { + updateRmData.visible = false urlTextField.clear() tokenTextField.clear() serviceInfo.visible = false areUrlandTokenFilled() infoButton.text = qsTr("Get service info") acceptElement.visible = true + userNote.visible = true } Component.onCompleted: { @@ -59,7 +60,8 @@ Item { tokenTextField.text = settings.rmToken() areUrlandTokenFilled() recordsManagement.loadStoredServiceInfo() - updateRmDataButton.visible = recordsManagement.isValidRecordsManagement() + updateRmData.visible = recordsManagement.isValidRecordsManagement() + userNote.visible = !updateRmData.visible } PageHeader { @@ -104,12 +106,36 @@ Item { anchors.right: parent.right anchors.left: parent.left spacing: formItemVerticalSpacing + Column { + id: updateRmData + width: parent.width + spacing: formItemVerticalSpacing + visible: false + AccessibleText { + color: datovkaPalette.mid + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + width: parent.width + text: qsTr("Click button below for synchronization of information about uploaded files.") + } + AccessibleButton { + id: updateRmDataButton + height: inputItemHeight + font.pointSize: defaultTextFont.font.pointSize + anchors.horizontalCenter: parent.horizontalCenter + text: qsTr("Update list of uploaded files") + onClicked: { + recordsManagement.getStoredMsgInfoFromRecordsManagement(urlTextField.text, tokenTextField.text) + } + } + } // Column AccessibleText { + id: userNote color: datovkaPalette.mid wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter width: parent.width - text: qsTr("Please fill service URL and your identification token. Click button 'Get service info' to login into records management service. You should received service info. Finally, click to icon for saving into settings.") + text: qsTr("Please fill service URL and your identification token. Click button 'Get service info' to login into records management service. You should received service info. Finally, click the icon for saving into settings.") } AccessibleText { color: datovkaPalette.text @@ -172,6 +198,7 @@ Item { onClicked: { serviceInfo.visible = recordsManagement.callServiceInfo(urlTextField.text, tokenTextField.text) serviceInfoError.visible = !serviceInfo.visible + acceptElement.visible = serviceInfo.visible } } AccessibleButton { @@ -238,25 +265,7 @@ Item { accessibleName: qsTr("Records management logo") } } // Grid - AccessibleText { - color: datovkaPalette.mid - wrapMode: Text.Wrap - horizontalAlignment: Text.AlignHCenter - width: parent.width - text: qsTr("Click button below for synchronization of information about uploaded files.") - } - AccessibleButton { - id: updateRmDataButton - visible: false - height: inputItemHeight - font.pointSize: defaultTextFont.font.pointSize - anchors.horizontalCenter: parent.horizontalCenter - text: qsTr("Update list of uploaded files") - onClicked: { - recordsManagement.getStoredMsgInfoFromRecordsManagement(urlTextField.text, tokenTextField.text) - } - } - } // // Column + } // Column } // Column } // Pane ScrollIndicator.vertical: ScrollIndicator {} @@ -266,15 +275,17 @@ Item { onServiceInfo: { if (srName !== "" && srToken !== "") { serviceInfo.visible = true + userNote.visible = false + acceptElement.visible = true infoButton.text = (serviceInfo.visible) ? qsTr("Update service info") : qsTr("Get service info") serviceName.text = srName tokenName.text = srToken - updateRmDataButton.visible = serviceInfo.visible serviceInfoError.visible = false serviceLogo.source = "image://images/rmlogo.svg" } else { serviceInfo.visible = false - updateRmDataButton.visible = serviceInfo.visible + updateRmData.visible = serviceInfo.visible + userNote.visible = !serviceInfo.visible } } } diff --git a/src/records_management.cpp b/src/records_management.cpp index 06a7e6a6..91bfbc7d 100644 --- a/src/records_management.cpp +++ b/src/records_management.cpp @@ -343,10 +343,10 @@ void RecordsManagement::rmSyncFinished(const QString &userName, int accNumber, { if (accNumber < accTotal) { emit statusBarTextChanged( - tr("Sync account '%1' (%2/%3)").arg(userName).arg(accNumber).arg(accTotal), + tr("Update account '%1' (%2/%3)").arg(userName).arg(accNumber).arg(accTotal), true, true); } else { - emit statusBarTextChanged(tr("Sync done"), false, true); + emit statusBarTextChanged(tr("Update done"), false, true); } } -- GitLab From 4ec2471608e75b8afa649a92a4590529beb2d8b4 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Tue, 13 Feb 2018 12:33:48 +0100 Subject: [PATCH 43/63] Removed unnecessary line --- src/main.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 3257bad4..ca89da45 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -350,7 +350,6 @@ int main(int argc, char *argv[]) InteractionZfoFile interactionZfoFile; - /* Inicialize app delegate component for interaction with iOS * Reaction on the iOS action "Open in..." */ #if defined Q_OS_IOS -- GitLab From 1efe6dde1c4c089831f867e8a2d8685b185d5ce6 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Thu, 15 Feb 2018 12:03:50 +0100 Subject: [PATCH 44/63] Fixed some texts and updated Czech localisation. --- qml/pages/PageMenuDatovkaSettings.qml | 2 +- qml/pages/PageMenuMessageDetail.qml | 2 +- qml/pages/PageRecordsManagementUpload.qml | 16 +- qml/pages/PageSettingsRecordsManagement.qml | 4 +- res/locale/datovka_cs.ts | 610 ++++++++++++++++---- res/locale/datovka_en.ts | 598 +++++++++++++++---- 6 files changed, 972 insertions(+), 260 deletions(-) diff --git a/qml/pages/PageMenuDatovkaSettings.qml b/qml/pages/PageMenuDatovkaSettings.qml index 2bab4795..428c8269 100644 --- a/qml/pages/PageMenuDatovkaSettings.qml +++ b/qml/pages/PageMenuDatovkaSettings.qml @@ -172,7 +172,7 @@ Component { image: "qrc:/ui/briefcase.svg" showEntry: true showNext: true - name: qsTr("Records management") + name: qsTr("Records Management") funcName: "settRecMan" } ListElement { diff --git a/qml/pages/PageMenuMessageDetail.qml b/qml/pages/PageMenuMessageDetail.qml index b282d58e..12b28839 100644 --- a/qml/pages/PageMenuMessageDetail.qml +++ b/qml/pages/PageMenuMessageDetail.qml @@ -171,7 +171,7 @@ Component { image: "qrc:/ui/briefcase.svg" showEntry: true showNext: true - name: qsTr("Upload to Records Management") + name: qsTr("Upload to records management") funcName: "uploadRM" } ListElement { diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index 5269e201..b097c177 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -119,7 +119,7 @@ Component { wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter width: parent.width - text: qsTr("Here you can upload message into chosen folders in the records management. Update folder hierarchy and select target folders.") + text: qsTr("The message can be uploaded into selected locations in the records management hierarchy.") } Row { id: buttonRow @@ -265,6 +265,10 @@ Component { } } + /* + * TODO -- Handle situation where non-leaf nodes can also + * be selected. + */ MouseArea { function handleClick() { if (!rLeaf) { @@ -281,7 +285,15 @@ Component { anchors.fill: parent Accessible.role: Accessible.Button - Accessible.name: qsTr("Open attachment '%1'.").arg(rName) + Accessible.name: { + if (!rLeaf) { + qsTr("View content of %1.").arg(rName) + } else if (rSelectable) { + (!rSelected) ? qsTr("Select %1.").arg(rName) : qsTr("Deselect %1.").arg(rName) + } else { + "" + } + } Accessible.onScrollDownAction: uploadHierarchyList.scrollDown() Accessible.onScrollUpAction: uploadHierarchyList.scrollUp() Accessible.onPressAction: { diff --git a/qml/pages/PageSettingsRecordsManagement.qml b/qml/pages/PageSettingsRecordsManagement.qml index afc08c56..07135516 100644 --- a/qml/pages/PageSettingsRecordsManagement.qml +++ b/qml/pages/PageSettingsRecordsManagement.qml @@ -116,7 +116,7 @@ Item { wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter width: parent.width - text: qsTr("Click button below for synchronization of information about uploaded files.") + text: qsTr("Button below updates the information about data messages uploaded into the records management service.") } AccessibleButton { id: updateRmDataButton @@ -135,7 +135,7 @@ Item { wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter width: parent.width - text: qsTr("Please fill service URL and your identification token. Click button 'Get service info' to login into records management service. You should received service info. Finally, click the icon for saving into settings.") + text: qsTr("Please fill in service URL and your identification token. Click button '%1' to log into the records management service. You should receive information about the service. Finally, click the top right button to save the settings.").arg(infoButton.text) } AccessibleText { color: datovkaPalette.text diff --git a/res/locale/datovka_cs.ts b/res/locale/datovka_cs.ts index f5835d2c..df2976c4 100644 --- a/res/locale/datovka_cs.ts +++ b/res/locale/datovka_cs.ts @@ -296,6 +296,54 @@ Zpráva byla stažena + + ErrorEntry + + + No error occurred + Žádná chyba se nevyskytla + + + + Request was malformed + Požadavek byl poškozený + + + + Identifier is missing + Chybějící identifikátor + + + + Supplied identifier is wrong + Neplatný identifikátor + + + + File format is not supported + Nepodporovaný formát souboru + + + + Data are already present + Data již existují + + + + Service limit was exceeded + Překročen limit služby + + + + Unspecified error + Blíže neurčená chyba + + + + Unknown error + Neznámá chyba + + FileDialogue @@ -429,27 +477,27 @@ GlobalSettingsQmlWrapper - + Last synchronisation: %1 Poslední synchronizace: %1 - + Select directory Zvolit adresář - + New location error Chyba nového umístění - + It is not possible to store databases in the new location. Write permission denied. Není možné uložit databáze do nového umístění. Nemáte právo zápisu. - + Action will be cancelled. Akce bude zrušena. @@ -1185,26 +1233,26 @@ MessageList - - + + Unread message from sender Nepřečtená zpráva od odesílatele - - + + Read message from sender Přečtená zpráva od odesílatele - - + + Message to receiver Zpráva příjemci - - + + Subject Předmět @@ -1212,57 +1260,57 @@ Messages - + Delete message: %1 Smazat zprávu: %1 - + Do you want to delete the message '%1'? Přejete si smazat zprávu '%1'? - + Note: It will delete all attachments and message information from the local database. Poznámka: Z místní databáze budou smazány všechny přílohy a informace o zprávě. - + New location error Chyba nového umístění - + It is not possible to store databases in the new location because there is not enough free space left. Není možné uložit databáze do nového umístění, protože se tam nenachází dostatek volného místa. - + Databases size is %1 MB. Velikost databáze je %1 MB. - + Change database location Změna umístění databází - + What do you want to do with the currently used database files? Co chcete udělat se stávajícími databázovými soubory? - + You have the option to move the current database files to a new location or you can delete them and create new empty databases. Máte možnost přesunout soubory současných databází do nového umístění, nebo je můžete smazat a vytvořit nové prázdné databáze. - + Move Přesunout - + Create new Nově vytvořit @@ -1458,26 +1506,26 @@ Synchronizovat účet '%1'. - - + + Received messages Přijaté zprávy - - + + Received messages of account '%1'. (Unread %2 of %3 received messages.) Přijaté zprávy účtu '%1'. (Nepřečteno %2 z %3 přijatých zpráv.) - - + + Sent messages Odeslané zprávy - - + + Sent messages of account '%1'. (Total of %2 sent messages.) Odeslané zprávy účtu '%1'. (Celkem %2 odeslaných zpráv.) @@ -1940,50 +1988,56 @@ Nastavení - - + + Add account Přidat účet - - + + Search message Hledat zprávu - - + + Import message Importovat zprávu - - + + General Obecné - - + + Synchronization Synchronizace - - + + Storage Úložiště - - + + Security and PIN Zabezpečení a PIN - - + + + Records Management + Spisová služba + + + + User Guide Uživatelská příručka @@ -2024,50 +2078,56 @@ PageMenuMessageDetail - - + + Message Zpráva - - + + Download attachments Stáhnout přílohy - - + + Reply Odpověď - - + + Forward Přeposlat - - + + Use as template Použít jako šablonu - - + + + Upload to records management + Nahrát do spisové služby + + + + Send attachments by email Odeslat přílohy emailem - - + + Save attachments Uložit přílohy - - + + Delete attachments Smazat přílohy @@ -2102,88 +2162,88 @@ Soubor neobsahuje platnou datovou zprávu ani platné údaje o doručení a nebo je poškozen. - - + + Unknown ZFO content Neznámy obsah souboru ZFO - - + + Received message Přijatá zpráva - - + + Sent message Odeslaná zpráva - - + + ZFO Message ZFO zpráva - - + + ZFO Delivery info ZFO doručenka - - + + Message ID ID zprávy - - + + Email attachments Poslat přílohy emailem - - + + Save attachments. Uložit přílohy. - - + + Download message Stáhnout zprávu - - + + Show menu of available operations Zobrazit menu s dostupnými operacemi - - + + Attachments Přílohy - - + + Open attachment '%1'. Otevřít přílohu '%1'. - - + + Attachments have not been downloaded yet. Click the icon or this text for their download. Přílohy nebyly doposud staženy. Klikněte na ikonu nebo na tento text pro jejich stažení. - - + + No attachments present. Přílohy nejsou přítomny. @@ -2290,6 +2350,95 @@ Klikněte na ikonu nebo na tento text pro jejich stažení. Nenalezena žádná zpráva pro uvedený hledaný text. + + PageRecordsManagementUpload + + + + Upload message %1 + Nahrát zprávu %1 + + + + + Filter upload hierarchy. + Filtrovat hierarchii pro nahrávání. + + + Here you can upload message into chosen folders in the records management. Update folder hierarchy and select target folders. + Zde můžete nahrávat + + + + + The message can be uploaded into selected locations in the records management hierarchy. + Zprávu můžete nahrát do zvolených umístění v hierarchii spisové služby. + + + + + Update upload hierarchy + Aktualizovat hierarchii + + + + + Upload message + Nahrát zprávu + + + + + Up + Nahoru + + + + + Set filter + Nastavit filtr + + + + + Clear and hide filter field + Vymazat a schovat pole filtru + + + + + Upload hierarchy has not been downloaded yet. + Hierarchie spisové služby nebyla doposud stažena. + + + + + No hierarchy entry found that matches filter text '%1'. + Nenalezena žádná položka hierarchie odpovídající textu '%1'. + + + + + View content of %1. + Zobrazit obsah %1. + + + + + Select %1. + Vybrat %1. + + + + + Deselect %1. + Zrušit výběr %1. + + + Open attachment '%1'. + Otevřít přílohu '%1'. + + PageSendMessage @@ -2874,6 +3023,121 @@ Klikněte na ikonu nebo na tento text pro jejich stažení. Vybrat dobu (v sekundách), za jak dlouho se aplikace uzamkne. + + PageSettingsRecordsManagement + + + + + + + + Get service info + Stáhnout info o službě + + + + + Records management + Spisová služba + + + + + Accept changes + Přijmout změny + + + Button below updates the information about data messages present into the records management service. + Tlačítko níže aktualizuje informace o datových zprávách + + + + + Button below updates the information about data messages uploaded into the records management service. + Tlačítko níže aktualizuje informace o datových zprávách nahraných do spisové služby. + + + + + Update list of uploaded files + Aktualizovat seznam nahraných zpráv + + + + + Please fill in service URL and your identification token. Click button '%1' to log into the records management service. You should receive information about the service. Finally, click the top right button to save the settings. + Vyplňte prosím URL služby a Váš identifikační token. Stiskněte tlačítko '%1' k přihlášení do spisové služby. Měli byste obdržet informace o službě. Pro uložení nastavení stiskněte pravé horní tlačítko. + + + + + URL + URL + + + + + Enter url + Zadejte URL + + + + + + + Token + Token + + + + + Enter token + Zadejte token + + + + + Clear + Vymazat + + + + + Communication error. Cannot obtain records management info from server. Internet connection failed or service url and identification token can be wrong! + Chyba komunikace. Nelze obdržet informace o spisové službě. Připojení k internetu nefunguje nebo URL služby a/nebo identifikační token jsou neplatné! + + + + + Records management info + Informace o spisové službě + + + + + Name + Jméno + + + + + Logo + Logo + + + + + Records management logo + Logo spisové služby + + + + + Update service info + Aktualizovat info o službě + + PageSettingsStorage @@ -3067,13 +3331,13 @@ Klikněte na ikonu nebo na tento text pro jejich stažení. - + General Obecné informace - + Personal delivery Doručení do vlastních rukou @@ -3086,8 +3350,8 @@ Klikněte na ikonu nebo na tento text pro jejich stažení. - - + + Yes Ano @@ -3095,8 +3359,8 @@ Klikněte na ikonu nebo na tento text pro jejich stažení. - - + + No Ne @@ -3112,37 +3376,47 @@ Klikněte na ikonu nebo na tento text pro jejich stažení. - + Delivery by fiction Doručení fikcí - - + + Address Adresa - + Message type Typ zprávy - + Message author Odesílající osoba + + + Records Management location + + + + + Folder + + - + Message state Stav zprávy - + Events Události @@ -3309,7 +3583,7 @@ Klikněte na ikonu nebo na tento text pro jejich stažení. - + Sender Odesílatel @@ -3321,7 +3595,7 @@ Klikněte na ikonu nebo na tento text pro jejich stažení. - + Recipient Příjemce @@ -3429,7 +3703,7 @@ Klikněte na ikonu nebo na tento text pro jejich stažení. - + Attachment size Velikost příloh @@ -3441,15 +3715,15 @@ Klikněte na ikonu nebo na tento text pro jejich stažení. - - + + Name Jméno - + Subject Předmět @@ -3476,38 +3750,38 @@ Klikněte na ikonu nebo na tento text pro jejich stažení. - - + + Databox ID ID schránky - + Data box application Aplikace pro datové schránky - + ZFO file to be viewed. Soubor zfo, který má být zobrazen. - + Last synchronisation: %1 Poslední synchronizace: %1 - + Security problem Bezpečnostní problém - + OpenSSL support is required! Je nezbytná podpora OpenSSL! - + The device does not support OpenSSL. The application won't work correctly. Vaše zařízení nepodporuje OpenSSL. Aplikace nebude pracovat správně. @@ -3576,6 +3850,88 @@ Klikněte na ikonu nebo na tento text pro jejich stažení. File has not been imported! Soubor nebyl importován! + + + Message '%1' could not be uploaded. + Zpráva '%1' nemohla být nahrána. + + + + Received error + Obržena chyba + + + + File Upload Error + Chyba nahrávání souboru + + + + Successful File Upload + Úspěšné nahrání souboru + + + + Message '%1' was successfully uploaded into the records management service. + Zpráva '%1' byla úspěšně nahrána do spisové služby. + + + + It can be now found in the records management service in these locations: + Lze ji teď nalézt ve spisové službě v těchto položkách: + + + + RecordsManagement + + + Get service info + Stáhnout info o službě + + + + + + Done + Hotovo + + + + + + Communication error + Chyba komunikace + + + + Upload hierarchy + Hierarchie služby + + + + Sync service + + + + + Sync failed + Synchronizace selhala + + + + Update account '%1' (%2/%3) + Aktualizuji účet '%1' (%2/%3) + + + + Update done + Aktualizace dokončena + + + + Upload message + Nahrát zprávu + TaskFindDataboxFulltext @@ -3611,26 +3967,26 @@ Klikněte na ikonu nebo na tento text pro jejich stažení. main - - + + Version Verze - - + + Enter PIN code Zadejte PIN kód - - + + Wrong PIN code! Špatný PIN kód! - - + + Enter Vstoupit diff --git a/res/locale/datovka_en.ts b/res/locale/datovka_en.ts index cf0ee1f5..c63fae91 100644 --- a/res/locale/datovka_en.ts +++ b/res/locale/datovka_en.ts @@ -296,6 +296,54 @@ + + ErrorEntry + + + No error occurred + + + + + Request was malformed + + + + + Identifier is missing + + + + + Supplied identifier is wrong + + + + + File format is not supported + + + + + Data are already present + + + + + Service limit was exceeded + + + + + Unspecified error + + + + + Unknown error + + + FileDialogue @@ -429,27 +477,27 @@ GlobalSettingsQmlWrapper - + Last synchronisation: %1 - + Select directory - + New location error - + It is not possible to store databases in the new location. Write permission denied. - + Action will be cancelled. @@ -1185,26 +1233,26 @@ MessageList - - + + Unread message from sender - - + + Read message from sender - - + + Message to receiver - - + + Subject @@ -1212,57 +1260,57 @@ Messages - + Delete message: %1 - + Do you want to delete the message '%1'? - + Note: It will delete all attachments and message information from the local database. - + New location error - + It is not possible to store databases in the new location because there is not enough free space left. - + Databases size is %1 MB. - + Change database location - + What do you want to do with the currently used database files? - + You have the option to move the current database files to a new location or you can delete them and create new empty databases. - + Move - + Create new @@ -1458,26 +1506,26 @@ - - + + Received messages - - + + Received messages of account '%1'. (Unread %2 of %3 received messages.) - - + + Sent messages - - + + Sent messages of account '%1'. (Total of %2 sent messages.) @@ -1940,50 +1988,56 @@ - - + + Add account - - + + Search message - - + + Import message - - + + General - - + + Synchronization - - + + Storage - - + + Security and PIN - - + + + Records Management + + + + + User Guide @@ -2024,50 +2078,56 @@ PageMenuMessageDetail - - + + Message - - + + Download attachments - - + + Reply - - + + Forward - - + + Use as template - - + + + Upload to records management + + + + + Send attachments by email - - + + Save attachments - - + + Delete attachments @@ -2102,87 +2162,87 @@ - - + + Unknown ZFO content - - + + Received message - - + + Sent message - - + + ZFO Message - - + + ZFO Delivery info - - + + Message ID - - + + Email attachments - - + + Save attachments. - - + + Download message - - + + Show menu of available operations - - + + Attachments - - + + Open attachment '%1'. - - + + Attachments have not been downloaded yet. Click the icon or this text for their download. - - + + No attachments present. @@ -2289,6 +2349,87 @@ Click the icon or this text for their download. + + PageRecordsManagementUpload + + + + Upload message %1 + + + + + + Filter upload hierarchy. + + + + + + The message can be uploaded into selected locations in the records management hierarchy. + + + + + + Update upload hierarchy + + + + + + Upload message + + + + + + Up + + + + + + Set filter + + + + + + Clear and hide filter field + + + + + + Upload hierarchy has not been downloaded yet. + + + + + + No hierarchy entry found that matches filter text '%1'. + + + + + + View content of %1. + + + + + + Select %1. + + + + + + Deselect %1. + + + PageSendMessage @@ -2873,6 +3014,117 @@ Click the icon or this text for their download. + + PageSettingsRecordsManagement + + + + + + + + Get service info + + + + + + Records management + + + + + + Accept changes + + + + + + Button below updates the information about data messages uploaded into the records management service. + + + + + + Update list of uploaded files + + + + + + Please fill in service URL and your identification token. Click button '%1' to log into the records management service. You should receive information about the service. Finally, click the top right button to save the settings. + + + + + + URL + + + + + + Enter url + + + + + + + + Token + + + + + + Enter token + + + + + + Clear + + + + + + Communication error. Cannot obtain records management info from server. Internet connection failed or service url and identification token can be wrong! + + + + + + Records management info + + + + + + Name + + + + + + Logo + + + + + + Records management logo + + + + + + Update service info + + + PageSettingsStorage @@ -3066,13 +3318,13 @@ Click the icon or this text for their download. - + General - + Personal delivery @@ -3085,8 +3337,8 @@ Click the icon or this text for their download. - - + + Yes @@ -3094,8 +3346,8 @@ Click the icon or this text for their download. - - + + No @@ -3111,37 +3363,47 @@ Click the icon or this text for their download. - + Delivery by fiction - - + + Address - + Message type - + Message author + + + Records Management location + + + + + Folder + + - + Message state - + Events @@ -3308,7 +3570,7 @@ Click the icon or this text for their download. - + Sender @@ -3320,7 +3582,7 @@ Click the icon or this text for their download. - + Recipient @@ -3428,7 +3690,7 @@ Click the icon or this text for their download. - + Attachment size @@ -3440,15 +3702,15 @@ Click the icon or this text for their download. - - + + Name - + Subject @@ -3475,38 +3737,38 @@ Click the icon or this text for their download. - - + + Databox ID - + Data box application - + ZFO file to be viewed. - + Last synchronisation: %1 - + Security problem - + OpenSSL support is required! - + The device does not support OpenSSL. The application won't work correctly. @@ -3575,6 +3837,88 @@ Click the icon or this text for their download. File has not been imported! + + + Message '%1' could not be uploaded. + + + + + Received error + + + + + File Upload Error + + + + + Successful File Upload + + + + + Message '%1' was successfully uploaded into the records management service. + + + + + It can be now found in the records management service in these locations: + + + + + RecordsManagement + + + Get service info + + + + + + + Done + + + + + + + Communication error + + + + + Upload hierarchy + + + + + Sync service + + + + + Sync failed + + + + + Update account '%1' (%2/%3) + + + + + Update done + + + + + Upload message + + TaskFindDataboxFulltext @@ -3610,26 +3954,26 @@ Click the icon or this text for their download. main - - + + Version - - + + Enter PIN code - - + + Wrong PIN code! - - + + Enter -- GitLab From 93a1eb21db90ee1a7df3423619012c8f960e1ba5 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Thu, 15 Feb 2018 13:01:34 +0100 Subject: [PATCH 45/63] Fixed listview scrolling and set clip property --- qml/pages/PageRecordsManagementUpload.qml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index b097c177..f8f36bed 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -228,6 +228,11 @@ Component { width: parent.width model: uploadHierarchyProxyModel + clip: true + spacing: 1 + opacity: 1 + interactive: true + delegate: Rectangle { id: uploadItem -- GitLab From db819c0628386d226d2a24e2652bd80700e1fdeb Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Thu, 15 Feb 2018 14:46:56 +0100 Subject: [PATCH 46/63] Updaded upload message page layout --- qml/pages/PageMenuMessageDetail.qml | 2 +- qml/pages/PageRecordsManagementUpload.qml | 101 +++++++++++----------- res/qml.qrc | 1 + res/ui/folder-upload.svg | 6 ++ 4 files changed, 59 insertions(+), 51 deletions(-) create mode 100755 res/ui/folder-upload.svg diff --git a/qml/pages/PageMenuMessageDetail.qml b/qml/pages/PageMenuMessageDetail.qml index 12b28839..80b3a9da 100644 --- a/qml/pages/PageMenuMessageDetail.qml +++ b/qml/pages/PageMenuMessageDetail.qml @@ -168,7 +168,7 @@ Component { funcName: "useTemplate" } ListElement { - image: "qrc:/ui/briefcase.svg" + image: "qrc:/ui/folder-upload.svg" showEntry: true showNext: true name: qsTr("Upload to records management") diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index f8f36bed..9c52d73b 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -43,6 +43,7 @@ Component { property var messageModel: null Component.onCompleted: { + uploadButton.enabled = false uploadHierarchyProxyModel.setSourceModel(uploadHierarchyListModel) uploadHierarchyProxyModel.sort() timer.start() @@ -72,7 +73,7 @@ Component { id: uploadHierarchyListModel onModelReset: { uploadHierarchyProxyModel.sort() - locationLabel.text = uploadHierarchyListModel.navigatedRootName(true, "/") + locationLabel.text = uploadHierarchyListModel.navigatedRootName(true, " / ") } Component.onCompleted: { } @@ -96,70 +97,44 @@ Component { spacing: defaultMargin anchors.right: parent.right anchors.rightMargin: defaultMargin - AccessibleImageButton { - id: searchButton + AccessibleOverlaidImageButton { + id: uploadButton anchors.verticalCenter: parent.verticalCenter - sourceSize.height: imgHeightHeader - source: "qrc:/ui/magnify.svg" - accessibleName: qsTr("Filter upload hierarchy.") + anchors.right: parent.right + anchors.rightMargin: defaultMargin + image.sourceSize.height: imgHeightHeader + image.source: "qrc:/ui/folder-upload.svg" + accessibleName: qsTr("Send message to records management.") onClicked: { - filterBar.visible = true - uploadHierarchyList.anchors.top = filterBar.bottom - filterBar.filterField.forceActiveFocus() - Qt.inputMethod.show() + uploadButton.enabled = false + if (recordsManagement.uploadMessage(userName, msgId, msgType, uploadHierarchyListModel)) { + messages.updateRmStatus(messageModel, userName, msgId, true) + pageView.pop(StackView.Immediate) + } + uploadButton.enabled = true } } } } - AccessibleText { id: topText anchors.top: headerBar.bottom + anchors.topMargin: defaultMargin color: datovkaPalette.mid wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter width: parent.width text: qsTr("The message can be uploaded into selected locations in the records management hierarchy.") } - Row { - id: buttonRow - spacing: formItemVerticalSpacing * 5 - anchors.top: topText.bottom - anchors.horizontalCenter: parent.horizontalCenter - AccessibleButton { - id: hierarchyButton - height: inputItemHeight - font.pointSize: defaultTextFont.font.pointSize - text: qsTr("Update upload hierarchy") - onClicked: { - downloadUploadHierarchy() - } - } - AccessibleButton { - id: uploadButton - visible: false - height: inputItemHeight - font.pointSize: defaultTextFont.font.pointSize - text: qsTr("Upload message") - onClicked: { - if (recordsManagement.uploadMessage(userName, msgId, msgType, uploadHierarchyListModel)) { - messages.updateRmStatus(messageModel, userName, msgId, true) - pageView.pop(StackView.Immediate) - } - } - } - } Row { id: navigateUpRow - spacing: formItemVerticalSpacing * 5 - anchors.top: buttonRow.bottom + spacing: formItemVerticalSpacing * 3 + anchors.top: topText.bottom anchors.left: parent.left anchors.right: parent.right AccessibleButton { id: upButton - anchors { - verticalCenter: parent.verticalCenter; - } + anchors.verticalCenter: parent.verticalCenter text: "<" accessibleName: qsTr("Up") /* Needs to be specified as "<" is not read. */ onClicked: { @@ -168,10 +143,37 @@ Component { } AccessibleText { id: locationLabel - anchors { - verticalCenter: parent.verticalCenter; + anchors.verticalCenter: parent.verticalCenter + text: " / " + } + } + Row { + id: navigateToolBar + spacing: formItemVerticalSpacing * 3 + anchors.top: topText.bottom + anchors.right: parent.right + AccessibleImageButton { + id: searchButton + anchors.verticalCenter: parent.verticalCenter + sourceSize.height: imgHeightHeader + source: "qrc:/ui/magnify.svg" + accessibleName: qsTr("Filter upload hierarchy.") + onClicked: { + filterBar.visible = true + uploadHierarchyList.anchors.top = filterBar.bottom + filterBar.filterField.forceActiveFocus() + Qt.inputMethod.show() + } + } + AccessibleImageButton { + id: hierarchyButton + anchors.verticalCenter: parent.verticalCenter + sourceSize.height: imgHeightHeader + source: "qrc:/ui/sync.svg" + accessibleName: qsTr("Update upload hierarchy.") + onClicked: { + downloadUploadHierarchy() } - text: "/" } } Rectangle { @@ -194,7 +196,7 @@ Component { placeholderText: qsTr("Set filter") fontPointSize: defaultTextFont.font.pointSize buttonImageHeight: imgHeight - buttonAccessibleName: qsTr("Clear and hide filter field") + buttonAccessibleName: qsTr("Clear and hide filter field.") onTextChanged: { uploadHierarchyProxyModel.setFilterRegExpStr(text) @@ -282,8 +284,7 @@ Component { } else if (rSelectable) { uploadHierarchyListModel.toggleNodeSelection( uploadHierarchyProxyModel.mapToSource(index)) - uploadButton.visible = (uploadHierarchyListModel.selectedIds().length > 0) - hierarchyButton.visible = !uploadButton.visible + uploadButton.enabled = (uploadHierarchyListModel.selectedIds().length > 0) } } diff --git a/res/qml.qrc b/res/qml.qrc index 95a145e8..863e1a0a 100644 --- a/res/qml.qrc +++ b/res/qml.qrc @@ -63,6 +63,7 @@ ui/file-import.svg ui/folder-account.svg ui/folder-download.svg + ui/folder-upload.svg ui/format-list-bulleted.svg ui/forward.svg ui/help-circle.svg diff --git a/res/ui/folder-upload.svg b/res/ui/folder-upload.svg new file mode 100755 index 00000000..c122e814 --- /dev/null +++ b/res/ui/folder-upload.svg @@ -0,0 +1,6 @@ + + + + + + -- GitLab From 7008af77689190d74bd7d07d8821f9584cf0abf0 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Thu, 15 Feb 2018 15:02:21 +0100 Subject: [PATCH 47/63] Revert "Updaded upload message page layout" This reverts commit 85df88bb539bfb52dd853658fa428892c46f8360. --- qml/pages/PageMenuMessageDetail.qml | 2 +- qml/pages/PageRecordsManagementUpload.qml | 101 +++++++++++----------- res/qml.qrc | 1 - res/ui/folder-upload.svg | 6 -- 4 files changed, 51 insertions(+), 59 deletions(-) delete mode 100755 res/ui/folder-upload.svg diff --git a/qml/pages/PageMenuMessageDetail.qml b/qml/pages/PageMenuMessageDetail.qml index 80b3a9da..12b28839 100644 --- a/qml/pages/PageMenuMessageDetail.qml +++ b/qml/pages/PageMenuMessageDetail.qml @@ -168,7 +168,7 @@ Component { funcName: "useTemplate" } ListElement { - image: "qrc:/ui/folder-upload.svg" + image: "qrc:/ui/briefcase.svg" showEntry: true showNext: true name: qsTr("Upload to records management") diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index 9c52d73b..f8f36bed 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -43,7 +43,6 @@ Component { property var messageModel: null Component.onCompleted: { - uploadButton.enabled = false uploadHierarchyProxyModel.setSourceModel(uploadHierarchyListModel) uploadHierarchyProxyModel.sort() timer.start() @@ -73,7 +72,7 @@ Component { id: uploadHierarchyListModel onModelReset: { uploadHierarchyProxyModel.sort() - locationLabel.text = uploadHierarchyListModel.navigatedRootName(true, " / ") + locationLabel.text = uploadHierarchyListModel.navigatedRootName(true, "/") } Component.onCompleted: { } @@ -97,29 +96,25 @@ Component { spacing: defaultMargin anchors.right: parent.right anchors.rightMargin: defaultMargin - AccessibleOverlaidImageButton { - id: uploadButton + AccessibleImageButton { + id: searchButton anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - anchors.rightMargin: defaultMargin - image.sourceSize.height: imgHeightHeader - image.source: "qrc:/ui/folder-upload.svg" - accessibleName: qsTr("Send message to records management.") + sourceSize.height: imgHeightHeader + source: "qrc:/ui/magnify.svg" + accessibleName: qsTr("Filter upload hierarchy.") onClicked: { - uploadButton.enabled = false - if (recordsManagement.uploadMessage(userName, msgId, msgType, uploadHierarchyListModel)) { - messages.updateRmStatus(messageModel, userName, msgId, true) - pageView.pop(StackView.Immediate) - } - uploadButton.enabled = true + filterBar.visible = true + uploadHierarchyList.anchors.top = filterBar.bottom + filterBar.filterField.forceActiveFocus() + Qt.inputMethod.show() } } } } + AccessibleText { id: topText anchors.top: headerBar.bottom - anchors.topMargin: defaultMargin color: datovkaPalette.mid wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter @@ -127,14 +122,44 @@ Component { text: qsTr("The message can be uploaded into selected locations in the records management hierarchy.") } Row { - id: navigateUpRow - spacing: formItemVerticalSpacing * 3 + id: buttonRow + spacing: formItemVerticalSpacing * 5 anchors.top: topText.bottom + anchors.horizontalCenter: parent.horizontalCenter + AccessibleButton { + id: hierarchyButton + height: inputItemHeight + font.pointSize: defaultTextFont.font.pointSize + text: qsTr("Update upload hierarchy") + onClicked: { + downloadUploadHierarchy() + } + } + AccessibleButton { + id: uploadButton + visible: false + height: inputItemHeight + font.pointSize: defaultTextFont.font.pointSize + text: qsTr("Upload message") + onClicked: { + if (recordsManagement.uploadMessage(userName, msgId, msgType, uploadHierarchyListModel)) { + messages.updateRmStatus(messageModel, userName, msgId, true) + pageView.pop(StackView.Immediate) + } + } + } + } + Row { + id: navigateUpRow + spacing: formItemVerticalSpacing * 5 + anchors.top: buttonRow.bottom anchors.left: parent.left anchors.right: parent.right AccessibleButton { id: upButton - anchors.verticalCenter: parent.verticalCenter + anchors { + verticalCenter: parent.verticalCenter; + } text: "<" accessibleName: qsTr("Up") /* Needs to be specified as "<" is not read. */ onClicked: { @@ -143,37 +168,10 @@ Component { } AccessibleText { id: locationLabel - anchors.verticalCenter: parent.verticalCenter - text: " / " - } - } - Row { - id: navigateToolBar - spacing: formItemVerticalSpacing * 3 - anchors.top: topText.bottom - anchors.right: parent.right - AccessibleImageButton { - id: searchButton - anchors.verticalCenter: parent.verticalCenter - sourceSize.height: imgHeightHeader - source: "qrc:/ui/magnify.svg" - accessibleName: qsTr("Filter upload hierarchy.") - onClicked: { - filterBar.visible = true - uploadHierarchyList.anchors.top = filterBar.bottom - filterBar.filterField.forceActiveFocus() - Qt.inputMethod.show() - } - } - AccessibleImageButton { - id: hierarchyButton - anchors.verticalCenter: parent.verticalCenter - sourceSize.height: imgHeightHeader - source: "qrc:/ui/sync.svg" - accessibleName: qsTr("Update upload hierarchy.") - onClicked: { - downloadUploadHierarchy() + anchors { + verticalCenter: parent.verticalCenter; } + text: "/" } } Rectangle { @@ -196,7 +194,7 @@ Component { placeholderText: qsTr("Set filter") fontPointSize: defaultTextFont.font.pointSize buttonImageHeight: imgHeight - buttonAccessibleName: qsTr("Clear and hide filter field.") + buttonAccessibleName: qsTr("Clear and hide filter field") onTextChanged: { uploadHierarchyProxyModel.setFilterRegExpStr(text) @@ -284,7 +282,8 @@ Component { } else if (rSelectable) { uploadHierarchyListModel.toggleNodeSelection( uploadHierarchyProxyModel.mapToSource(index)) - uploadButton.enabled = (uploadHierarchyListModel.selectedIds().length > 0) + uploadButton.visible = (uploadHierarchyListModel.selectedIds().length > 0) + hierarchyButton.visible = !uploadButton.visible } } diff --git a/res/qml.qrc b/res/qml.qrc index 863e1a0a..95a145e8 100644 --- a/res/qml.qrc +++ b/res/qml.qrc @@ -63,7 +63,6 @@ ui/file-import.svg ui/folder-account.svg ui/folder-download.svg - ui/folder-upload.svg ui/format-list-bulleted.svg ui/forward.svg ui/help-circle.svg diff --git a/res/ui/folder-upload.svg b/res/ui/folder-upload.svg deleted file mode 100755 index c122e814..00000000 --- a/res/ui/folder-upload.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - -- GitLab From 6b2843999f2e0b05eb3ab4eb19bb45e39620dcc9 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Thu, 15 Feb 2018 16:04:25 +0100 Subject: [PATCH 48/63] Fixed errors in UploadHierarchyListModel. --- .../models/upload_hierarchy_list_model.cpp | 7 ++++++- .../models/upload_hierarchy_list_model.h | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/records_management/models/upload_hierarchy_list_model.cpp b/src/records_management/models/upload_hierarchy_list_model.cpp index b7d9d238..25bdfdf5 100644 --- a/src/records_management/models/upload_hierarchy_list_model.cpp +++ b/src/records_management/models/upload_hierarchy_list_model.cpp @@ -45,7 +45,7 @@ UploadHierarchyListModel::UploadHierarchyListModel( : QAbstractListModel(parent), m_hierarchy(model.m_hierarchy), m_workingRoot(m_hierarchy.root()), - m_selectedIds() + m_selectedIds(model.m_selectedIds) { } @@ -229,6 +229,7 @@ void UploadHierarchyListModel::setHierarchy(const UploadHierarchyResp &uhr) beginResetModel(); m_hierarchy = uhr; m_workingRoot = m_hierarchy.root(); + m_selectedIds.clear(); endResetModel(); } @@ -283,6 +284,10 @@ QString UploadHierarchyListModel::navigatedRootName(bool takeSuper, } } + if ((!sep.isEmpty()) && (0 != rootName.indexOf(sep))) { + rootName = sep + rootName; + } + return rootName; } diff --git a/src/records_management/models/upload_hierarchy_list_model.h b/src/records_management/models/upload_hierarchy_list_model.h index c04f5e14..138e5751 100644 --- a/src/records_management/models/upload_hierarchy_list_model.h +++ b/src/records_management/models/upload_hierarchy_list_model.h @@ -169,6 +169,8 @@ public: /*! * @brief Toggles the node selection. * + * @note Emits dataChanged() signal. + * * @param[in] row Sub-node index to be used. * @return True if selection was toggled. */ -- GitLab From 1cb59fab8217d29003e820c609fc7a8394a7a0b5 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Thu, 15 Feb 2018 16:05:06 +0100 Subject: [PATCH 49/63] Don't hide buttons in PageRecordsManagementUpload. --- qml/pages/PageRecordsManagementUpload.qml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index f8f36bed..19d9504e 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -70,7 +70,11 @@ Component { UploadHierarchyListModel { id: uploadHierarchyListModel + onDataChanged: { + uploadButton.enabled = (uploadHierarchyListModel.selectedIds().length > 0) + } onModelReset: { + uploadButton.enabled = (uploadHierarchyListModel.selectedIds().length > 0) uploadHierarchyProxyModel.sort() locationLabel.text = uploadHierarchyListModel.navigatedRootName(true, "/") } @@ -137,7 +141,7 @@ Component { } AccessibleButton { id: uploadButton - visible: false + enabled: false height: inputItemHeight font.pointSize: defaultTextFont.font.pointSize text: qsTr("Upload message") @@ -151,7 +155,7 @@ Component { } Row { id: navigateUpRow - spacing: formItemVerticalSpacing * 5 + spacing: formItemVerticalSpacing * 3 anchors.top: buttonRow.bottom anchors.left: parent.left anchors.right: parent.right @@ -282,8 +286,6 @@ Component { } else if (rSelectable) { uploadHierarchyListModel.toggleNodeSelection( uploadHierarchyProxyModel.mapToSource(index)) - uploadButton.visible = (uploadHierarchyListModel.selectedIds().length > 0) - hierarchyButton.visible = !uploadButton.visible } } -- GitLab From 2935bd6a998e90f5eaf0d72a9160352fb7b4e1e5 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Thu, 15 Feb 2018 18:09:48 +0100 Subject: [PATCH 50/63] Experimenting with Button containing an Image. --- qml/components/AccessibleButtonWithImage.qml | 67 ++++++++++++++++++++ qml/pages/PageRecordsManagementUpload.qml | 8 ++- res/qml.qrc | 1 + src/main.cpp | 1 + 4 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 qml/components/AccessibleButtonWithImage.qml diff --git a/qml/components/AccessibleButtonWithImage.qml b/qml/components/AccessibleButtonWithImage.qml new file mode 100644 index 00000000..4cbf8bc4 --- /dev/null +++ b/qml/components/AccessibleButtonWithImage.qml @@ -0,0 +1,67 @@ +/* + * 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. + */ + +import QtGraphicalEffects 1.0 +import QtQuick 2.7 +import QtQuick.Controls 2.2 + +/* + * Accessible button component. + */ +Button { + id: root + + /* These properties must be set by caller. */ + property string accessibleDescription: "" + property string accessibleName: "" + property string source: "" + property int sourceHeight: 0.0 + + /* + * TODO -- Setting the image as root.contentItem causes the image to resize + * and blur. + */ + Image { + anchors.centerIn: parent + source: root.source + sourceSize.height: root.sourceHeight + opacity: enabled || root.highlighted || root.checked ? 1 : 0.3 + ColorOverlay { + anchors.fill: parent + source: parent + color: root.contentItem.color // Current contentItem is a Text item inherited from Button. + } + horizontalAlignment: Image.AlignHCenter + verticalAlignment: Image.AlignVCenter + } + + Accessible.role: Accessible.Button + Accessible.checkable: root.checkable + Accessible.checked: root.checked + Accessible.description: root.accessibleDescription + Accessible.name: (root.accessibleName !== "") ? root.accessibleName : root.text + Accessible.pressed: root.pressed + Accessible.onPressAction: { + root.clicked() + } +} diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index 19d9504e..fdb496d9 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -130,11 +130,13 @@ Component { spacing: formItemVerticalSpacing * 5 anchors.top: topText.bottom anchors.horizontalCenter: parent.horizontalCenter - AccessibleButton { + AccessibleButtonWithImage { id: hierarchyButton height: inputItemHeight - font.pointSize: defaultTextFont.font.pointSize - text: qsTr("Update upload hierarchy") + accessibleName: qsTr("Update upload hierarchy") + source: "qrc:/ui/sync.svg" + sourceHeight: imgHeightHeader + onClicked: { downloadUploadHierarchy() } diff --git a/res/qml.qrc b/res/qml.qrc index 95a145e8..125c6f53 100644 --- a/res/qml.qrc +++ b/res/qml.qrc @@ -102,6 +102,7 @@ cznic.png ui/datovka@2x.png ../qml/components/AccessibleButton.qml + ../qml/components/AccessibleButtonWithImage.qml ../qml/components/AccessibleComboBox.qml ../qml/components/AccessibleImageButton.qml ../qml/components/AccessibleMenu.qml diff --git a/src/main.cpp b/src/main.cpp index ca89da45..9fac5faf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -133,6 +133,7 @@ const struct QmlTypeEntry qmlPages[] = { static const struct QmlTypeEntry qmlComponents[] = { { "AccessibleButton", 1, 0 }, + { "AccessibleButtonWithImage", 1, 0 }, { "AccessibleComboBox", 1, 0 }, { "AccessibleImageButton", 1, 0 }, { "AccessibleMenu", 1, 0 }, -- GitLab From 7115a762db971d3bd4fbfb8c32ecd31d7df89538 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Fri, 16 Feb 2018 09:35:45 +0100 Subject: [PATCH 51/63] Using upload icon in records management upload page. --- qml/pages/PageRecordsManagementUpload.qml | 8 ++-- res/qml.qrc | 1 + res/ui/briefcase.svg | 0 res/ui/upload.svg | 58 +++++++++++++++++++++++ 4 files changed, 63 insertions(+), 4 deletions(-) mode change 100755 => 100644 res/ui/briefcase.svg create mode 100644 res/ui/upload.svg diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index fdb496d9..311fff23 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -136,17 +136,17 @@ Component { accessibleName: qsTr("Update upload hierarchy") source: "qrc:/ui/sync.svg" sourceHeight: imgHeightHeader - onClicked: { downloadUploadHierarchy() } } - AccessibleButton { + AccessibleButtonWithImage { id: uploadButton enabled: false height: inputItemHeight - font.pointSize: defaultTextFont.font.pointSize - text: qsTr("Upload message") + accessibleName: qsTr("Upload message") + source: "qrc:/ui/upload.svg" + sourceHeight: imgHeightHeader onClicked: { if (recordsManagement.uploadMessage(userName, msgId, msgType, uploadHierarchyListModel)) { messages.updateRmStatus(messageModel, userName, msgId, true) diff --git a/res/qml.qrc b/res/qml.qrc index 125c6f53..1ac62a24 100644 --- a/res/qml.qrc +++ b/res/qml.qrc @@ -98,6 +98,7 @@ ui/sync.svg ui/sync-all.svg ui/up.svg + ui/upload.svg datovka.png cznic.png ui/datovka@2x.png diff --git a/res/ui/briefcase.svg b/res/ui/briefcase.svg old mode 100755 new mode 100644 diff --git a/res/ui/upload.svg b/res/ui/upload.svg new file mode 100644 index 00000000..e8223af8 --- /dev/null +++ b/res/ui/upload.svg @@ -0,0 +1,58 @@ + + + + + + + + image/svg+xml + + + + + + + + + -- GitLab From 40e6ce315debdc09fc810d6f4abfdee5e53a278a Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Fri, 16 Feb 2018 11:59:25 +0100 Subject: [PATCH 52/63] Added UploadHierarchyListModel::selectedFullNames(). --- .../models/upload_hierarchy_list_model.cpp | 82 +++++++++++++------ .../models/upload_hierarchy_list_model.h | 21 +++-- 2 files changed, 71 insertions(+), 32 deletions(-) diff --git a/src/records_management/models/upload_hierarchy_list_model.cpp b/src/records_management/models/upload_hierarchy_list_model.cpp index 25bdfdf5..6a7bc2b1 100644 --- a/src/records_management/models/upload_hierarchy_list_model.cpp +++ b/src/records_management/models/upload_hierarchy_list_model.cpp @@ -36,7 +36,7 @@ UploadHierarchyListModel::UploadHierarchyListModel(QObject *parent) : QAbstractListModel(parent), m_hierarchy(), m_workingRoot(Q_NULLPTR), - m_selectedIds() + m_selected() { } @@ -45,7 +45,7 @@ UploadHierarchyListModel::UploadHierarchyListModel( : QAbstractListModel(parent), m_hierarchy(model.m_hierarchy), m_workingRoot(m_hierarchy.root()), - m_selectedIds(model.m_selectedIds) + m_selected(model.m_selected) { } @@ -191,10 +191,10 @@ QVariant UploadHierarchyListModel::data(const QModelIndex &index, return isSelectable(entry); break; case ROLE_SELECTED: - return idInSet(entry, m_selectedIds, false); + return idInSelection(entry, m_selected, false); break; case ROLE_SELECTED_RECURSIVE: - return idInSet(entry, m_selectedIds, true); + return idInSelection(entry, m_selected, true); break; default: return QVariant(); @@ -229,7 +229,7 @@ void UploadHierarchyListModel::setHierarchy(const UploadHierarchyResp &uhr) beginResetModel(); m_hierarchy = uhr; m_workingRoot = m_hierarchy.root(); - m_selectedIds.clear(); + m_selected.clear(); endResetModel(); } @@ -268,27 +268,48 @@ void UploadHierarchyListModel::navigateSub(int row) } } -QString UploadHierarchyListModel::navigatedRootName(bool takeSuper, - const QString &sep) const +/*! + * @brief Return node name. + * + * @param[in] node Node pointer. + * @param[in] takeSuper If true, then full path across all + * superordinate nodes is taken. + * @param[in] sep Separator to be used. It is always appended to + * the end of the returned string. + * @return Name or path of names to the node. + */ +static +QString nodeName(const UploadHierarchyResp::NodeEntry *node, bool takeSuper, + const QString &sep) { - if ((!m_hierarchy.isValid() || (m_workingRoot == Q_NULLPTR))) { - return sep; + if (Q_UNLIKELY(node == Q_NULLPTR)) { + return QString(); } - QString rootName(m_workingRoot->name() + sep); + QString nodeName(node->name() + sep); if (takeSuper) { - const UploadHierarchyResp::NodeEntry *node = - m_workingRoot->super(); + node = node->super(); while ((node != Q_NULLPTR) && (!node->name().isEmpty())) { - rootName = node->name() + sep + rootName; + nodeName = node->name() + sep + nodeName; + node = node->super(); } } - if ((!sep.isEmpty()) && (0 != rootName.indexOf(sep))) { - rootName = sep + rootName; + if ((!sep.isEmpty()) && (0 != nodeName.indexOf(sep))) { + nodeName = sep + nodeName; + } + + return nodeName; +} + +QString UploadHierarchyListModel::navigatedRootName(bool takeSuper, + const QString &sep) const +{ + if ((!m_hierarchy.isValid() || (m_workingRoot == Q_NULLPTR))) { + return sep; } - return rootName; + return nodeName(m_workingRoot, takeSuper, sep); } bool UploadHierarchyListModel::toggleNodeSelection(int row) @@ -302,10 +323,11 @@ bool UploadHierarchyListModel::toggleNodeSelection(int row) const UploadHierarchyResp::NodeEntry *sub = m_workingRoot->sub().at(row); if ((sub != Q_NULLPTR) && isSelectable(sub)) { - if (idInSet(sub, m_selectedIds, false)) { - m_selectedIds.remove(sub->id()); + if (idInSelection(sub, m_selected, false)) { + m_selected.remove(sub->id()); } else { - m_selectedIds.insert(sub->id()); + m_selected.insert(sub->id(), + nodeName(sub, true, "/")); /* TODO -- Make the separator configurable. */ } QModelIndex changedIdx(index(row, 0, QModelIndex())); QVector roles(2); @@ -321,7 +343,14 @@ bool UploadHierarchyListModel::toggleNodeSelection(int row) QStringList UploadHierarchyListModel::selectedIds(void) const { - return m_selectedIds.toList(); + return m_selected.keys(); +} + +QStringList UploadHierarchyListModel::selectedFullNames(bool sort) const +{ + /* TODO -- Enable sorting. */ + + return m_selected.values(); } UploadHierarchyListModel *UploadHierarchyListModel::fromVariant( @@ -336,7 +365,8 @@ UploadHierarchyListModel *UploadHierarchyListModel::fromVariant( bool UploadHierarchyListModel::showRootName(void) const { - return m_hierarchy.isValid() && (m_workingRoot != Q_NULLPTR) && !m_workingRoot->name().isEmpty(); + return m_hierarchy.isValid() && (m_workingRoot != Q_NULLPTR) && + !m_workingRoot->name().isEmpty(); } QStringList UploadHierarchyListModel::filterData( @@ -379,9 +409,9 @@ QStringList UploadHierarchyListModel::filterDataRecursive( return res; } -bool UploadHierarchyListModel::idInSet( - const UploadHierarchyResp::NodeEntry *entry, const QSet &idSet, - bool takeSub) +bool UploadHierarchyListModel::idInSelection( + const UploadHierarchyResp::NodeEntry *entry, + const QMap &idMap, bool takeSub) { if (Q_UNLIKELY(entry == Q_NULLPTR)) { Q_ASSERT(0); @@ -391,13 +421,13 @@ bool UploadHierarchyListModel::idInSet( int present = false; const QString &id(entry->id()); if (!id.isEmpty()) { - present = idSet.contains(id); + present = idMap.contains(id); } if (takeSub) { int i = 0; while ((!present) && (i < entry->sub().size())) { - present = idInSet(entry->sub().at(i), idSet, true); + present = idInSelection(entry->sub().at(i), idMap, true); ++i; } } diff --git a/src/records_management/models/upload_hierarchy_list_model.h b/src/records_management/models/upload_hierarchy_list_model.h index 138e5751..fe151bc5 100644 --- a/src/records_management/models/upload_hierarchy_list_model.h +++ b/src/records_management/models/upload_hierarchy_list_model.h @@ -24,7 +24,7 @@ #pragma once #include -#include +#include #include #include "src/datovka_shared/records_management/json/upload_hierarchy.h" @@ -178,13 +178,22 @@ public: bool toggleNodeSelection(int row); /*! - * @brief return unsorted list of selected node identifiers. + * @brief Return unsorted list of selected node identifiers. * * @return List of node identifiers. */ Q_INVOKABLE QStringList selectedIds(void) const; + /*! + * @brief Return list of selected node names. + * + * @param[in] sort Set to true if list should be sorted alpabetically. + * @return List of full names. + */ + Q_INVOKABLE + QStringList selectedFullNames(bool sort) const; + /*! * @brief Converts QVariant obtained from QML into model pointer. * @@ -233,19 +242,19 @@ private: * @brief Check whether identifier of node is in set. * * @param[in] entry Node entry. - * @param[in] idSet Identifier set to search in. + * @param[in] idMap Identifier map to search in. * @param[in] takeSub Whether sub-nodes are to be taken into * consideration. * @return true if node identifier is in set or any of its sub-nodes if * takeSub enabled. */ static - bool idInSet(const UploadHierarchyResp::NodeEntry *entry, - const QSet &idSet, bool takeSub); + bool idInSelection(const UploadHierarchyResp::NodeEntry *entry, + const QMap &idMap, bool takeSub); UploadHierarchyResp m_hierarchy; /*!< Upload hierarchy structure. */ const UploadHierarchyResp::NodeEntry *m_workingRoot; /*!< Node acting as working root. */ - QSet m_selectedIds; /*!< List of selected identifiers. */ + QMap m_selected; /*!< Keys are ids, values are full names. */ }; /* QML passes its arguments via QVariant. */ -- GitLab From f0b3afc68251d78f1c52b5f8c4de9fd5142b2c72 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Fri, 16 Feb 2018 12:42:11 +0100 Subject: [PATCH 53/63] Enabled selected hierarchy names sorting. --- .../models/upload_hierarchy_list_model.cpp | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/records_management/models/upload_hierarchy_list_model.cpp b/src/records_management/models/upload_hierarchy_list_model.cpp index 6a7bc2b1..88b4a18d 100644 --- a/src/records_management/models/upload_hierarchy_list_model.cpp +++ b/src/records_management/models/upload_hierarchy_list_model.cpp @@ -21,8 +21,10 @@ * the two. */ +#include /* std::sort */ #include /* qmlRegisterType */ +#include "src/datovka_shared/localisation/localisation.h" #include "src/records_management/models/upload_hierarchy_list_model.h" void UploadHierarchyListModel::declareQML(void) @@ -346,11 +348,26 @@ QStringList UploadHierarchyListModel::selectedIds(void) const return m_selected.keys(); } +/*! + * @brief Used for sorting hierarchy names. + */ +class HierarchyNameLess { +public: + bool operator()(const QString &a, const QString &b) const + { + return Localisation::stringCollator.compare(a, b) < 0; + } +}; + QStringList UploadHierarchyListModel::selectedFullNames(bool sort) const { - /* TODO -- Enable sorting. */ + QStringList values(m_selected.values()); + + if (sort) { + ::std::sort(values.begin(), values.end(), HierarchyNameLess()); + } - return m_selected.values(); + return values; } UploadHierarchyListModel *UploadHierarchyListModel::fromVariant( -- GitLab From 8ef3eb64a5badff67c021fe78b3c593275999919 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Fri, 16 Feb 2018 12:53:07 +0100 Subject: [PATCH 54/63] Hid some private static methods from UploadHierarchyListModel interface. --- .../models/upload_hierarchy_list_model.cpp | 183 ++++++++++-------- .../models/upload_hierarchy_list_model.h | 36 ---- 2 files changed, 105 insertions(+), 114 deletions(-) diff --git a/src/records_management/models/upload_hierarchy_list_model.cpp b/src/records_management/models/upload_hierarchy_list_model.cpp index 88b4a18d..1806bc7b 100644 --- a/src/records_management/models/upload_hierarchy_list_model.cpp +++ b/src/records_management/models/upload_hierarchy_list_model.cpp @@ -152,10 +152,102 @@ QHash UploadHierarchyListModel::roleNames(void) const return roles; } +/*! + * @brief Return all data related to node which can be used for + * filtering. + * + * @param[in] entry Node identifier. + * @return List of strings. + */ +static +QStringList filterData(const UploadHierarchyResp::NodeEntry *entry) +{ + if (Q_UNLIKELY(entry == Q_NULLPTR)) { + Q_ASSERT(0); + return QStringList(); + } + + return QStringList(entry->name()) + entry->metadata(); +} + +/*! + * @brief Returns list of all (meta)data (including children). + * + * @param[in] entry Node identifying the root. + * @param[in] takeSuper Set true when data of superordinate node should + * be taken into account. + * @return List of all gathered data according to which can be filtered. + */ +static +QStringList filterDataRecursive( + const UploadHierarchyResp::NodeEntry *entry, bool takeSuper) +{ + if (Q_UNLIKELY(entry == Q_NULLPTR)) { + Q_ASSERT(0); + return QStringList(); + } + + QStringList res(filterData(entry)); + foreach (const UploadHierarchyResp::NodeEntry *sub, entry->sub()) { + res += filterDataRecursive(sub, false); + } + if (takeSuper) { + /* + * Add also filter data from superordinate node. This has the + * effect that all sub-nodes (including those not matching the + * filter) of a node which matches the entered filter are + * going to be also displayed. + */ + const UploadHierarchyResp::NodeEntry *sup = entry->super(); + if (Q_UNLIKELY(sup == Q_NULLPTR)) { + return res; + } + res += filterData(sup); + } + + return res; +} + /* Return true if entry is selectable. */ #define isSelectable(entry) \ (!(entry)->id().isEmpty()) +/*! + * @brief Check whether identifier of node is in set. + * + * @param[in] entry Node entry. + * @param[in] idMap Identifier map to search in. + * @param[in] takeSub Whether sub-nodes are to be taken into + * consideration. + * @return true if node identifier is in set or any of its sub-nodes if + * takeSub enabled. + */ +static +bool idInSelection(const UploadHierarchyResp::NodeEntry *entry, + const QMap &idMap, bool takeSub) +{ + if (Q_UNLIKELY(entry == Q_NULLPTR)) { + Q_ASSERT(0); + return false; + } + + int present = false; + const QString &id(entry->id()); + if (!id.isEmpty()) { + present = idMap.contains(id); + } + + if (takeSub) { + int i = 0; + while ((!present) && (i < entry->sub().size())) { + present = idInSelection(entry->sub().at(i), idMap, true); + ++i; + } + } + + return present; +} + QVariant UploadHierarchyListModel::data(const QModelIndex &index, int role) const { @@ -271,29 +363,30 @@ void UploadHierarchyListModel::navigateSub(int row) } /*! - * @brief Return node name. + * @brief Return entry name. * - * @param[in] node Node pointer. + * @param[in] entry Entry pointer. * @param[in] takeSuper If true, then full path across all * superordinate nodes is taken. * @param[in] sep Separator to be used. It is always appended to * the end of the returned string. - * @return Name or path of names to the node. + * @return Name or path of names to the entry. */ static -QString nodeName(const UploadHierarchyResp::NodeEntry *node, bool takeSuper, +QString entryName(const UploadHierarchyResp::NodeEntry *entry, bool takeSuper, const QString &sep) { - if (Q_UNLIKELY(node == Q_NULLPTR)) { + if (Q_UNLIKELY(entry == Q_NULLPTR)) { + Q_ASSERT(0); return QString(); } - QString nodeName(node->name() + sep); + QString nodeName(entry->name() + sep); if (takeSuper) { - node = node->super(); - while ((node != Q_NULLPTR) && (!node->name().isEmpty())) { - nodeName = node->name() + sep + nodeName; - node = node->super(); + entry = entry->super(); + while ((entry != Q_NULLPTR) && (!entry->name().isEmpty())) { + nodeName = entry->name() + sep + nodeName; + entry = entry->super(); } } @@ -311,7 +404,7 @@ QString UploadHierarchyListModel::navigatedRootName(bool takeSuper, return sep; } - return nodeName(m_workingRoot, takeSuper, sep); + return entryName(m_workingRoot, takeSuper, sep); } bool UploadHierarchyListModel::toggleNodeSelection(int row) @@ -329,7 +422,7 @@ bool UploadHierarchyListModel::toggleNodeSelection(int row) m_selected.remove(sub->id()); } else { m_selected.insert(sub->id(), - nodeName(sub, true, "/")); /* TODO -- Make the separator configurable. */ + entryName(sub, true, "/")); /* TODO -- Make the separator configurable. */ } QModelIndex changedIdx(index(row, 0, QModelIndex())); QVector roles(2); @@ -385,69 +478,3 @@ bool UploadHierarchyListModel::showRootName(void) const return m_hierarchy.isValid() && (m_workingRoot != Q_NULLPTR) && !m_workingRoot->name().isEmpty(); } - -QStringList UploadHierarchyListModel::filterData( - const UploadHierarchyResp::NodeEntry *entry) -{ - if (Q_UNLIKELY(entry == Q_NULLPTR)) { - Q_ASSERT(0); - return QStringList(); - } - - return QStringList(entry->name()) + entry->metadata(); -} - -QStringList UploadHierarchyListModel::filterDataRecursive( - const UploadHierarchyResp::NodeEntry *entry, bool takeSuper) -{ - if (Q_UNLIKELY(entry == Q_NULLPTR)) { - Q_ASSERT(0); - return QStringList(); - } - - QStringList res(filterData(entry)); - foreach (const UploadHierarchyResp::NodeEntry *sub, entry->sub()) { - res += filterDataRecursive(sub, false); - } - if (takeSuper) { - /* - * Add also filter data from superordinate node. This has the - * effect that all sub-nodes (including those not matching the - * filter) of a node which matches the entered filter are - * going to be also displayed. - */ - const UploadHierarchyResp::NodeEntry *sup = entry->super(); - if (Q_UNLIKELY(sup == Q_NULLPTR)) { - return res; - } - res += filterData(sup); - } - - return res; -} - -bool UploadHierarchyListModel::idInSelection( - const UploadHierarchyResp::NodeEntry *entry, - const QMap &idMap, bool takeSub) -{ - if (Q_UNLIKELY(entry == Q_NULLPTR)) { - Q_ASSERT(0); - return false; - } - - int present = false; - const QString &id(entry->id()); - if (!id.isEmpty()) { - present = idMap.contains(id); - } - - if (takeSub) { - int i = 0; - while ((!present) && (i < entry->sub().size())) { - present = idInSelection(entry->sub().at(i), idMap, true); - ++i; - } - } - - return present; -} diff --git a/src/records_management/models/upload_hierarchy_list_model.h b/src/records_management/models/upload_hierarchy_list_model.h index fe151bc5..776d0ccd 100644 --- a/src/records_management/models/upload_hierarchy_list_model.h +++ b/src/records_management/models/upload_hierarchy_list_model.h @@ -216,42 +216,6 @@ private: */ bool showRootName(void) const; - /*! - * @brief Return all data related to node which can be used for - * filtering. - * - * @param[in] entry Node identifier. - * @return List of strings. - */ - static - QStringList filterData(const UploadHierarchyResp::NodeEntry *entry); - - /*! - * @brief Returns list of all (meta)data (including children). - * - * @param[in] entry Node identifying the root. - * @param[in] takeSuper Set true when data of superordinate node should - * be taken into account. - * @return List of all gathered data according to which can be filtered. - */ - static - QStringList filterDataRecursive( - const UploadHierarchyResp::NodeEntry *entry, bool takeSuper); - - /*! - * @brief Check whether identifier of node is in set. - * - * @param[in] entry Node entry. - * @param[in] idMap Identifier map to search in. - * @param[in] takeSub Whether sub-nodes are to be taken into - * consideration. - * @return true if node identifier is in set or any of its sub-nodes if - * takeSub enabled. - */ - static - bool idInSelection(const UploadHierarchyResp::NodeEntry *entry, - const QMap &idMap, bool takeSub); - UploadHierarchyResp m_hierarchy; /*!< Upload hierarchy structure. */ const UploadHierarchyResp::NodeEntry *m_workingRoot; /*!< Node acting as working root. */ QMap m_selected; /*!< Keys are ids, values are full names. */ -- GitLab From 2f3a5ce08747ed403bb9957bb9170641c7b20b98 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Fri, 16 Feb 2018 13:05:37 +0100 Subject: [PATCH 55/63] Moved update list of uploded filed to main settings menu --- qml/pages/PageMenuDatovkaSettings.qml | 59 +++-- qml/pages/PageSettingsRecordsManagement.qml | 256 +++++++++----------- 2 files changed, 157 insertions(+), 158 deletions(-) diff --git a/qml/pages/PageMenuDatovkaSettings.qml b/qml/pages/PageMenuDatovkaSettings.qml index 428c8269..0c0e14b6 100644 --- a/qml/pages/PageMenuDatovkaSettings.qml +++ b/qml/pages/PageMenuDatovkaSettings.qml @@ -36,10 +36,26 @@ Component { property var accountModel: null Component.onCompleted: { + var index if (accountModel.rowCount() <= 0) { - // Hide some menu items - // Note: remove argument is ListModel item index - settingsMenuListModel.remove(1) + index = settingsMenuList.get_model_index(settingsMenuListModel, "funcName", "searchMsg"); + if (index >= 0) { + settingsMenuListModel.setProperty(index, "showEntry", false) + } + index = settingsMenuList.get_model_index(settingsMenuListModel, "funcName", "importMsg"); + if (index >= 0) { + settingsMenuListModel.setProperty(index, "showEntry", false) + } + index = settingsMenuList.get_model_index(settingsMenuListModel, "funcName", "settRecMan"); + if (index >= 0) { + settingsMenuListModel.setProperty(index, "showEntry", false) + } + } + if (recordsManagement.isValidRecordsManagement()) { + index = settingsMenuList.get_model_index(settingsMenuListModel, "funcName", "updateRecManData"); + if (index >= 0) { + settingsMenuListModel.setProperty(index, "showEntry", true) + } } } @@ -77,6 +93,16 @@ Component { "accountModel": accountModel }, StackView.Immediate) }, + "updateRecManData": function callUpdateRecManData() { + recordsManagement.getStoredMsgInfoFromRecordsManagement(settings.rmUrl(), settings.rmToken()) + pageView.pop(StackView.Immediate) + }, + "settRecMan": function callSettRecMan() { + pageView.replace(pageSettingsRecordsManagement, { + "pageView": pageView, + "statusBar": statusBar + }, StackView.Immediate) + }, "settGeneral": function callSettGeneral() { pageView.replace(pageSettingsGeneral, { "pageView": pageView, @@ -105,12 +131,6 @@ Component { "accountModel": accountModel }, StackView.Immediate) }, - "settRecMan": function callSettRecMan() { - pageView.replace(pageSettingsRecordsManagement, { - "pageView": pageView, - "statusBar": statusBar - }, StackView.Immediate) - }, "userGuide": function callUserGuide() { Qt.openUrlExternally("https://secure.nic.cz/files/datove_schranky/redirect/mobile-manual.html") pageView.pop(StackView.Immediate) @@ -140,6 +160,20 @@ Component { name: qsTr("Import message") funcName: "importMsg" } + ListElement { + image: "qrc:/ui/briefcase.svg" + showEntry: false + showNext: false + name: qsTr("Update list of uploaded files") + funcName: "updateRmData" + } + ListElement { + image: "qrc:/ui/briefcase.svg" + showEntry: true + showNext: true + name: qsTr("Records Management") + funcName: "settRecMan" + } ListElement { image: "qrc:/ui/settings.svg" showEntry: true @@ -168,13 +202,6 @@ Component { name: qsTr("Security and PIN") funcName: "settSecPin" } - ListElement { - image: "qrc:/ui/briefcase.svg" - showEntry: true - showNext: true - name: qsTr("Records Management") - funcName: "settRecMan" - } ListElement { image: "qrc:/ui/information.svg" showEntry: true diff --git a/qml/pages/PageSettingsRecordsManagement.qml b/qml/pages/PageSettingsRecordsManagement.qml index 07135516..0c3bc0b2 100644 --- a/qml/pages/PageSettingsRecordsManagement.qml +++ b/qml/pages/PageSettingsRecordsManagement.qml @@ -41,27 +41,28 @@ Item { clearButton.enabled = infoButton.enabled } + /* Get optimal logo size base on visible screen size */ + function getOptimalLogoSize() { + var logoSize = pageSettingsRecordsManagement.height / 4 + return (logoSize < imgHeightHeader) ? imgHeightHeader : logoSize + } + /* Clear all data and info */ function clearAll() { - updateRmData.visible = false urlTextField.clear() tokenTextField.clear() - serviceInfo.visible = false + serviceInfoSection.visible = false areUrlandTokenFilled() - infoButton.text = qsTr("Get service info") acceptElement.visible = true - userNote.visible = true } Component.onCompleted: { - serviceInfo.visible = false + serviceInfoSection.visible = false urlTextField.text = settings.rmUrl() lastUrlFromSettings = settings.rmUrl() tokenTextField.text = settings.rmToken() areUrlandTokenFilled() recordsManagement.loadStoredServiceInfo() - updateRmData.visible = recordsManagement.isValidRecordsManagement() - userNote.visible = !updateRmData.visible } PageHeader { @@ -105,112 +106,95 @@ Item { Column { anchors.right: parent.right anchors.left: parent.left - spacing: formItemVerticalSpacing + spacing: formItemVerticalSpacing * 3 Column { - id: updateRmData + id: serviceInputSection width: parent.width - spacing: formItemVerticalSpacing - visible: false + //spacing: formItemVerticalSpacing AccessibleText { + id: userNote color: datovkaPalette.mid wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter width: parent.width - text: qsTr("Button below updates the information about data messages uploaded into the records management service.") + text: qsTr("Please fill in service URL and your identification token. Click button to log into records management service. You should receive valid service info. Finally, click the top right button to save the settings.") } - AccessibleButton { - id: updateRmDataButton + AccessibleText { + color: datovkaPalette.text + text: qsTr("Service URL") + } + AccessibleTextField { + id: urlTextField + width: parent.width height: inputItemHeight font.pointSize: defaultTextFont.font.pointSize - anchors.horizontalCenter: parent.horizontalCenter - text: qsTr("Update list of uploaded files") - onClicked: { - recordsManagement.getStoredMsgInfoFromRecordsManagement(urlTextField.text, tokenTextField.text) + placeholderText: qsTr("Enter service url") + InputLineMenu { + id: urlMenu + inputTextControl: urlTextField + isPassword: false } - } - } // Column - AccessibleText { - id: userNote - color: datovkaPalette.mid - wrapMode: Text.Wrap - horizontalAlignment: Text.AlignHCenter - width: parent.width - text: qsTr("Please fill in service URL and your identification token. Click button '%1' to log into the records management service. You should receive information about the service. Finally, click the top right button to save the settings.").arg(infoButton.text) - } - AccessibleText { - color: datovkaPalette.text - text: qsTr("URL") - } - AccessibleTextField { - id: urlTextField - width: parent.width - height: inputItemHeight - font.pointSize: defaultTextFont.font.pointSize - placeholderText: qsTr("Enter url") - InputLineMenu { - id: urlMenu - inputTextControl: urlTextField - isPassword: false - } - onPressAndHold: { - if (settings.useExplicitClipboardOperations()) { - urlMenu.implicitWidth = computeMenuWidth(urlMenu) - urlMenu.open() + onPressAndHold: { + if (settings.useExplicitClipboardOperations()) { + urlMenu.implicitWidth = computeMenuWidth(urlMenu) + urlMenu.open() + } } - } - onTextChanged: { - areUrlandTokenFilled() - } - } - AccessibleText { - color: datovkaPalette.text - text: qsTr("Token") - } - AccessibleTextField { - id: tokenTextField - width: parent.width - height: inputItemHeight - font.pointSize: defaultTextFont.font.pointSize - placeholderText: qsTr("Enter token") - InputLineMenu { - id: tokenMenu - inputTextControl: tokenTextField - isPassword: false - } - onPressAndHold: { - if (settings.useExplicitClipboardOperations()) { - tokenMenu.implicitWidth = computeMenuWidth(tokenMenu) - tokenMenu.open() + onTextChanged: { + areUrlandTokenFilled() } } - onTextChanged: { - areUrlandTokenFilled() + AccessibleText { + color: datovkaPalette.text + text: qsTr("Your token") } - } - Row { - spacing: formItemVerticalSpacing * 5 - anchors.horizontalCenter: parent.horizontalCenter - AccessibleButton { - id: infoButton + AccessibleTextField { + id: tokenTextField + width: parent.width height: inputItemHeight font.pointSize: defaultTextFont.font.pointSize - text: qsTr("Get service info") - onClicked: { - serviceInfo.visible = recordsManagement.callServiceInfo(urlTextField.text, tokenTextField.text) - serviceInfoError.visible = !serviceInfo.visible - acceptElement.visible = serviceInfo.visible + placeholderText: qsTr("Enter your token") + InputLineMenu { + id: tokenMenu + inputTextControl: tokenTextField + isPassword: false } - } - AccessibleButton { - id: clearButton - height: inputItemHeight - font.pointSize: defaultTextFont.font.pointSize - text: qsTr("Clear") - onClicked: { - clearAll() + onPressAndHold: { + if (settings.useExplicitClipboardOperations()) { + tokenMenu.implicitWidth = computeMenuWidth(tokenMenu) + tokenMenu.open() + } + } + onTextChanged: { + areUrlandTokenFilled() } } - } // Row + Row { + spacing: formItemVerticalSpacing * 5 + anchors.horizontalCenter: parent.horizontalCenter + AccessibleButton { + id: infoButton + height: inputItemHeight + font.pointSize: defaultTextFont.font.pointSize + text: qsTr("Get service info") + onClicked: { + serviceInfoSection.visible = recordsManagement.callServiceInfo(urlTextField.text, tokenTextField.text) + serviceInfoError.visible = !serviceInfoSection.visible + acceptElement.visible = serviceInfoSection.visible + } + } + AccessibleButtonWithImage { + id: clearButton + height: inputItemHeight + accessibleName: qsTr("Clear records management data") + source: "qrc:/ui/remove.svg" + sourceHeight: imgHeightHeader + onClicked: { + clearAll() + } + } + } // Row + } // Column AccessibleText { id: serviceInfoError visible: false @@ -219,53 +203,45 @@ Item { width: parent.width text: qsTr("Communication error. Cannot obtain records management info from server. Internet connection failed or service url and identification token can be wrong!") } - Column { - id: serviceInfo - width: parent.width + Grid { + id: serviceInfoSection + columns: 2 spacing: formItemVerticalSpacing AccessibleText { font.pointSize: defaultTextFont.font.pointSize - text: qsTr("Records management info") + ":" + text: qsTr("Service name") + ":" } - Grid { - columns: 2 - spacing: formItemVerticalSpacing - AccessibleText { - font.pointSize: defaultTextFont.font.pointSize - text: qsTr("Name") + ":" - } - AccessibleText { - id: serviceName - font.pointSize: defaultTextFont.font.pointSize - font.bold: true - text: "" - } - AccessibleText { - font.pointSize: defaultTextFont.font.pointSize - text: qsTr("Token") + ":" - } - AccessibleText { - id: tokenName - font.pointSize: defaultTextFont.font.pointSize - font.bold: true - text: "" - } - AccessibleText { - height: imgHeightHeader - font.pointSize: defaultTextFont.font.pointSize - verticalAlignment: Text.AlignVCenter - text: qsTr("Logo") + ":" - } - AccessibleImageButton { - id: serviceLogo - width: imgHeightHeader - height: imgHeightHeader - source: "qrc:/ui/briefcase.svg" - cache: false - accessibleName: qsTr("Records management logo") - } - } // Grid - } // Column + AccessibleText { + id: serviceName + font.pointSize: defaultTextFont.font.pointSize + font.bold: true + text: "" + } + AccessibleText { + font.pointSize: defaultTextFont.font.pointSize + text: qsTr("Service token") + ":" + } + AccessibleText { + id: tokenName + font.pointSize: defaultTextFont.font.pointSize + font.bold: true + text: "" + } + AccessibleText { + height: imgHeightHeader + font.pointSize: defaultTextFont.font.pointSize + verticalAlignment: Text.AlignVCenter + text: qsTr("Service logo") + ":" + } + AccessibleImageButton { + id: serviceLogo + width: getOptimalLogoSize() + height: serviceLogo.width + source: "qrc:/ui/briefcase.svg" + cache: false + accessibleName: qsTr("Records management logo") + } + } // Grid } // Column } // Pane ScrollIndicator.vertical: ScrollIndicator {} @@ -274,18 +250,14 @@ Item { target: recordsManagement onServiceInfo: { if (srName !== "" && srToken !== "") { - serviceInfo.visible = true - userNote.visible = false + serviceInfoSection.visible = true acceptElement.visible = true - infoButton.text = (serviceInfo.visible) ? qsTr("Update service info") : qsTr("Get service info") serviceName.text = srName tokenName.text = srToken serviceInfoError.visible = false serviceLogo.source = "image://images/rmlogo.svg" } else { - serviceInfo.visible = false - updateRmData.visible = serviceInfo.visible - userNote.visible = !serviceInfo.visible + serviceInfoSection.visible = false } } } -- GitLab From 967d1d5c9d62b51f41031515c40cbe2033b83370 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Fri, 16 Feb 2018 13:14:50 +0100 Subject: [PATCH 56/63] Fixed wrong func name in the main settings menu --- qml/pages/PageMenuDatovkaSettings.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qml/pages/PageMenuDatovkaSettings.qml b/qml/pages/PageMenuDatovkaSettings.qml index 0c0e14b6..552a67ad 100644 --- a/qml/pages/PageMenuDatovkaSettings.qml +++ b/qml/pages/PageMenuDatovkaSettings.qml @@ -165,7 +165,7 @@ Component { showEntry: false showNext: false name: qsTr("Update list of uploaded files") - funcName: "updateRmData" + funcName: "updateRecManData" } ListElement { image: "qrc:/ui/briefcase.svg" -- GitLab From a901905568818ec0cf2c4cd02393422727fb71d4 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Fri, 16 Feb 2018 13:37:30 +0100 Subject: [PATCH 57/63] Viewing selected hierarchy location when uploading to document service. --- qml/pages/PageRecordsManagementUpload.qml | 33 ++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index 311fff23..b69b516e 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -24,7 +24,7 @@ import QtGraphicalEffects 1.0 import QtQml 2.2 import QtQuick 2.7 -import QtQuick.Controls 2.1 +import QtQuick.Controls 2.2 import cz.nic.mobileDatovka 1.0 import cz.nic.mobileDatovka.models 1.0 @@ -66,17 +66,29 @@ Component { onTriggered: { downloadUploadHierarchy() } - } + } + + FontMetrics { + id: fontMetrics + font: selectedLocations.text + } + + function viewSelectedNodeNames() { + var nameList = uploadHierarchyListModel.selectedFullNames(true) + selectedLocations.text = nameList.join(" sd fkjs asdjfdsaj sadjf sd fsdjf \n") + } UploadHierarchyListModel { id: uploadHierarchyListModel onDataChanged: { uploadButton.enabled = (uploadHierarchyListModel.selectedIds().length > 0) + viewSelectedNodeNames() } onModelReset: { uploadButton.enabled = (uploadHierarchyListModel.selectedIds().length > 0) uploadHierarchyProxyModel.sort() locationLabel.text = uploadHierarchyListModel.navigatedRootName(true, "/") + viewSelectedNodeNames() } Component.onCompleted: { } @@ -155,10 +167,25 @@ Component { } } } + ScrollView { + id: selectedLocationsView + anchors.top: buttonRow.bottom + anchors.left: parent.left + anchors.right: parent.right + clip: true + height: fontMetrics.height * 5 + + ScrollBar.horizontal.policy: ScrollBar.AsNeeded + ScrollBar.vertical.policy: ScrollBar.AsNeeded + + AccessibleText { + id: selectedLocations + } + } Row { id: navigateUpRow spacing: formItemVerticalSpacing * 3 - anchors.top: buttonRow.bottom + anchors.top: selectedLocationsView.bottom anchors.left: parent.left anchors.right: parent.right AccessibleButton { -- GitLab From c73d55f4768f69cf61ffe30bb6d6042fc8fb37e4 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Fri, 16 Feb 2018 13:39:10 +0100 Subject: [PATCH 58/63] Removed ballast characters. --- qml/pages/PageRecordsManagementUpload.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index b69b516e..6222afdf 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -75,7 +75,7 @@ Component { function viewSelectedNodeNames() { var nameList = uploadHierarchyListModel.selectedFullNames(true) - selectedLocations.text = nameList.join(" sd fkjs asdjfdsaj sadjf sd fsdjf \n") + selectedLocations.text = nameList.join("\n") } UploadHierarchyListModel { -- GitLab From 1161fa979111a67b1fa719dd9620e0f3268b9c85 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Fri, 16 Feb 2018 14:05:03 +0100 Subject: [PATCH 59/63] Added margins into upload message page --- qml/pages/PageRecordsManagementUpload.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index 6222afdf..3866f691 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -131,6 +131,7 @@ Component { AccessibleText { id: topText anchors.top: headerBar.bottom + anchors.topMargin: defaultMargin color: datovkaPalette.mid wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter @@ -170,6 +171,7 @@ Component { ScrollView { id: selectedLocationsView anchors.top: buttonRow.bottom + anchors.topMargin: defaultMargin anchors.left: parent.left anchors.right: parent.right clip: true -- GitLab From f1a465efa9f19b4c7ce5fe4179d7a751f140756c Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Fri, 16 Feb 2018 14:31:31 +0100 Subject: [PATCH 60/63] Showing the last entry when selecting upload position. --- qml/pages/PageRecordsManagementUpload.qml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index 3866f691..643084dd 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -76,6 +76,13 @@ Component { function viewSelectedNodeNames() { var nameList = uploadHierarchyListModel.selectedFullNames(true) selectedLocations.text = nameList.join("\n") + + var vertPos = 0.0 + if (selectedLocationsView.contentHeight > selectedLocationsView.height) { + /* Navigate to bottom. */ + vertPos = 1.0 - (selectedLocationsView.height / selectedLocationsView.contentHeight); + } + selectedLocationsView.ScrollBar.vertical.position = vertPos } UploadHierarchyListModel { -- GitLab From c8fc44b8dfaae2e4a5651dbe589b5636b7a972e9 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Mon, 19 Feb 2018 13:23:32 +0100 Subject: [PATCH 61/63] Placed content (except view) of records management upload page into a pane. --- qml/pages/PageRecordsManagementUpload.qml | 148 +++++++++++----------- 1 file changed, 77 insertions(+), 71 deletions(-) diff --git a/qml/pages/PageRecordsManagementUpload.qml b/qml/pages/PageRecordsManagementUpload.qml index 643084dd..834e85d4 100644 --- a/qml/pages/PageRecordsManagementUpload.qml +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -135,89 +135,95 @@ Component { } } - AccessibleText { - id: topText + Pane { + id: controlPane anchors.top: headerBar.bottom - anchors.topMargin: defaultMargin - color: datovkaPalette.mid - wrapMode: Text.Wrap - horizontalAlignment: Text.AlignHCenter width: parent.width - text: qsTr("The message can be uploaded into selected locations in the records management hierarchy.") - } - Row { - id: buttonRow - spacing: formItemVerticalSpacing * 5 - anchors.top: topText.bottom - anchors.horizontalCenter: parent.horizontalCenter - AccessibleButtonWithImage { - id: hierarchyButton - height: inputItemHeight - accessibleName: qsTr("Update upload hierarchy") - source: "qrc:/ui/sync.svg" - sourceHeight: imgHeightHeader - onClicked: { - downloadUploadHierarchy() + + Column { + width: parent.width + + AccessibleText { + id: topText + anchors.topMargin: defaultMargin + color: datovkaPalette.mid + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + width: parent.width + text: qsTr("The message can be uploaded into selected locations in the records management hierarchy.") } - } - AccessibleButtonWithImage { - id: uploadButton - enabled: false - height: inputItemHeight - accessibleName: qsTr("Upload message") - source: "qrc:/ui/upload.svg" - sourceHeight: imgHeightHeader - onClicked: { - if (recordsManagement.uploadMessage(userName, msgId, msgType, uploadHierarchyListModel)) { - messages.updateRmStatus(messageModel, userName, msgId, true) - pageView.pop(StackView.Immediate) + Row { + id: buttonRow + spacing: formItemVerticalSpacing * 5 + anchors.horizontalCenter: parent.horizontalCenter + AccessibleButtonWithImage { + id: hierarchyButton + height: inputItemHeight + accessibleName: qsTr("Update upload hierarchy") + source: "qrc:/ui/sync.svg" + sourceHeight: imgHeightHeader + onClicked: { + downloadUploadHierarchy() + } + } + AccessibleButtonWithImage { + id: uploadButton + enabled: false + height: inputItemHeight + accessibleName: qsTr("Upload message") + source: "qrc:/ui/upload.svg" + sourceHeight: imgHeightHeader + onClicked: { + if (recordsManagement.uploadMessage(userName, msgId, msgType, uploadHierarchyListModel)) { + messages.updateRmStatus(messageModel, userName, msgId, true) + pageView.pop(StackView.Immediate) + } + } } } - } - } - ScrollView { - id: selectedLocationsView - anchors.top: buttonRow.bottom - anchors.topMargin: defaultMargin - anchors.left: parent.left - anchors.right: parent.right - clip: true - height: fontMetrics.height * 5 + ScrollView { + id: selectedLocationsView + anchors.topMargin: defaultMargin + anchors.left: parent.left + anchors.right: parent.right + clip: true + height: fontMetrics.height * 5 - ScrollBar.horizontal.policy: ScrollBar.AsNeeded - ScrollBar.vertical.policy: ScrollBar.AsNeeded + ScrollBar.horizontal.policy: ScrollBar.AsNeeded + ScrollBar.vertical.policy: ScrollBar.AsNeeded - AccessibleText { - id: selectedLocations - } - } - Row { - id: navigateUpRow - spacing: formItemVerticalSpacing * 3 - anchors.top: selectedLocationsView.bottom - anchors.left: parent.left - anchors.right: parent.right - AccessibleButton { - id: upButton - anchors { - verticalCenter: parent.verticalCenter; - } - text: "<" - accessibleName: qsTr("Up") /* Needs to be specified as "<" is not read. */ - onClicked: { - uploadHierarchyListModel.navigateSuper() + AccessibleText { + id: selectedLocations + } } - } - AccessibleText { - id: locationLabel - anchors { - verticalCenter: parent.verticalCenter; + Row { + id: navigateUpRow + spacing: formItemVerticalSpacing * 3 + anchors.left: parent.left + anchors.right: parent.right + AccessibleButton { + id: upButton + anchors { + verticalCenter: parent.verticalCenter; + } + text: "<" + accessibleName: qsTr("Up") /* Needs to be specified as "<" is not read. */ + onClicked: { + uploadHierarchyListModel.navigateSuper() + } + } + AccessibleText { + id: locationLabel + anchors { + verticalCenter: parent.verticalCenter; + } + text: "/" + } } - text: "/" } } Rectangle { - anchors.top: navigateUpRow.bottom + anchors.top: controlPane.bottom anchors.bottom: parent.bottom width: parent.width color: datovkaPalette.window -- GitLab From ed80e6c84720576f6258e9b4434f590749675991 Mon Sep 17 00:00:00 2001 From: Karel Slany Date: Mon, 19 Feb 2018 14:07:43 +0100 Subject: [PATCH 62/63] Disabled the ColorOverlay inside AccessibleButtonWithImage component. --- qml/components/AccessibleButtonWithImage.qml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qml/components/AccessibleButtonWithImage.qml b/qml/components/AccessibleButtonWithImage.qml index 4cbf8bc4..bea5d7c0 100644 --- a/qml/components/AccessibleButtonWithImage.qml +++ b/qml/components/AccessibleButtonWithImage.qml @@ -21,7 +21,6 @@ * the two. */ -import QtGraphicalEffects 1.0 import QtQuick 2.7 import QtQuick.Controls 2.2 @@ -46,11 +45,12 @@ Button { source: root.source sourceSize.height: root.sourceHeight opacity: enabled || root.highlighted || root.checked ? 1 : 0.3 - ColorOverlay { - anchors.fill: parent - source: parent - color: root.contentItem.color // Current contentItem is a Text item inherited from Button. - } + /* It looks like the greying-out works also without a ColorOverlay. */ + //ColorOverlay { + // anchors.fill: parent + // source: parent + // color: root.contentItem.color // Current contentItem is a Text item inherited from Button. + //} horizontalAlignment: Image.AlignHCenter verticalAlignment: Image.AlignVCenter } -- GitLab From 7474f06632500fe96a4436322537720dc23cb8a0 Mon Sep 17 00:00:00 2001 From: Martin Straka Date: Mon, 19 Feb 2018 14:21:29 +0100 Subject: [PATCH 63/63] Added indentation of some elements in rm settings page --- qml/pages/PageSettingsRecordsManagement.qml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qml/pages/PageSettingsRecordsManagement.qml b/qml/pages/PageSettingsRecordsManagement.qml index 0c3bc0b2..afd70855 100644 --- a/qml/pages/PageSettingsRecordsManagement.qml +++ b/qml/pages/PageSettingsRecordsManagement.qml @@ -119,6 +119,9 @@ Item { width: parent.width text: qsTr("Please fill in service URL and your identification token. Click button to log into records management service. You should receive valid service info. Finally, click the top right button to save the settings.") } + Text { + text: " " + } AccessibleText { color: datovkaPalette.text text: qsTr("Service URL") -- GitLab