diff --git a/mobile-datovka.pro b/mobile-datovka.pro index 8ea5bc4dd0ff7696c9c9a82348e59029ef17414e..95a4fc682e80c7a4e568037de191932a2055ebf6 100644 --- a/mobile-datovka.pro +++ b/mobile-datovka.pro @@ -88,10 +88,23 @@ 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 \ 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 \ + 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/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 \ src/datovka_shared/worker/pool.cpp \ src/dialogues/dialogues.cpp \ @@ -118,11 +131,15 @@ 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 \ 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 \ src/sqlite/account_db.cpp \ @@ -134,6 +151,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 \ @@ -144,6 +162,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 @@ -153,10 +172,23 @@ 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 \ 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 \ + 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/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 \ src/datovka_shared/worker/pool.h \ src/dialogues/dialogues.h \ @@ -166,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 \ @@ -183,11 +216,15 @@ 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 \ 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 \ src/sqlite/account_db.h \ @@ -199,6 +236,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 \ @@ -209,6 +247,7 @@ 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 diff --git a/qml/components/AccessibleButtonWithImage.qml b/qml/components/AccessibleButtonWithImage.qml new file mode 100644 index 0000000000000000000000000000000000000000..bea5d7c01354fb94d6075b351438e023548763dc --- /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 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 + /* 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 + } + + 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/components/MessageList.qml b/qml/components/MessageList.qml index a3a67894c2bc81fedc8b63e7f6303ffccaf76083..2de69fe136bbaacf79b4ca8a5b4b451103297b0d 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/qml/components/ProgressBar.qml b/qml/components/ProgressBar.qml index e57b1840a87a7711b0fc7a2553c4d9883bef82d8..955b87727cea2883660180fb95f48700caa8bfef 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/main.qml b/qml/main.qml index 3e6d2f585396ddba58c23995f7b066ccfa15d1f3..92ea32252dd9f425c8c993619f025db47ce5ec5e 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -77,10 +77,12 @@ 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 {} property Component pageSettingsPin: PageSettingsPin {} + property Component pageSettingsRecordsManagement: PageSettingsRecordsManagement {} property Component pageSettingsStorage: PageSettingsStorage {} property Component pageSettingsSync: PageSettingsSync {} diff --git a/qml/pages/PageMenuDatovkaSettings.qml b/qml/pages/PageMenuDatovkaSettings.qml index c1e4897720d4ea3828ffac65d5023408280a891f..552a67ad4a74df1a401195538080198638e37674 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, @@ -134,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: "updateRecManData" + } + ListElement { + image: "qrc:/ui/briefcase.svg" + showEntry: true + showNext: true + name: qsTr("Records Management") + funcName: "settRecMan" + } ListElement { image: "qrc:/ui/settings.svg" showEntry: true diff --git a/qml/pages/PageMenuMessageDetail.qml b/qml/pages/PageMenuMessageDetail.qml index cff0bac6401dceb66d8af460b98f0b575e72a27e..12b288399e92c125a6dbcd4d37cfac023267f99e 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,17 @@ Component { "action": "template" }, StackView.Immediate) }, + "uploadRM": function callUploadRM() { + pageView.replace(pageRecordsManagementUpload, { + "pageView": pageView, + "statusBar": statusBar, + "acntName" : acntName, + "userName": userName, + "msgId": msgId, + "msgType": msgType, + "messageModel": messageModel + }, StackView.Immediate) + }, "sendEmail": function callSendEmail() { files.sendAttachmentsWithEmail(userName, msgId) pageView.pop(StackView.Immediate) @@ -149,6 +167,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/PageMessageDetail.qml b/qml/pages/PageMessageDetail.qml index c93b1a86787255486856e6d9de899c9faba7515c..7257a554a1d99a566f3515211a2ceef6a2b00e61 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 new file mode 100644 index 0000000000000000000000000000000000000000..834e85d4d7f01cf705378977c055b40d4edf41e7 --- /dev/null +++ b/qml/pages/PageRecordsManagementUpload.qml @@ -0,0 +1,361 @@ +/* + * 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 QtQml 2.2 +import QtQuick 2.7 +import QtQuick.Controls 2.2 +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 + property var messageModel: null + + 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() + } + } + + FontMetrics { + id: fontMetrics + font: selectedLocations.text + } + + 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 { + 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: { + } + } + + UploadHierarchyQmlProxyModel { + id: uploadHierarchyProxyModel + Component.onCompleted: { + setFilterRole(UploadHierarchyListModel.ROLE_FILTER_DATA_RECURSIVE) + } + } + + PageHeader { + id: headerBar + title: qsTr("Upload message %1").arg(msgId) + 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: { + filterBar.visible = true + uploadHierarchyList.anchors.top = filterBar.bottom + filterBar.filterField.forceActiveFocus() + Qt.inputMethod.show() + } + } + } + } + + Pane { + id: controlPane + anchors.top: headerBar.bottom + width: parent.width + + 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.") + } + 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.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 + + AccessibleText { + id: selectedLocations + } + } + 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: "/" + } + } + } + } + Rectangle { + anchors.top: controlPane.bottom + anchors.bottom: parent.bottom + width: parent.width + color: datovkaPalette.window + + FilterBar { + id: filterBar + visible: false + z: 1 + anchors.top: parent.top + 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 = parent.top + 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 + + delegateHeight: headerHeight + + anchors.top: parent.top + anchors.bottom: parent.bottom + width: parent.width + model: uploadHierarchyProxyModel + + clip: true + spacing: 1 + opacity: 1 + interactive: true + + delegate: Rectangle { + id: uploadItem + + color: (!rSelected) ? datovkaPalette.base : readBgColor + height: uploadHierarchyList.delegateHeight + width: parent.width + + Text { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: defaultMargin + /* Use lighter text if some sub-nodes are selected but not this actual node. */ + color: (rSelected || (!rSelectedRecursive)) ? datovkaPalette.text : datovkaPalette.mid + text: rName + } + 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 + } + } + + /* + * TODO -- Handle situation where non-leaf nodes can also + * be selected. + */ + MouseArea { + function handleClick() { + if (!rLeaf) { + uploadHierarchyListModel.navigateSub( + uploadHierarchyProxyModel.mapToSource(index)) + } else if (rSelectable) { + uploadHierarchyListModel.toggleNodeSelection( + uploadHierarchyProxyModel.mapToSource(index)) + } + } + + anchors.fill: parent + + Accessible.role: Accessible.Button + 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: { + handleClick() + } + onClicked: { + handleClick() + } + } + } + } + } + } +} diff --git a/qml/pages/PageSettingsRecordsManagement.qml b/qml/pages/PageSettingsRecordsManagement.qml new file mode 100644 index 0000000000000000000000000000000000000000..afd70855cf2652f4ebdd45f396653c36e9fd0110 --- /dev/null +++ b/qml/pages/PageSettingsRecordsManagement.qml @@ -0,0 +1,267 @@ +/* + * 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: pageSettingsRecordsManagement + + /* These properties must be set by caller. */ + property var pageView + property var statusBar + /* Remember last service url from settings */ + property string lastUrlFromSettings: "" + + /* 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 + } + + /* 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() { + urlTextField.clear() + tokenTextField.clear() + serviceInfoSection.visible = false + areUrlandTokenFilled() + acceptElement.visible = true + } + + Component.onCompleted: { + serviceInfoSection.visible = false + urlTextField.text = settings.rmUrl() + lastUrlFromSettings = settings.rmUrl() + tokenTextField.text = settings.rmToken() + areUrlandTokenFilled() + recordsManagement.loadStoredServiceInfo() + } + + PageHeader { + id: headerBar + title: qsTr("Records management") + onBackClicked: { + pageView.pop(StackView.Immediate) + } + Row { + anchors.verticalCenter: parent.verticalCenter + spacing: defaultMargin + anchors.right: parent.right + anchors.rightMargin: defaultMargin + AccessibleImageButton { + id: acceptElement + visible: false + anchors.verticalCenter: parent.verticalCenter + sourceSize.height: imgHeightHeader + source: "qrc:/ui/checkbox-marked-circle.svg" + accessibleName: qsTr("Accept changes") + onClicked: { + settings.setRmUrl(urlTextField.text) + settings.setRmToken(tokenTextField.text) + recordsManagement.updateServiceInfo(urlTextField.text, lastUrlFromSettings, serviceName.text, tokenName.text) + pageView.pop(StackView.Immediate) + } + } + } + } // 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 * 3 + Column { + id: serviceInputSection + width: parent.width + //spacing: formItemVerticalSpacing + 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 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") + } + AccessibleTextField { + id: urlTextField + width: parent.width + height: inputItemHeight + font.pointSize: defaultTextFont.font.pointSize + placeholderText: qsTr("Enter service url") + InputLineMenu { + id: urlMenu + inputTextControl: urlTextField + isPassword: false + } + onPressAndHold: { + if (settings.useExplicitClipboardOperations()) { + urlMenu.implicitWidth = computeMenuWidth(urlMenu) + urlMenu.open() + } + } + onTextChanged: { + areUrlandTokenFilled() + } + } + AccessibleText { + color: datovkaPalette.text + text: qsTr("Your token") + } + AccessibleTextField { + id: tokenTextField + width: parent.width + height: inputItemHeight + font.pointSize: defaultTextFont.font.pointSize + placeholderText: qsTr("Enter your token") + InputLineMenu { + id: tokenMenu + inputTextControl: tokenTextField + isPassword: false + } + onPressAndHold: { + if (settings.useExplicitClipboardOperations()) { + tokenMenu.implicitWidth = computeMenuWidth(tokenMenu) + tokenMenu.open() + } + } + onTextChanged: { + areUrlandTokenFilled() + } + } + 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 + 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!") + } + Grid { + id: serviceInfoSection + columns: 2 + spacing: formItemVerticalSpacing + AccessibleText { + font.pointSize: defaultTextFont.font.pointSize + text: qsTr("Service name") + ":" + } + 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 {} + } // Flickable + Connections { + target: recordsManagement + onServiceInfo: { + if (srName !== "" && srToken !== "") { + serviceInfoSection.visible = true + acceptElement.visible = true + serviceName.text = srName + tokenName.text = srToken + serviceInfoError.visible = false + serviceLogo.source = "image://images/rmlogo.svg" + } else { + serviceInfoSection.visible = false + } + } + } +} // Item diff --git a/res/locale/datovka_cs.ts b/res/locale/datovka_cs.ts index f5835d2cf2c6324a970e57e6c99652db01111429..df2976c467e55b7ea33176516c5635660d0ed3b4 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 cf0ee1f5cd916ccd0b4ef41ce460e6765ee30267..c63fae9132a9525e7371fbafa392396012a6756a 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 diff --git a/res/qml.qrc b/res/qml.qrc index 3dc80f593f1a9c563659e583135e0c367cd9ef48..1ac62a247c68d692a63d7ac643a979524614b130 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 @@ -97,10 +98,12 @@ ui/sync.svg ui/sync-all.svg ui/up.svg + ui/upload.svg datovka.png 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 @@ -140,10 +143,12 @@ ../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 ../qml/pages/PageSettingsPin.qml + ../qml/pages/PageSettingsRecordsManagement.qml ../qml/pages/PageSettingsStorage.qml ../qml/pages/PageSettingsSync.qml ../qml/main.qml diff --git a/res/ui/briefcase.svg b/res/ui/briefcase.svg new file mode 100644 index 0000000000000000000000000000000000000000..b4afb054ef657f1c9dbda063dff0b1c8a6227d45 --- /dev/null +++ b/res/ui/briefcase.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/res/ui/upload.svg b/res/ui/upload.svg new file mode 100644 index 0000000000000000000000000000000000000000..e8223af87cb029a6ee04f016f01eec005e85c4a9 --- /dev/null +++ b/res/ui/upload.svg @@ -0,0 +1,58 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/src/accounts.cpp b/src/accounts.cpp index 75bd453cfb14e87d902be6dc1f51201c66fab56f..77ba2639788fdf0ad278255beb54a51cf31fc7d3 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; } diff --git a/src/datovka_shared/graphics/graphics.cpp b/src/datovka_shared/graphics/graphics.cpp new file mode 100644 index 0000000000000000000000000000000000000000..762f6256b33312ab1307028b8df296a8d1b2c7ed --- /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 0000000000000000000000000000000000000000..c3a30b9216a87fa4ae1c17bf1cd3ddffc60308fb --- /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/datovka_shared/io/records_management_db.cpp b/src/datovka_shared/io/records_management_db.cpp new file mode 100644 index 0000000000000000000000000000000000000000..25ea587447aae3e2e6c50725206ca4a37ded2019 --- /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/io/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 0000000000000000000000000000000000000000..ce91fcbd02ef0228bcfb57e920f2648e3c74992f --- /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/localisation/localisation.cpp b/src/datovka_shared/localisation/localisation.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ed02ddde6c8b33c7500305725c44d87cb8e8ede0 --- /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 0000000000000000000000000000000000000000..2b400b55d2ce95c8acf5d1aa9d75049a99506d54 --- /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/conversion.cpp b/src/datovka_shared/records_management/conversion.cpp new file mode 100644 index 0000000000000000000000000000000000000000..522363831bd2c522aa74292c0a664b06ebfb2cf6 --- /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 0000000000000000000000000000000000000000..bbe2c837a197446159018cd2f1bede393fd8ad5e --- /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 0000000000000000000000000000000000000000..692233cfeb06a59ad0633bf74123ee8fb2570c52 --- /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 0000000000000000000000000000000000000000..09229b1d72a347ff5f858e86ba28b1a9d5668f8b --- /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 0000000000000000000000000000000000000000..c9ef4661136f4557da8198b4719a46fd888cd19a --- /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 0000000000000000000000000000000000000000..00ebe9e4feb0f385397ac56140d967bbe46f98e3 --- /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 0000000000000000000000000000000000000000..bbbb7423443b8543e4f1b1074001b6bb69da992d --- /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 0000000000000000000000000000000000000000..9e4413d11d0e8376d6290257085580833348d100 --- /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 0000000000000000000000000000000000000000..273c17c4d710e64e67e42bed30a1f6b46712205c --- /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 0000000000000000000000000000000000000000..0645c2cb3c73d06762a7a368f68ef25a24f6874c --- /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/records_management/json/stored_files.cpp b/src/datovka_shared/records_management/json/stored_files.cpp new file mode 100644 index 0000000000000000000000000000000000000000..995ce6a2b521412c7112bc7fd1503301ee282ff1 --- /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 0000000000000000000000000000000000000000..374515c388b00176836bda7e9f3c34d8616ad2ab --- /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 0000000000000000000000000000000000000000..6f5e34038c7c19e236a8b21ce854df049065a01e --- /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 0000000000000000000000000000000000000000..f73545f16ddc292a2bc789b5ccbf84fd36ab792c --- /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 0000000000000000000000000000000000000000..9c3b254010d42edbaeace846593ca6b651af0925 --- /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 0000000000000000000000000000000000000000..24e7a37d0ac3c559ef16b5079fa4d2ee6164f8a7 --- /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. */ +}; 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 0000000000000000000000000000000000000000..147cf552edb09393e5300308db8d8816db1bb5fa --- /dev/null +++ b/src/datovka_shared/records_management/models/upload_hierarchy_proxy_model.cpp @@ -0,0 +1,90 @@ +/* + * 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" +#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, sortRole())); + QVariant rightData(sourceModel()->data(sourceRight, sortRole())); + + if (Q_LIKELY(leftData.canConvert())) { + Q_ASSERT(rightData.canConvert()); + return Localisation::stringCollator.compare(leftData.toString(), + rightData.toString()) < 0; + } 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 0000000000000000000000000000000000000000..3dc1dce005657d3c76a3670d15ee5f6e69a4385a --- /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/datovka_shared/settings/records_management.cpp b/src/datovka_shared/settings/records_management.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6f26cfe2d9b21cf9b9010583ae689d289055e3c9 --- /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 0000000000000000000000000000000000000000..166a64dfa7b06cb3b3709dd518ad92843462a8d1 --- /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/io/db_tables.h b/src/io/db_tables.h new file mode 100644 index 0000000000000000000000000000000000000000..c0e6a3738ad6391ec639619180805109e889c205 --- /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" diff --git a/src/main.cpp b/src/main.cpp index 1deab149da2ee9c8aee3492541db850ccc73f068..9fac5fafce5ac9b1d6ab5da82165523314c39a9e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -32,6 +32,8 @@ #include #include "src/accounts.h" +#include "src/datovka_shared/io/records_management_db.h" +#include "src/datovka_shared/localisation/localisation.h" #include "src/dialogues/qml_dialogue_helper.h" #include "src/dialogues/qml_input_dialogue.h" #include "src/dialogues/dialogues.h" @@ -49,10 +51,14 @@ #include "src/os_android.h" #endif /* defined(Q_OS_ANDROID) */ #include "src/qml_interaction/attachment_data.h" +#include "src/qml_interaction/image_provider.h" #include "src/qml_interaction/interaction_filesystem.h" #include "src/qml_interaction/interaction_zfo_file.h" #include "src/qml_interaction/message_envelope.h" #include "src/qml_interaction/string_manipulation.h" +#include "src/records_management/models/upload_hierarchy_list_model.h" +#include "src/records_management/models/upload_hierarchy_qml_proxy_model.h" +#include "src/records_management.h" #include "src/settings.h" #include "src/setwrapper.h" #include "src/sqlite/db_tables.h" @@ -109,10 +115,12 @@ 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 }, { "PageSettingsPin", 1, 0 }, + { "PageSettingsRecordsManagement", 1, 0 }, { "PageSettingsStorage", 1, 0 }, { "PageSettingsSync", 1, 0 }, { NULL, 0, 0 } @@ -125,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 }, @@ -267,12 +276,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..."; } @@ -288,6 +293,7 @@ int main(int argc, char *argv[]) Files files; IsdsWrapper isds; GlobalSettingsQmlWrapper settings; + RecordsManagement recordsManagement; StringManipulation strManipulation; Zfo zfo; @@ -314,6 +320,13 @@ int main(int argc, char *argv[]) QQmlApplicationEngine engine; QQmlContext *ctx = engine.rootContext(); + globImgProvPtr = new (std::nothrow) ImageProvider; + if (Q_NULLPTR == globImgProvPtr) { + qCritical("Cannot create image provider."); + return EXIT_FAILURE; + } + engine.addImageProvider(IMAGE_PROVIDER_ID, globImgProvPtr); + /* Register application pages to QML */ registerQmlTypes(uri, QML_PAGE_LOC, qmlPages); registerQmlTypes(uri, QML_COMPONENT_LOC, qmlComponents); @@ -333,6 +346,8 @@ int main(int argc, char *argv[]) Messages::declareQML(); MsgEnvelope::declareQML(); MsgInfo::declareQML(); + UploadHierarchyListModel::declareQML(); + UploadHierarchyQmlProxyModel::declareQML(); InteractionZfoFile interactionZfoFile; @@ -353,6 +368,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); @@ -369,21 +385,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 */ @@ -398,6 +412,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/messages.cpp b/src/messages.cpp index 20d7fede1d1e207388b472f1586efd275bd08d36..6cb750a9faeff98f82e8ee1f09097b5c694f64b9 100644 --- a/src/messages.cpp +++ b/src/messages.cpp @@ -234,6 +234,21 @@ void Messages::markMessagesAsLocallyRead(const QVariant &msgModelVariant, messageModel->overrideReadAll(isRead); } +void Messages::updateRmStatus(const QVariant &msgModelVariant, + const QString &userName, 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); + emit updateMessageDetail(getMessageDetail(userName, QString::number(dmId))); +} + 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 d5f79195b070013e535b9688a27c3d7b9f8e5310..c3b445b2cc767d45c49e4866a8c482ba8200b286 100644 --- a/src/messages.h +++ b/src/messages.h @@ -130,6 +130,19 @@ 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] userName User name identifying the account. + * @param[in] dmId Message id. + * @param[in] isUploadRm Set whether to force records management + * upload state. + */ + Q_INVOKABLE + void updateRmStatus(const QVariant &msgModelVariant, + const QString &userName, qint64 dmId, bool isUploadRm); + /*! * @brief Delete selected message from databases. * @@ -179,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 diff --git a/src/models/messagemodel.cpp b/src/models/messagemodel.cpp index 232775617aebd32f120c0090fde6e36b11752e6b..e06267fe2d2c6de030e51c9db0feb06f63d5ddc9 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++; @@ -389,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 a111e9fd408b8f9cd449278e1be8ce434016d7d3..ced26e1dc9c6103157056ebd025bfbd7de4d6b81 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) @@ -234,12 +237,23 @@ 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. */ }; /* QML passes its arguments via QVariant. */ Q_DECLARE_METATYPE(MessageListModel) Q_DECLARE_METATYPE(MessageListModel::Roles) - -#endif // MESSAGEMODEL_H diff --git a/src/net/isds_wrapper.cpp b/src/net/isds_wrapper.cpp index 4e3ae73e82e91ab4b1fe1200485314bdd69e73ba..7f6469fe0c917961df47b0007aac103bc050b2e0 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 eb791356fd07af72b2bc97a0131191e146cf82e7..30c958ffc504018977dc0ed322767c8a77f07400 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/qml_interaction/image_provider.cpp b/src/qml_interaction/image_provider.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d283381ed1c83c74be876d171cb7725a60729999 --- /dev/null +++ b/src/qml_interaction/image_provider.cpp @@ -0,0 +1,89 @@ +/* + * 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 DEFAULT_EDGE_LEN 128 + +ImageProvider::ImageProvider(void) + : QQuickImageProvider(QQuickImageProvider::Pixmap), + m_svgImgs() +{ +} + +QPixmap ImageProvider::requestPixmap(const QString &id, QSize *size, + const QSize &requestedSize) +{ + if (Q_UNLIKELY(id.isEmpty())) { + return QPixmap(); + } + + QByteArray svgData(svg(id)); + if (svgData.isEmpty()) { + return QPixmap(); + } + + int edgeLen = DEFAULT_EDGE_LEN; + if (requestedSize.isValid()) { + int h = requestedSize.height(); + int w = requestedSize.width(); + edgeLen = (h < w) ? h : w; + } + QPixmap pixmap(Graphics::pixmapFromSvg(svgData, edgeLen)); + + if (size != Q_NULLPTR) { + *size = pixmap.size(); + } + return pixmap; +} + +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 new file mode 100644 index 0000000000000000000000000000000000000000..24644e523676a7372fba736cb5d65b1fbf94e0d5 --- /dev/null +++ b/src/qml_interaction/image_provider.h @@ -0,0 +1,80 @@ +/* + * 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 + +#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. + */ +class ImageProvider : public QQuickImageProvider { + +public: + /*! + * @brief Constructor. + */ + ImageProvider(void); + + /*! + * @brief Transform image from stored byte array and return a pixmap. + * + * @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) 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 new file mode 100644 index 0000000000000000000000000000000000000000..91bfbc7d82274e3c66d1b8b870d078674f37d503 --- /dev/null +++ b/src/records_management.cpp @@ -0,0 +1,394 @@ +/* + * 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/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" + +/*! + * @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), + m_rmc(RecordsManagementConnection::ignoreSslErrorsDflt, this) +{ + globWorkPool.start(); + + connect(&globMsgProcEmitter, + SIGNAL(rmSyncFinishedSignal(QString, int, int)), + this, SLOT(rmSyncFinished(QString, int, int))); +} + +RecordsManagement::~RecordsManagement(void) +{ + globWorkPool.wait(); + globWorkPool.stop(); +} + +bool RecordsManagement::callServiceInfo(const QString &urlStr, + const QString &tokenStr) +{ + QByteArray response; + + if (urlStr.trimmed().isEmpty() || tokenStr.trimmed().isEmpty()) { + return false; + } + + emit statusBarTextChanged(tr("Get service info"), true, true); + + 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()) { + 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; + } + } + } + + emit statusBarTextChanged(tr("Communication error"), false, true); + return false; +} + +void RecordsManagement::callUploadHierarchy( + const QVariant &hirerachyModelVariant) +{ + QByteArray response; + + if (globRecordsManagementSet.url().isEmpty() || + globRecordsManagementSet.token().isEmpty()) { + return; + } + + UploadHierarchyListModel *hierarchyModel = + UploadHierarchyListModel::fromVariant(hirerachyModelVariant); + if (hierarchyModel == Q_NULLPTR) { + Q_ASSERT(0); + qCritical("%s", "Cannot access upload hierarchy model."); + return; + } + + emit statusBarTextChanged(tr("Upload hierarchy"), true, true); + + /* Clear model. */ + hierarchyModel->setHierarchy(UploadHierarchyResp()); + + m_rmc.setConnection(globRecordsManagementSet.url(), + globRecordsManagementSet.token()); + + 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()) { + /* Set model. */ + hierarchyModel->setHierarchy(uhRes); + emit statusBarTextChanged(tr("Done"), false, true); + return; + } + } + } + + emit statusBarTextChanged(tr("Communication error"), false, true); +} + +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( + 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); + 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."); + continue; + } + + TaskRecordsManagementStoredMessages *task = + new (::std::nothrow) TaskRecordsManagementStoredMessages( + url, token, + TaskRecordsManagementStoredMessages::RM_DOWNLOAD_ALL, + msgDb, userName, accNumber, accTotal); + 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); + + } +} + +bool RecordsManagement::isValidRecordsManagement(void) +{ + return globRecordsManagementSet.isValid(); +} + +void RecordsManagement::loadStoredServiceInfo(void) +{ + if (Q_NULLPTR == globRecordsManagementDbPtr) { + return; + } + + RecordsManagementDb::ServiceInfoEntry entry( + globRecordsManagementDbPtr->serviceInfo()); + if (!entry.isValid()) { + return; + } + + if (globImgProvPtr != Q_NULLPTR) { + globImgProvPtr->setSvg(RM_SVG_LOGO_ID, entry.logoSvg); + } + emit serviceInfo(entry.name, entry.tokenName); +} + +bool RecordsManagement::uploadMessage(const QString &userName, + const QString &dmId, enum Messages::MessageType messageType, + const QVariant &hirerachyModelVariant) +{ + bool ok = false; + qint64 msgId = dmId.toLongLong(&ok); + if (!ok || (msgId < 0)) { + return false; + } + + QByteArray msgData = + QByteArray::fromBase64(globZfoDbPtr->getZfoContentFromDb(msgId, + AccountListModel::globAccounts[userName].isTestAccount())); + + if (msgData.isEmpty()) { + return false; + } + + 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; + } + + 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; + } + + return uploadFile(msgId, msgFileName, msgData, hierarchyModel->selectedIds()); +} + +bool RecordsManagement::updateServiceInfo(const QString &newUrlStr, + const QString &oldUrlStr, const QString &srName, const QString &srToken) +{ + if (Q_NULLPTR == globRecordsManagementDbPtr) { + return false; + } + + QString cUrlStr = newUrlStr.trimmed(); + + if (!cUrlStr.isEmpty()) { + RecordsManagementDb::ServiceInfoEntry entry; + entry.url = cUrlStr; + entry.name = srName; + entry.tokenName = srToken; + entry.logoSvg = (globImgProvPtr != Q_NULLPTR) ? + globImgProvPtr->svg(RM_SVG_LOGO_ID) : QByteArray(); + globRecordsManagementDbPtr->updateServiceInfo(entry); + if (oldUrlStr != cUrlStr) { + return globRecordsManagementDbPtr->deleteAllStoredMsg(); + } + } else { + return globRecordsManagementDbPtr->deleteAllEntries(); + } + + return true; +} + +void RecordsManagement::rmSyncFinished(const QString &userName, int accNumber, + int accTotal) +{ + if (accNumber < accTotal) { + emit statusBarTextChanged( + tr("Update account '%1' (%2/%3)").arg(userName).arg(accNumber).arg(accTotal), + true, true); + } else { + emit statusBarTextChanged(tr("Update done"), false, true); + } +} + +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; + } + + 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()) { + emit statusBarTextChanged(tr("Done"), false, true); + return processUploadFileResponse(ufRes, dmId); + } + } + } + + emit statusBarTextChanged(tr("Communication error"), false, true); + return false; +} diff --git a/src/records_management.h b/src/records_management.h new file mode 100644 index 0000000000000000000000000000000000000000..c67263bb0ed6f8de2d33db83d1be4d3673649346 --- /dev/null +++ b/src/records_management.h @@ -0,0 +1,164 @@ +/* + * 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/messages.h" + +class RecordsManagement : public QObject { + Q_OBJECT + +public: + /*! + * @brief Constructor. + */ + RecordsManagement(QObject *parent = Q_NULLPTR); + + /*! + * @brief Destructor. + */ + ~RecordsManagement(void); + + /*! + * @brief Calls service info and displays results. + * + * @param[in] urlStr Records management url string. + * @param[in] tokenStr Records management token string. + * @return True if success. + */ + Q_INVOKABLE + bool callServiceInfo(const QString &urlStr, const QString &tokenStr); + + /*! + * @brief Download upload hierarchy and set model. + * + * @param[in] hirerachyModelVariant Model for hierarchy update. + */ + Q_INVOKABLE + void callUploadHierarchy(const QVariant &hirerachyModelVariant); + + /*! + * @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(const QString &urlStr, + const QString &tokenSt); + + /*! + * @brief Test if records management is set, active and valid. + * + * @return True if records management is set, active and valid. + */ + Q_INVOKABLE + bool isValidRecordsManagement(void); + + /*! + * @brief Loads service information from storage. + */ + Q_INVOKABLE + void loadStoredServiceInfo(void); + + /*! + * @brief Upload message into records management service. + * + * @param[in] userName Account user name identifier. + * @param[in] dmId Message identifier. + * @param[in] messageType Message orientation. + * @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 QVariant &hirerachyModelVariant); + + /*! + * @brief Update record management 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 &newUrlStr, + const QString &oldUrlStr, const QString &srName, + const QString &srToken); + +signals: + + /*! + * @brief Send service info to QML. + * + * @param[in] srName Service name. + * @param[in] srToken Service token. + */ + 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: + + /*! + * @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); + + RecordsManagementConnection m_rmc; /*!< Connection to records management service. */ +}; 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 0000000000000000000000000000000000000000..1806bc7b50ffcd3db82ec60eccdecb911858d25a --- /dev/null +++ b/src/records_management/models/upload_hierarchy_list_model.cpp @@ -0,0 +1,480 @@ +/* + * 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 /* 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) +{ + qmlRegisterType("cz.nic.mobileDatovka.models", 1, 0, "UploadHierarchyListModel"); + qRegisterMetaType(); + qRegisterMetaType(); +} + +UploadHierarchyListModel::UploadHierarchyListModel(QObject *parent) + : QAbstractListModel(parent), + m_hierarchy(), + m_workingRoot(Q_NULLPTR), + m_selected() +{ +} + +UploadHierarchyListModel::UploadHierarchyListModel( + const UploadHierarchyListModel &model, QObject *parent) + : QAbstractListModel(parent), + m_hierarchy(model.m_hierarchy), + m_workingRoot(m_hierarchy.root()), + m_selected(model.m_selected) +{ +} + +QModelIndex UploadHierarchyListModel::index(int row, int column, + const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) { + return QModelIndex(); + } + + quintptr internalId = 0; + + if (!parent.isValid()) { + /* 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(); + 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(); + 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 QModelIndex(); + } 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()) { + /* List model has always invalid parent. */ + + if ((!m_hierarchy.isValid()) || (m_workingRoot == Q_NULLPTR)) { + /* Invalid hierarchy. */ + return 0; + } + + return m_workingRoot->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"; + roles[ROLE_LEAF] = "rLeaf"; + roles[ROLE_SELECTABLE] = "rSelectable"; + roles[ROLE_SELECTED] = "rSelected"; + roles[ROLE_SELECTED_RECURSIVE] = "rSelectedRecursive"; + } + 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 +{ + 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 Qt::DisplayRole: + return entry->name(); + break; + 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; + case ROLE_LEAF: + return entry->sub().size() <= 0; + break; + case ROLE_SELECTABLE: + return isSelectable(entry); + break; + case ROLE_SELECTED: + return idInSelection(entry, m_selected, false); + break; + case ROLE_SELECTED_RECURSIVE: + return idInSelection(entry, m_selected, true); + 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 (!isSelectable(entry)) { + flags &= ~Qt::ItemIsSelectable; + } + + return flags; +} + +void UploadHierarchyListModel::setHierarchy(const UploadHierarchyResp &uhr) +{ + beginResetModel(); + m_hierarchy = uhr; + m_workingRoot = m_hierarchy.root(); + m_selected.clear(); + 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())) { + 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(); + } + } +} + +/*! + * @brief Return entry name. + * + * @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 entry. + */ +static +QString entryName(const UploadHierarchyResp::NodeEntry *entry, bool takeSuper, + const QString &sep) +{ + if (Q_UNLIKELY(entry == Q_NULLPTR)) { + Q_ASSERT(0); + return QString(); + } + + QString nodeName(entry->name() + sep); + if (takeSuper) { + entry = entry->super(); + while ((entry != Q_NULLPTR) && (!entry->name().isEmpty())) { + nodeName = entry->name() + sep + nodeName; + entry = entry->super(); + } + } + + 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 entryName(m_workingRoot, takeSuper, sep); +} + +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 (idInSelection(sub, m_selected, false)) { + m_selected.remove(sub->id()); + } else { + m_selected.insert(sub->id(), + entryName(sub, true, "/")); /* TODO -- Make the separator configurable. */ + } + 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_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 +{ + QStringList values(m_selected.values()); + + if (sort) { + ::std::sort(values.begin(), values.end(), HierarchyNameLess()); + } + + return values; +} + +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_workingRoot != Q_NULLPTR) && + !m_workingRoot->name().isEmpty(); +} 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 0000000000000000000000000000000000000000..776d0ccdb20395497f25cef63980e00c815476dc --- /dev/null +++ b/src/records_management/models/upload_hierarchy_list_model.h @@ -0,0 +1,226 @@ +/* + * 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/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, + ROLE_SELECTABLE, + ROLE_SELECTED, /* The actual node is selected. */ + ROLE_SELECTED_RECURSIVE /* Actual node or any sub-node is selected. */ + }; + Q_ENUM(Roles) + + /* 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 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); + + /*! + * @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 Toggles the node selection. + * + * @note Emits dataChanged() signal. + * + * @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 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. + * + * @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; + + 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. */ +}; + +/* 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 new file mode 100644 index 0000000000000000000000000000000000000000..275569a4029758158afc6c46cfa5be7e5f8fe929 --- /dev/null +++ b/src/records_management/models/upload_hierarchy_qml_proxy_model.cpp @@ -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. + */ + +#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::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 new file mode 100644 index 0000000000000000000000000000000000000000..fb9759d97b04ef95ee477d2583f35ddfb6be55ec --- /dev/null +++ b/src/records_management/models/upload_hierarchy_qml_proxy_model.h @@ -0,0 +1,102 @@ +/* + * 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; + + /*! + * @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. */ +Q_DECLARE_METATYPE(UploadHierarchyQmlProxyModel) diff --git a/src/settings.cpp b/src/settings.cpp index 3cb081af1f3ec609336287a11867192cadef1a4a..d40f7aeea6c0a0ce575870c37658f4437990f3bc 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,8 @@ #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" #include "src/settings.h" @@ -33,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 @@ -68,7 +67,7 @@ Settings globSet; Settings::Settings(void) : PinSettings(), - language(LANGUAGE_SYSTEM), + language(Localisation::langSystem), fontSize(DEFAULT_FONT_SIZE), downloadOnlyNewMsgs(true), downloadCompleteMsgs(false), @@ -130,6 +129,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) @@ -137,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( @@ -194,6 +196,9 @@ void Settings::loadFromSettings(const QSettings &settings) if (dbsLocation.isEmpty()) { dbsLocation = getDefaultDbAndConfigLocation(); } + + /* Records management settings. */ + globRecordsManagementSet.loadFromSettings(settings); } QString Settings::settingsPath(void) diff --git a/src/setwrapper.cpp b/src/setwrapper.cpp index e974c8252eaa74aa6669c7b9892bf0d386f36627..90981c964eec7fe0be15738dc49d997399e8425d 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()) { @@ -146,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); diff --git a/src/setwrapper.h b/src/setwrapper.h index fb5782fd01bdfabbdfa6a281e496bc02858025a7..c49b73d28608c7d4247712c0fc466b2415ee94a5 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 a690975dc37783486796b51bea157e91d4a91ab9..6ccfd1deacec5475e869c46c12965d5a845dfe5c 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 f83845fa5aad37be23b9be8d50d62b0e505513f5..13675db6f16313b0c2ce6b991638136368403d75 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'. */ diff --git a/src/sqlite/message_db.cpp b/src/sqlite/message_db.cpp index ce92c0888e49b4352ef58bbe7b665dedb30b4aca..e29ca4c0a4185893849d07f2cf15e3c0b2543282 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 @@ -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" @@ -131,6 +132,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); @@ -348,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; @@ -487,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; diff --git a/src/sqlite/message_db.h b/src/sqlite/message_db.h index 9f7002d3cd6d31326ead749284941fd5151d60a7..d668cf8276d570d821183f68f425a2d3523e8385 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/emitter.h b/src/worker/emitter.h index b2c7ebca939fb1d4ec13d8259ede0a1e502ec0e9..040767c909dff06981de776ceb8730a84a4e4624 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/pool.cpp b/src/worker/pool.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ce57f2791c7802d0e569a7ab187ab050b2631182 --- /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 0000000000000000000000000000000000000000..77eea3487d391a30992febfacaad9ad125204295 --- /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 0000000000000000000000000000000000000000..f833e5c0ccafad51a4d63fbaa4dce2bbaaf78c9a --- /dev/null +++ b/src/worker/task_records_management_stored_messages.cpp @@ -0,0 +1,374 @@ +/* + * 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/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 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)), + m_url(urlStr), + 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()); + 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); + + 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'.", + 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 0000000000000000000000000000000000000000..5af3264f56d8ec1762f7c2865353192f4c1a1b2f --- /dev/null +++ b/src/worker/task_records_management_stored_messages.h @@ -0,0 +1,118 @@ +/* + * 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. + * @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()); + + /*! + * @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 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. + */ +};