diff --git a/resources.qrc b/resources.qrc index f233501fc..976c534b7 100644 --- a/resources.qrc +++ b/resources.qrc @@ -51,5 +51,6 @@ src/gui/tray/EnforcedPlainTextLabel.qml theme/Style/Style.qml theme/Style/qmldir + src/gui/filedetails/NCRadioButton.qml diff --git a/src/gui/filedetails/NCRadioButton.qml b/src/gui/filedetails/NCRadioButton.qml new file mode 100644 index 000000000..05e879164 --- /dev/null +++ b/src/gui/filedetails/NCRadioButton.qml @@ -0,0 +1,43 @@ +/* + * Copyright (C) by Oleksandr Zolotov + * + * 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 2 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. + */ + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import Style 1.0 + +RadioButton { + id: root + property int indicatorItemWidth: Style.radioButtonIndicatorSize + property int indicatorItemHeight: Style.radioButtonIndicatorSize + property string color: Style.ncTextColor + readonly property int radius: Style.radioButtonCustomRadius + + indicator: Rectangle { + implicitWidth: root.indicatorItemWidth + implicitHeight: root.indicatorItemHeight + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: Style.radioButtonCustomMarginLeftOuter + radius: root.radius + border.color: root.color + border.width: Style.normalBorderWidth + Rectangle { + anchors.fill: parent + visible: root.checked + color: root.color + radius: root.radius + anchors.margins: Style.radioButtonCustomMarginLeftInner + } + } +} diff --git a/src/gui/filedetails/ShareDelegate.qml b/src/gui/filedetails/ShareDelegate.qml index 913dec2e0..e48deaf4d 100644 --- a/src/gui/filedetails/ShareDelegate.qml +++ b/src/gui/filedetails/ShareDelegate.qml @@ -33,11 +33,13 @@ GridLayout { signal resetPasswordField signal showPasswordSetError(string errorMessage); + signal toggleHideDownload(bool enable) signal toggleAllowEditing(bool enable) signal toggleAllowResharing(bool enable) signal togglePasswordProtect(bool enable) signal toggleExpirationDate(bool enable) signal toggleNoteToRecipient(bool enable) + signal permissionModeChanged(int permissionMode) signal setLinkShareLabel(string label) signal setExpireDate(var milliseconds) // Since QML ints are only 32 bits, use a variant @@ -236,9 +238,11 @@ GridLayout { onToggleAllowEditing: root.toggleAllowEditing(enable) onToggleAllowResharing: root.toggleAllowResharing(enable) + onToggleHideDownload: root.toggleHideDownload(enable) onTogglePasswordProtect: root.togglePasswordProtect(enable) onToggleExpirationDate: root.toggleExpirationDate(enable) onToggleNoteToRecipient: root.toggleNoteToRecipient(enable) + onPermissionModeChanged: root.permissionModeChanged(permissionMode) onSetLinkShareLabel: root.setLinkShareLabel(label) onSetExpireDate: root.setExpireDate(milliseconds) // Since QML ints are only 32 bits, use a variant diff --git a/src/gui/filedetails/ShareDetailsPage.qml b/src/gui/filedetails/ShareDetailsPage.qml index 03dd7c52d..a4bcc7caf 100644 --- a/src/gui/filedetails/ShareDetailsPage.qml +++ b/src/gui/filedetails/ShareDetailsPage.qml @@ -32,9 +32,11 @@ Page { signal toggleAllowEditing(bool enable) signal toggleAllowResharing(bool enable) + signal toggleHideDownload(bool enable) signal togglePasswordProtect(bool enable) signal toggleExpirationDate(bool enable) signal toggleNoteToRecipient(bool enable) + signal permissionModeChanged(int permissionMode) signal setLinkShareLabel(string label) signal setExpireDate(var milliseconds) // Since QML ints are only 32 bits, use a variant @@ -65,16 +67,21 @@ Page { readonly property string linkShareLabel: shareModelData.linkShareLabel ?? "" readonly property bool editingAllowed: shareModelData.editingAllowed + readonly property bool hideDownload: shareModelData.hideDownload readonly property bool noteEnabled: shareModelData.noteEnabled readonly property bool expireDateEnabled: shareModelData.expireDateEnabled readonly property bool expireDateEnforced: shareModelData.expireDateEnforced readonly property bool passwordProtectEnabled: shareModelData.passwordProtectEnabled readonly property bool passwordEnforced: shareModelData.passwordEnforced - readonly property bool isSecureFileDropLink: shareModelData.isSecureFileDropLink + readonly property bool isSharePermissionChangeInProgress: shareModelData.isSharePermissionChangeInProgress + readonly property bool isHideDownloadInProgress: shareModelData.isHideDownloadInProgress + readonly property int currentPermissionMode: shareModelData.currentPermissionMode readonly property bool isLinkShare: shareModelData.shareType === ShareModel.ShareTypeLink - property bool waitingForEditingAllowedChange: false + readonly property bool isFolderItem: shareModelData.sharedItemType === ShareModel.SharedItemTypeFolder + readonly property bool isEncryptedItem: shareModelData.sharedItemType === ShareModel.SharedItemTypeEncryptedFile || shareModelData.sharedItemType === ShareModel.SharedItemTypeEncryptedFolder || shareModelData.sharedItemType === ShareModel.SharedItemTypeEncryptedTopLevelFolder + property bool waitingForNoteEnabledChange: false property bool waitingForExpireDateEnabledChange: false property bool waitingForPasswordProtectEnabledChange: false @@ -108,11 +115,6 @@ Page { waitingForExpireDateChange = false; } - function resetEditingAllowedField() { - editingAllowedMenuItem.checked = editingAllowed; - waitingForEditingAllowedChange = false; - } - function resetNoteEnabledField() { noteEnabledMenuItem.checked = noteEnabled; waitingForNoteEnabledChange = false; @@ -135,8 +137,6 @@ Page { resetPasswordField(); resetLinkShareLabelField(); resetExpireDateField(); - - resetEditingAllowedField(); resetNoteEnabledField(); resetExpireDateEnabledField(); resetPasswordProtectEnabledField(); @@ -154,8 +154,6 @@ Page { onPasswordChanged: resetPasswordField() onLinkShareLabelChanged: resetLinkShareLabelField() onExpireDateChanged: resetExpireDateField() - - onEditingAllowedChanged: resetEditingAllowedField() onNoteEnabledChanged: resetNoteEnabledField() onExpireDateEnabledChanged: resetExpireDateEnabledField() onPasswordProtectEnabledChanged: resetPasswordProtectEnabledField() @@ -313,34 +311,124 @@ Page { } } - // On these checkables, the clicked() signal is called after - // the check state changes. - CheckBox { - id: editingAllowedMenuItem + Loader { + Layout.fillWidth: true + active: !root.isFolderItem && !root.isEncryptedItem + visible: active + sourceComponent: CheckBox { + spacing: moreMenu.indicatorSpacing + padding: moreMenu.itemPadding + indicator.width: moreMenu.indicatorItemWidth + indicator.height: moreMenu.indicatorItemWidth + checkable: true + checked: root.editingAllowed + text: qsTr("Allow upload and editing") + enabled: !root.isSharePermissionChangeInProgress + + onClicked: root.toggleAllowEditing(checked) + + NCBusyIndicator { + anchors.fill: parent + visible: root.isSharePermissionChangeInProgress + running: visible + z: 1 + } + } + } + + Loader { + Layout.fillWidth: true + active: root.isFolderItem && !root.isEncryptedItem + visible: active + sourceComponent: ColumnLayout { + id: permissionRadioButtonsLayout + spacing: 0 + width: parent.width + + ButtonGroup { + id: permissionModeRadioButtonsGroup + } + + NCRadioButton { + readonly property int permissionMode: ShareModel.ModeViewOnly + Layout.fillWidth: true + ButtonGroup.group: permissionModeRadioButtonsGroup + enabled: !root.isSharePermissionChangeInProgress + checked: root.currentPermissionMode === permissionMode + text: qsTr("View only") + indicatorItemWidth: moreMenu.indicatorItemWidth + indicatorItemHeight: moreMenu.indicatorItemWidth + spacing: moreMenu.indicatorSpacing + padding: moreMenu.itemPadding + onClicked: root.permissionModeChanged(permissionMode) + } + + NCRadioButton { + readonly property int permissionMode: ShareModel.ModeUploadAndEditing + Layout.fillWidth: true + ButtonGroup.group: permissionModeRadioButtonsGroup + enabled: !root.isSharePermissionChangeInProgress + checked: root.currentPermissionMode === permissionMode + text: qsTr("Allow upload and editing") + indicatorItemWidth: moreMenu.indicatorItemWidth + indicatorItemHeight: moreMenu.indicatorItemWidth + spacing: moreMenu.indicatorSpacing + padding: moreMenu.itemPadding + onClicked: root.permissionModeChanged(permissionMode) + + NCBusyIndicator { + anchors.fill: parent + visible: root.isSharePermissionChangeInProgress + running: visible + z: 1 + } + } + + NCRadioButton { + readonly property int permissionMode: ShareModel.ModeFileDropOnly + Layout.fillWidth: true + ButtonGroup.group: permissionModeRadioButtonsGroup + enabled: !root.isSharePermissionChangeInProgress + checked: root.currentPermissionMode === permissionMode + text: qsTr("File drop (upload only)") + indicatorItemWidth: moreMenu.indicatorItemWidth + indicatorItemHeight: moreMenu.indicatorItemWidth + spacing: moreMenu.indicatorSpacing + padding: moreMenu.itemPadding + onClicked: root.permissionModeChanged(permissionMode) + } + } + } + + Loader { Layout.fillWidth: true - spacing: moreMenu.indicatorSpacing - padding: moreMenu.itemPadding - indicator.width: moreMenu.indicatorItemWidth - indicator.height: moreMenu.indicatorItemWidth + active: root.isLinkShare + visible: active + sourceComponent: ColumnLayout { + CheckBox { + id: hideDownloadEnabledMenuItem - checkable: true - checked: root.editingAllowed - text: qsTr("Allow editing") - enabled: !root.waitingForEditingAllowedChange - visible: !root.isSecureFileDropLink + anchors.left: parent.left + anchors.right: parent.right - onClicked: { - root.toggleAllowEditing(checked); - root.waitingForEditingAllowedChange = true; - } + spacing: moreMenu.indicatorSpacing + padding: moreMenu.itemPadding + indicator.width: moreMenu.indicatorItemWidth + indicator.height: moreMenu.indicatorItemWidth + checked: root.hideDownload + text: qsTr("Hide download") + enabled: !root.isHideDownloadInProgress + onClicked: root.toggleHideDownload(checked); - NCBusyIndicator { - anchors.fill: parent - visible: root.waitingForEditingAllowedChange - running: visible - z: 1 + NCBusyIndicator { + anchors.fill: parent + visible: root.isHideDownloadInProgress + running: visible + z: 1 + } + } } } diff --git a/src/gui/filedetails/ShareView.qml b/src/gui/filedetails/ShareView.qml index 20a63963c..a1c80bb0f 100644 --- a/src/gui/filedetails/ShareView.qml +++ b/src/gui/filedetails/ShareView.qml @@ -241,9 +241,11 @@ ColumnLayout { onToggleAllowEditing: shareModel.toggleShareAllowEditingFromQml(model.share, enable) onToggleAllowResharing: shareModel.toggleShareAllowResharingFromQml(model.share, enable) + onToggleHideDownload: shareModel.toggleHideDownloadFromQml(model.share, enable) onTogglePasswordProtect: shareModel.toggleSharePasswordProtectFromQml(model.share, enable) onToggleExpirationDate: shareModel.toggleShareExpirationDateFromQml(model.share, enable) onToggleNoteToRecipient: shareModel.toggleShareNoteToRecipientFromQml(model.share, enable) + onPermissionModeChanged: shareModel.changePermissionModeFromQml(model.share, permissionMode) onSetLinkShareLabel: shareModel.setLinkShareLabelFromQml(model.share, label) onSetExpireDate: shareModel.setShareExpireDateFromQml(model.share, milliseconds) diff --git a/src/gui/filedetails/sharemodel.cpp b/src/gui/filedetails/sharemodel.cpp index df7acb873..8cb513b34 100644 --- a/src/gui/filedetails/sharemodel.cpp +++ b/src/gui/filedetails/sharemodel.cpp @@ -81,7 +81,11 @@ QHash ShareModel::roleNames() const roles[PasswordRole] = "password"; roles[PasswordEnforcedRole] = "passwordEnforced"; roles[EditingAllowedRole] = "editingAllowed"; - roles[IsSecureFileDropLinkRole] = "isSecureFileDropLink"; + roles[CurrentPermissionModeRole] = "currentPermissionMode"; + roles[SharedItemTypeRole] = "sharedItemType"; + roles[IsSharePermissionsChangeInProgress] = "isSharePermissionChangeInProgress"; + roles[HideDownloadEnabledRole] = "hideDownload"; + roles[IsHideDownloadEnabledChangeInProgress] = "isHideDownloadInProgress"; return roles; } @@ -107,6 +111,8 @@ QVariant ShareModel::data(const QModelIndex &index, const int role) const return linkShare->getLabel(); case NoteEnabledRole: return !linkShare->getNote().isEmpty(); + case HideDownloadEnabledRole: + return linkShare->getHideDownload(); case NoteRole: return linkShare->getNote(); case ExpireDateEnabledRole: @@ -151,8 +157,22 @@ QVariant ShareModel::data(const QModelIndex &index, const int role) const return expireDateEnforcedForShare(share); case EnforcedMaximumExpireDateRole: return enforcedMaxExpireDateForShare(share); - case IsSecureFileDropLinkRole: - return _isSecureFileDropSupportedFolder && share->getPermissions().testFlag(OCC::SharePermission::SharePermissionCreate); + case CurrentPermissionModeRole: { + if (share->getPermissions() == OCC::SharePermission::SharePermissionCreate) { + return QVariant::fromValue(SharePermissionsMode::ModeFileDropOnly); + } else if ((share->getPermissions() & SharePermissionRead) && (share->getPermissions() & SharePermissionCreate) + && (share->getPermissions() & SharePermissionUpdate) && (share->getPermissions() & SharePermissionDelete)) { + return QVariant::fromValue(SharePermissionsMode::ModeUploadAndEditing); + } else { + return QVariant::fromValue(SharePermissionsMode::ModeViewOnly); + } + } + case SharedItemTypeRole: + return static_cast(_sharedItemType); + case IsSharePermissionsChangeInProgress: + return _sharePermissionsChangeInProgress; + case IsHideDownloadEnabledChangeInProgress: + return _hideDownloadEnabledChangeInProgress; case PasswordProtectEnabledRole: return share->isPasswordSet(); case PasswordRole: @@ -250,9 +270,15 @@ void ShareModel::updateData() _numericFileId = fileRecord.numericFileId(); - _isEncryptedItem = fileRecord.isE2eEncrypted(); - _isSecureFileDropSupportedFolder = - fileRecord.isE2eEncrypted() && fileRecord.e2eMangledName().isEmpty() && _accountState->account()->secureFileDropSupported(); + if (fileRecord.isDirectory()) { + if (fileRecord.isE2eEncrypted()) { + _sharedItemType = fileRecord.e2eMangledName().isEmpty() ? SharedItemType::SharedItemTypeEncryptedTopLevelFolder : SharedItemType::SharedItemTypeEncryptedFolder; + } else { + _sharedItemType = SharedItemType::SharedItemTypeFolder; + } + } else { + _sharedItemType = fileRecord.isE2eEncrypted() ? SharedItemType::SharedItemTypeEncryptedFile : SharedItemType::SharedItemTypeFile; + } // Will get added when shares are fetched if no link shares are fetched _placeholderLinkShare.reset(new Share(_accountState->account(), @@ -386,9 +412,9 @@ void ShareModel::handleSecureFileDropLinkShare() void ShareModel::handleLinkShare() { - if (!_isEncryptedItem) { + if (!isEncryptedItem()) { handlePlaceholderLinkShare(); - } else if (_isSecureFileDropSupportedFolder) { + } else if (isSecureFileDropSupportedFolder()) { handleSecureFileDropLinkShare(); } } @@ -456,7 +482,7 @@ void ShareModel::setupInternalLinkShare() _accountState->account().isNull() || _localPath.isEmpty() || _privateLinkUrl.isEmpty() || - _isEncryptedItem) { + isEncryptedItem()) { return; } @@ -466,6 +492,30 @@ void ShareModel::setupInternalLinkShare() Q_EMIT internalLinkReady(); } +void ShareModel::setSharePermissionChangeInProgress(const QString &shareId, const bool isInProgress) +{ + if (isInProgress == _sharePermissionsChangeInProgress) { + return; + } + + _sharePermissionsChangeInProgress = isInProgress; + + const auto shareIndex = _shareIdIndexHash.value(shareId); + Q_EMIT dataChanged(shareIndex, shareIndex, {IsSharePermissionsChangeInProgress}); +} + +void ShareModel::setHideDownloadEnabledChangeInProgress(const QString &shareId, const bool isInProgress) +{ + if (isInProgress == _hideDownloadEnabledChangeInProgress) { + return; + } + + _hideDownloadEnabledChangeInProgress = isInProgress; + + const auto shareIndex = _shareIdIndexHash.value(shareId); + Q_EMIT dataChanged(shareIndex, shareIndex, {IsHideDownloadEnabledChangeInProgress}); +} + void ShareModel::slotAddShare(const SharePtr &share) { if (share.isNull()) { @@ -515,6 +565,7 @@ void ShareModel::slotAddShare(const SharePtr &share) connect(linkShare.data(), &LinkShare::nameSet, this, [this, shareId]{ slotShareNameSet(shareId); }); connect(linkShare.data(), &LinkShare::labelSet, this, [this, shareId]{ slotShareLabelSet(shareId); }); connect(linkShare.data(), &LinkShare::expireDateSet, this, [this, shareId]{ slotShareExpireDateSet(shareId); }); + connect(linkShare.data(), &LinkShare::hideDownloadSet, this, [this, shareId] { slotHideDownloadSet(shareId); }); } else if (const auto userGroupShare = share.objectCast()) { connect(userGroupShare.data(), &UserGroupShare::noteSet, this, [this, shareId]{ slotShareNoteSet(shareId); }); connect(userGroupShare.data(), &UserGroupShare::expireDateSet, this, [this, shareId]{ slotShareExpireDateSet(shareId); }); @@ -582,7 +633,7 @@ QString ShareModel::displayStringForShare(const SharePtr &share) const { if (const auto linkShare = share.objectCast()) { - const auto isSecureFileDropShare = _isSecureFileDropSupportedFolder && linkShare->getPermissions().testFlag(OCC::SharePermission::SharePermissionCreate); + const auto isSecureFileDropShare = isSecureFileDropSupportedFolder() && linkShare->getPermissions().testFlag(OCC::SharePermission::SharePermissionCreate); const auto displayString = isSecureFileDropShare ? tr("Secure file drop link") : tr("Share link"); @@ -708,7 +759,8 @@ void ShareModel::slotSharePermissionsSet(const QString &shareId) const auto sharePersistentModelIndex = _shareIdIndexHash.value(shareId); const auto shareModelIndex = index(sharePersistentModelIndex.row()); - Q_EMIT dataChanged(shareModelIndex, shareModelIndex, { EditingAllowedRole }); + Q_EMIT dataChanged(shareModelIndex, shareModelIndex, { EditingAllowedRole, CurrentPermissionModeRole }); + setSharePermissionChangeInProgress(shareId, false); } void ShareModel::slotSharePasswordSet(const QString &shareId) @@ -722,6 +774,18 @@ void ShareModel::slotSharePasswordSet(const QString &shareId) Q_EMIT dataChanged(shareModelIndex, shareModelIndex, { PasswordProtectEnabledRole, PasswordRole }); } +void ShareModel::slotHideDownloadSet(const QString &shareId) +{ + if (shareId.isEmpty() || !_shareIdIndexHash.contains(shareId)) { + return; + } + + const auto sharePersistentModelIndex = _shareIdIndexHash.value(shareId); + const auto shareModelIndex = index(sharePersistentModelIndex.row()); + Q_EMIT dataChanged(shareModelIndex, shareModelIndex, {HideDownloadEnabledRole}); + setHideDownloadEnabledChangeInProgress(shareId, false); +} + void ShareModel::slotShareNoteSet(const QString &shareId) { if (shareId.isEmpty() || !_shareIdIndexHash.contains(shareId)) { @@ -768,37 +832,56 @@ void ShareModel::slotShareExpireDateSet(const QString &shareId) // ----------------------- Shares modification slots ----------------------- // -void ShareModel::toggleShareAllowEditing(const SharePtr &share, const bool enable) const +void ShareModel::toggleShareAllowEditing(const SharePtr &share, const bool enable) { - if (share.isNull()) { + if (share.isNull() || _sharePermissionsChangeInProgress) { return; } auto permissions = share->getPermissions(); enable ? permissions |= SharePermissionUpdate : permissions &= ~SharePermissionUpdate; + setSharePermissionChangeInProgress(share->getId(), true); share->setPermissions(permissions); } -void ShareModel::toggleShareAllowEditingFromQml(const QVariant &share, const bool enable) const +void ShareModel::toggleShareAllowEditingFromQml(const QVariant &share, const bool enable) { const auto ptr = share.value(); toggleShareAllowEditing(ptr, enable); } -void ShareModel::toggleShareAllowResharing(const SharePtr &share, const bool enable) const +void ShareModel::toggleShareAllowResharing(const SharePtr &share, const bool enable) { - if (share.isNull()) { + if (share.isNull() || _sharePermissionsChangeInProgress) { return; } auto permissions = share->getPermissions(); enable ? permissions |= SharePermissionShare : permissions &= ~SharePermissionShare; + setSharePermissionChangeInProgress(share->getId(), true); share->setPermissions(permissions); } -void ShareModel::toggleShareAllowResharingFromQml(const QVariant &share, const bool enable) const +void ShareModel::toggleHideDownloadFromQml(const QVariant &share, const bool enable) +{ + const auto sharePtr = share.value(); + if (sharePtr.isNull() || _hideDownloadEnabledChangeInProgress) { + return; + } + + const auto linkShare = sharePtr.objectCast(); + + if (linkShare.isNull()) { + return; + } + + setHideDownloadEnabledChangeInProgress(linkShare->getId(), true); + linkShare->setHideDownload(enable); +} + +void ShareModel::toggleShareAllowResharingFromQml(const QVariant &share, const bool enable) { const auto ptr = share.value(); toggleShareAllowResharing(ptr, enable); @@ -867,6 +950,42 @@ void ShareModel::toggleShareNoteToRecipientFromQml(const QVariant &share, const toggleShareNoteToRecipient(ptr, enable); } +void ShareModel::changePermissionModeFromQml(const QVariant &share, const SharePermissionsMode permissionMode) +{ + const auto sharePtr = share.value(); + if (sharePtr.isNull() || _sharePermissionsChangeInProgress) { + return; + } + + const auto shareIndex = _shareIdIndexHash.value(sharePtr->getId()); + + if (!checkIndex(shareIndex, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::ParentIsInvalid)) { + qCWarning(lcShareModel) << "Can't change permission mode for:" << sharePtr->getId() << ", invalid share index: " << shareIndex; + return; + } + + const auto currentPermissionMode = shareIndex.data(ShareModel::CurrentPermissionModeRole).value(); + + if (currentPermissionMode == permissionMode) { + return; + } + + SharePermissions perm = SharePermissionRead; + switch (permissionMode) { + case SharePermissionsMode::ModeViewOnly: + break; + case SharePermissionsMode::ModeUploadAndEditing: + perm |= SharePermissionCreate | SharePermissionUpdate | SharePermissionDelete; + break; + case SharePermissionsMode::ModeFileDropOnly: + perm = SharePermissionCreate; + break; + } + + setSharePermissionChangeInProgress(sharePtr->getId(), true); + sharePtr->setPermissions(perm); +} + void ShareModel::setLinkShareLabel(const QSharedPointer &linkShare, const QString &label) const { if (linkShare.isNull()) { @@ -944,7 +1063,7 @@ void ShareModel::setShareNoteFromQml(const QVariant &share, const QString ¬e) void ShareModel::createNewLinkShare() const { - if (_isEncryptedItem && !_isSecureFileDropSupportedFolder) { + if (isEncryptedItem() && !isSecureFileDropSupportedFolder()) { qCWarning(lcShareModel) << "Attempt to create a link share for non-root encrypted folder or a file."; return; } @@ -952,7 +1071,7 @@ void ShareModel::createNewLinkShare() const if (_manager) { const auto askOptionalPassword = _accountState->account()->capabilities().sharePublicLinkAskOptionalPassword(); const auto password = askOptionalPassword ? createRandomPassword() : QString(); - if (_isSecureFileDropSupportedFolder) { + if (isSecureFileDropSupportedFolder()) { _manager->createSecureFileDropShare(_sharePath, {}, password); return; } @@ -1089,6 +1208,17 @@ bool ShareModel::validCapabilities() const _accountState->account()->capabilities().isValid(); } +bool ShareModel::isSecureFileDropSupportedFolder() const +{ + return _sharedItemType == SharedItemType::SharedItemTypeEncryptedTopLevelFolder && _accountState->account()->secureFileDropSupported(); +} + +bool ShareModel::isEncryptedItem() const +{ + return _sharedItemType == SharedItemType::SharedItemTypeEncryptedFile || _sharedItemType == SharedItemType::SharedItemTypeEncryptedFolder + || _sharedItemType == SharedItemType::SharedItemTypeEncryptedTopLevelFolder; +} + bool ShareModel::sharingEnabled() const { return validCapabilities() && diff --git a/src/gui/filedetails/sharemodel.h b/src/gui/filedetails/sharemodel.h index 9d1071298..71458fcc7 100644 --- a/src/gui/filedetails/sharemodel.h +++ b/src/gui/filedetails/sharemodel.h @@ -57,7 +57,11 @@ public: PasswordRole, PasswordEnforcedRole, EditingAllowedRole, - IsSecureFileDropLinkRole, + CurrentPermissionModeRole, + SharedItemTypeRole, + IsSharePermissionsChangeInProgress, + HideDownloadEnabledRole, + IsHideDownloadEnabledChangeInProgress, }; Q_ENUM(Roles) @@ -79,6 +83,23 @@ public: ShareTypeSecureFileDropPlaceholderLink = Share::TypeSecureFileDropPlaceholderLink, }; Q_ENUM(ShareType); + + enum class SharedItemType { + SharedItemTypeUndefined = -1, + SharedItemTypeFile, + SharedItemTypeFolder, + SharedItemTypeEncryptedFile, + SharedItemTypeEncryptedFolder, + SharedItemTypeEncryptedTopLevelFolder, + }; + Q_ENUM(SharedItemType); + + enum class SharePermissionsMode { + ModeViewOnly, + ModeUploadAndEditing, + ModeFileDropOnly, + }; + Q_ENUM(SharePermissionsMode); explicit ShareModel(QObject *parent = nullptr); @@ -135,16 +156,18 @@ public slots: void deleteShare(const OCC::SharePtr &share) const; void deleteShareFromQml(const QVariant &share) const; - void toggleShareAllowEditing(const OCC::SharePtr &share, const bool enable) const; - void toggleShareAllowEditingFromQml(const QVariant &share, const bool enable) const; - void toggleShareAllowResharing(const OCC::SharePtr &share, const bool enable) const; - void toggleShareAllowResharingFromQml(const QVariant &share, const bool enable) const; + void toggleHideDownloadFromQml(const QVariant &share, const bool enable); + void toggleShareAllowEditing(const OCC::SharePtr &share, const bool enable); + void toggleShareAllowEditingFromQml(const QVariant &share, const bool enable); + void toggleShareAllowResharing(const OCC::SharePtr &share, const bool enable); + void toggleShareAllowResharingFromQml(const QVariant &share, const bool enable); void toggleSharePasswordProtect(const OCC::SharePtr &share, const bool enable); void toggleSharePasswordProtectFromQml(const QVariant &share, const bool enable); void toggleShareExpirationDate(const OCC::SharePtr &share, const bool enable) const; void toggleShareExpirationDateFromQml(const QVariant &share, const bool enable) const; void toggleShareNoteToRecipient(const OCC::SharePtr &share, const bool enable) const; void toggleShareNoteToRecipientFromQml(const QVariant &share, const bool enable) const; + void changePermissionModeFromQml(const QVariant &share, const SharePermissionsMode permissionMode); void setLinkShareLabel(const QSharedPointer &linkShare, const QString &label) const; void setLinkShareLabelFromQml(const QVariant &linkShare, const QString &label) const; @@ -164,6 +187,8 @@ private slots: void handleSecureFileDropLinkShare(); void handleLinkShare(); void setupInternalLinkShare(); + void setSharePermissionChangeInProgress(const QString &shareId, const bool isInProgress); + void setHideDownloadEnabledChangeInProgress(const QString &shareId, const bool isInProgress); void slotPropfindReceived(const QVariantMap &result); void slotServerError(const int code, const QString &message); @@ -176,6 +201,7 @@ private slots: void slotSharePermissionsSet(const QString &shareId); void slotSharePasswordSet(const QString &shareId); void slotShareNoteSet(const QString &shareId); + void slotHideDownloadSet(const QString &shareId); void slotShareNameSet(const QString &shareId); void slotShareLabelSet(const QString &shareId); void slotShareExpireDateSet(const QString &shareId); @@ -187,9 +213,13 @@ private: [[nodiscard]] long long enforcedMaxExpireDateForShare(const SharePtr &share) const; [[nodiscard]] bool expireDateEnforcedForShare(const SharePtr &share) const; [[nodiscard]] bool validCapabilities() const; + [[nodiscard]] bool isSecureFileDropSupportedFolder() const; + [[nodiscard]] bool isEncryptedItem() const; bool _fetchOngoing = false; bool _hasInitialShareFetchCompleted = false; + bool _sharePermissionsChangeInProgress = false; + bool _hideDownloadEnabledChangeInProgress = false; SharePtr _placeholderLinkShare; SharePtr _internalLinkShare; SharePtr _secureFileDropPlaceholderLinkShare; @@ -201,8 +231,7 @@ private: QString _sharePath; SharePermissions _maxSharingPermissions; QByteArray _numericFileId; - bool _isEncryptedItem = false; - bool _isSecureFileDropSupportedFolder = false; + SharedItemType _sharedItemType = SharedItemType::SharedItemTypeUndefined; SyncJournalFileLockInfo _filelockState; QString _privateLinkUrl; diff --git a/src/gui/ocssharejob.cpp b/src/gui/ocssharejob.cpp index dd370f338..2eee30c1a 100644 --- a/src/gui/ocssharejob.cpp +++ b/src/gui/ocssharejob.cpp @@ -134,6 +134,18 @@ void OcsShareJob::setLabel(const QString &shareId, const QString &label) start(); } +void OcsShareJob::setHideDownload(const QString &shareId, const bool hideDownload) +{ + appendPath(shareId); + setVerb("PUT"); + + const auto value = QString::fromLatin1(hideDownload ? QByteArrayLiteral("true") : QByteArrayLiteral("false")); + addParam(QStringLiteral("hideDownload"), value); + _value = hideDownload; + + start(); +} + void OcsShareJob::createLinkShare(const QString &path, const QString &name, const QString &password) diff --git a/src/gui/ocssharejob.h b/src/gui/ocssharejob.h index ba75f94c0..2abaf80a4 100644 --- a/src/gui/ocssharejob.h +++ b/src/gui/ocssharejob.h @@ -102,6 +102,11 @@ public: */ void setLabel(const QString &shareId, const QString &label); + /** + * Set share hideDownload flag + */ + void setHideDownload(const QString &shareId, const bool hideDownload); + /** * Create a new link share * diff --git a/src/gui/sharemanager.cpp b/src/gui/sharemanager.cpp index c285331e2..00f812c31 100644 --- a/src/gui/sharemanager.cpp +++ b/src/gui/sharemanager.cpp @@ -203,7 +203,8 @@ LinkShare::LinkShare(AccountPtr account, const QUrl &url, const QDate &expireDate, const QString ¬e, - const QString &label) + const QString &label, + const bool hideDownload) : Share(account, id, uidowner, ownerDisplayName, path, Share::TypeLink, isPasswordSet, permissions) , _name(name) , _token(token) @@ -211,6 +212,7 @@ LinkShare::LinkShare(AccountPtr account, , _expireDate(expireDate) , _url(url) , _label(label) + , _hideDownload(hideDownload) { } @@ -239,6 +241,11 @@ QString LinkShare::getLabel() const return _label; } +bool LinkShare::getHideDownload() const +{ + return _hideDownload; +} + void LinkShare::setName(const QString &name) { createShareJob(&LinkShare::slotNameSet)->setName(getId(), name); @@ -270,6 +277,11 @@ void LinkShare::setLabel(const QString &label) createShareJob(&LinkShare::slotLabelSet)->setLabel(getId(), label); } +void LinkShare::setHideDownload(const bool hideDownload) +{ + createShareJob(&LinkShare::slotHideDownloadSet)->setHideDownload(getId(), hideDownload); +} + template OcsShareJob *LinkShare::createShareJob(const LinkShareSlot slotFunction) { auto *job = new OcsShareJob(_account); @@ -308,6 +320,16 @@ void LinkShare::slotLabelSet(const QJsonDocument &, const QVariant &label) } } +void LinkShare::slotHideDownloadSet(const QJsonDocument &jsonDoc, const QVariant &hideDownload) +{ + Q_UNUSED(jsonDoc); + if (!hideDownload.isValid()) { + return; + } + _hideDownload = hideDownload.toBool(); + emit hideDownloadSet(); +} + UserGroupShare::UserGroupShare(AccountPtr account, const QString &id, const QString &owner, @@ -583,7 +605,8 @@ QSharedPointer ShareManager::parseLinkShare(const QJsonObject &data) url, expireDate, note, - data.value("label").toString())); + data.value("label").toString(), + data.value("hide_download").toInt() == 1)); } SharePtr ShareManager::parseShare(const QJsonObject &data) const diff --git a/src/gui/sharemanager.h b/src/gui/sharemanager.h index 37f5ee6f9..fa74ceaf3 100644 --- a/src/gui/sharemanager.h +++ b/src/gui/sharemanager.h @@ -132,6 +132,7 @@ signals: void shareDeleted(); void serverError(int code, const QString &message); void passwordSet(); + void hideDownloadSet(); void passwordSetError(int statusCode, const QString &message); public slots: @@ -197,6 +198,7 @@ class LinkShare : public Share Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameSet) Q_PROPERTY(QString note READ getNote WRITE setNote NOTIFY noteSet) Q_PROPERTY(QString label READ getLabel WRITE setLabel NOTIFY labelSet) + Q_PROPERTY(bool hideDownload READ getHideDownload WRITE setHideDownload NOTIFY hideDownloadSet) Q_PROPERTY(QDate expireDate READ getExpireDate WRITE setExpireDate NOTIFY expireDateSet) Q_PROPERTY(QString token READ getToken CONSTANT) @@ -213,7 +215,8 @@ public: const QUrl &url, const QDate &expireDate, const QString ¬e, - const QString &label); + const QString &label, + const bool hideDownload); /* * Get the share link @@ -250,6 +253,11 @@ public: */ [[nodiscard]] QString getLabel() const; + /* + * Returns if the link share's hideDownload is true or false + */ + [[nodiscard]] bool getHideDownload() const; + /* * Returns the token of the link share. */ @@ -291,18 +299,25 @@ public slots: * Set the label of the share link. */ void setLabel(const QString &label); + + /* + * Set the hideDownload flag of the share link. + */ + void setHideDownload(const bool hideDownload); signals: void expireDateSet(); void noteSet(); void nameSet(); void labelSet(); + void hideDownloadSet(); private slots: void slotNoteSet(const QJsonDocument &, const QVariant &value); void slotExpireDateSet(const QJsonDocument &reply, const QVariant &value); void slotNameSet(const QJsonDocument &, const QVariant &value); void slotLabelSet(const QJsonDocument &, const QVariant &value); + void slotHideDownloadSet(const QJsonDocument &jsonDoc, const QVariant &hideDownload); private: QString _name; @@ -311,6 +326,7 @@ private: QDate _expireDate; QUrl _url; QString _label; + bool _hideDownload = false; }; class UserGroupShare : public Share diff --git a/theme/Style/Style.qml b/theme/Style/Style.qml index 5c0453fe6..1d788c55d 100644 --- a/theme/Style/Style.qml +++ b/theme/Style/Style.qml @@ -122,6 +122,11 @@ QtObject { readonly property int unifiedSearchResultSectionItemVerticalPadding: 8 readonly property int unifiedSearchResultNothingFoundHorizontalMargin: 10 + readonly property int radioButtonCustomMarginLeftInner: 4 + readonly property int radioButtonCustomMarginLeftOuter: 5 + readonly property int radioButtonCustomRadius: 9 + readonly property int radioButtonIndicatorSize: 16 + readonly property var fontMetrics: FontMetrics {} readonly property int activityContentSpace: 4