From 93d8810414d94cdb2b7e7adf00d2f58e5e3098a0 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Fri, 6 Apr 2018 17:13:29 +0200 Subject: [PATCH] SocketApi/Sharing: Add "copy public link" to menu #6356 * The new menu option will fetch shares and create a new link share if no "context menu share" currently exists. * Various cleanup of common operations in socketapi happened as well, in particular there's now FileData::get() that calculates all the relevant paths that are useful for most socketapi actions. --- src/gui/ocsshareejob.cpp | 2 + src/gui/ocssharejob.h | 2 + src/gui/owncloudgui.cpp | 4 +- src/gui/owncloudgui.h | 7 +- src/gui/sharedialog.cpp | 5 + src/gui/sharedialog.h | 3 + src/gui/socketapi.cpp | 419 +++++++++++++++++++++++++++------------ src/gui/socketapi.h | 37 +++- 8 files changed, 345 insertions(+), 134 deletions(-) diff --git a/src/gui/ocsshareejob.cpp b/src/gui/ocsshareejob.cpp index 40bb70d9164..0d1b6d44b2c 100644 --- a/src/gui/ocsshareejob.cpp +++ b/src/gui/ocsshareejob.cpp @@ -14,6 +14,8 @@ #include "ocsshareejob.h" +#include + namespace OCC { OcsShareeJob::OcsShareeJob(AccountPtr account) diff --git a/src/gui/ocssharejob.h b/src/gui/ocssharejob.h index 09c69410d8f..5760085fcc5 100644 --- a/src/gui/ocssharejob.h +++ b/src/gui/ocssharejob.h @@ -21,6 +21,8 @@ #include #include +class QJsonDocument; + namespace OCC { /** diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 5e193178aae..165d4f99a99 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -1032,7 +1032,7 @@ void ownCloudGui::raiseDialog(QWidget *raiseWidget) } -void ownCloudGui::slotShowShareDialog(const QString &sharePath, const QString &localPath) +void ownCloudGui::slotShowShareDialog(const QString &sharePath, const QString &localPath, ShareDialogStartPage startPage) { const auto folder = FolderMan::instance()->folderForPath(localPath); if (!folder) { @@ -1076,7 +1076,7 @@ void ownCloudGui::slotShowShareDialog(const QString &sharePath, const QString &l w = _shareDialogs[localPath]; } else { qCInfo(lcApplication) << "Opening share dialog" << sharePath << localPath << maxSharingPermissions; - w = new ShareDialog(accountState, sharePath, localPath, maxSharingPermissions, fileRecord.numericFileId()); + w = new ShareDialog(accountState, sharePath, localPath, maxSharingPermissions, fileRecord.numericFileId(), startPage); w->setAttribute(Qt::WA_DeleteOnClose, true); _shareDialogs[localPath] = w; diff --git a/src/gui/owncloudgui.h b/src/gui/owncloudgui.h index d1b60ae0e5e..9bc13647255 100644 --- a/src/gui/owncloudgui.h +++ b/src/gui/owncloudgui.h @@ -37,6 +37,11 @@ class Application; class LogBrowser; class AccountState; +enum class ShareDialogStartPage { + UsersAndGroups, + PublicLinks, +}; + /** * @brief The ownCloudGui class * @ingroup gui @@ -93,7 +98,7 @@ public slots: * localPath is the absolute local path to it (so not relative * to the folder). */ - void slotShowShareDialog(const QString &sharePath, const QString &localPath); + void slotShowShareDialog(const QString &sharePath, const QString &localPath, ShareDialogStartPage startPage); void slotRemoveDestroyedShareDialogs(); diff --git a/src/gui/sharedialog.cpp b/src/gui/sharedialog.cpp index 5c070a1259f..7b461050073 100644 --- a/src/gui/sharedialog.cpp +++ b/src/gui/sharedialog.cpp @@ -39,6 +39,7 @@ ShareDialog::ShareDialog(QPointer accountState, const QString &localPath, SharePermissions maxSharingPermissions, const QByteArray &numericFileId, + ShareDialogStartPage startPage, QWidget *parent) : QDialog(parent) , _ui(new Ui::ShareDialog) @@ -47,6 +48,7 @@ ShareDialog::ShareDialog(QPointer accountState, , _localPath(localPath) , _maxSharingPermissions(maxSharingPermissions) , _privateLinkUrl(accountState->account()->deprecatedPrivateLinkUrl(numericFileId).toString(QUrl::FullyEncoded)) + , _startPage(startPage) , _linkWidget(NULL) , _userGroupWidget(NULL) , _progressIndicator(NULL) @@ -217,6 +219,9 @@ void ShareDialog::showSharingUi() _linkWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); _ui->shareWidgets->addTab(_linkWidget, tr("Public Links")); _linkWidget->getShares(); + + if (_startPage == ShareDialogStartPage::PublicLinks) + _ui->shareWidgets->setCurrentWidget(_linkWidget); } } diff --git a/src/gui/sharedialog.h b/src/gui/sharedialog.h index 8af3f3fff8e..7aa93cfdeb7 100644 --- a/src/gui/sharedialog.h +++ b/src/gui/sharedialog.h @@ -17,6 +17,7 @@ #include "accountstate.h" #include "sharepermissions.h" +#include "owncloudgui.h" #include #include @@ -44,6 +45,7 @@ class ShareDialog : public QDialog const QString &localPath, SharePermissions maxSharingPermissions, const QByteArray &numericFileId, + ShareDialogStartPage startPage, QWidget *parent = 0); ~ShareDialog(); @@ -64,6 +66,7 @@ private slots: SharePermissions _maxSharingPermissions; QByteArray _numericFileId; QString _privateLinkUrl; + ShareDialogStartPage _startPage; ShareLinkWidget *_linkWidget; ShareUserGroupWidget *_userGroupWidget; diff --git a/src/gui/socketapi.cpp b/src/gui/socketapi.cpp index abcab4dacde..e1599ab90eb 100644 --- a/src/gui/socketapi.cpp +++ b/src/gui/socketapi.cpp @@ -32,6 +32,9 @@ #include "capabilities.h" #include "common/asserts.h" #include "guiutility.h" +#ifndef OWNCLOUD_TEST +#include "sharemanager.h" +#endif #include #include @@ -45,6 +48,7 @@ #include #include #include +#include #include @@ -82,6 +86,7 @@ static QString buildMessage(const QString &verb, const QString &path, const QStr namespace OCC { Q_LOGGING_CATEGORY(lcSocketApi, "gui.socketapi", QtInfoMsg) +Q_LOGGING_CATEGORY(lcPublicLink, "gui.socketapi.publiclink", QtInfoMsg) class BloomFilter { @@ -352,6 +357,49 @@ void SocketApi::broadcastMessage(const QString &msg, bool doWait) } } +void SocketApi::processShareRequest(const QString &localFile, SocketListener *listener, ShareDialogStartPage startPage) +{ + auto theme = Theme::instance(); + + auto fileData = FileData::get(localFile); + auto shareFolder = fileData.folder; + if (!shareFolder) { + const QString message = QLatin1String("SHARE:NOP:") + QDir::toNativeSeparators(localFile); + // files that are not within a sync folder are not synced. + listener->sendMessage(message); + } else if (!shareFolder->accountState()->isConnected()) { + const QString message = QLatin1String("SHARE:NOTCONNECTED:") + QDir::toNativeSeparators(localFile); + // if the folder isn't connected, don't open the share dialog + listener->sendMessage(message); + } else if (!theme->linkSharing() && (!theme->userGroupSharing() || shareFolder->accountState()->account()->serverVersionInt() < Account::makeServerVersion(8, 2, 0))) { + const QString message = QLatin1String("SHARE:NOP:") + QDir::toNativeSeparators(localFile); + listener->sendMessage(message); + } else { + SyncFileStatus fileStatus = fileData.syncFileStatus(); + + // Verify the file is on the server (to our knowledge of course) + if (fileStatus.tag() != SyncFileStatus::StatusUpToDate) { + const QString message = QLatin1String("SHARE:NOTSYNCED:") + QDir::toNativeSeparators(localFile); + listener->sendMessage(message); + return; + } + + auto &remotePath = fileData.accountRelativePath; + + // Can't share root folder + if (remotePath == "/") { + const QString message = QLatin1String("SHARE:CANNOTSHAREROOT:") + QDir::toNativeSeparators(localFile); + listener->sendMessage(message); + return; + } + + const QString message = QLatin1String("SHARE:OK:") + QDir::toNativeSeparators(localFile); + listener->sendMessage(message); + + emit shareCommandReceived(remotePath, fileData.localPath, startPage); + } +} + void SocketApi::broadcastStatusPushMessage(const QString &systemPath, SyncFileStatus fileStatus) { QString msg = buildMessage(QLatin1String("STATUS"), systemPath, fileStatus.toSocketAPIString()); @@ -372,23 +420,17 @@ void SocketApi::command_RETRIEVE_FILE_STATUS(const QString &argument, SocketList { QString statusString; - Folder *syncFolder = FolderMan::instance()->folderForPath(argument); - if (!syncFolder) { + auto fileData = FileData::get(argument); + if (!fileData.folder) { // this can happen in offline mode e.g.: nothing to worry about statusString = QLatin1String("NOP"); } else { - QString systemPath = QDir::cleanPath(argument); - if (systemPath.endsWith(QLatin1Char('/'))) { - systemPath.truncate(systemPath.length() - 1); - qCWarning(lcSocketApi) << "Removed trailing slash for directory: " << systemPath << "Status pushes won't have one."; - } // The user probably visited this directory in the file shell. // Let the listener know that it should now send status pushes for sibblings of this file. - QString directory = systemPath.left(systemPath.lastIndexOf('/')); + QString directory = fileData.localPath.left(fileData.localPath.lastIndexOf('/')); listener->registerMonitoredDirectory(qHash(directory)); - QString relativePath = systemPath.mid(syncFolder->cleanPath().length() + 1); - SyncFileStatus fileStatus = syncFolder->syncEngine().syncFileStatusTracker().fileStatus(relativePath); + SyncFileStatus fileStatus = fileData.syncFileStatus(); statusString = fileStatus.toSocketAPIString(); } @@ -398,152 +440,223 @@ void SocketApi::command_RETRIEVE_FILE_STATUS(const QString &argument, SocketList void SocketApi::command_SHARE(const QString &localFile, SocketListener *listener) { - auto theme = Theme::instance(); + processShareRequest(localFile, listener, ShareDialogStartPage::UsersAndGroups); +} - Folder *shareFolder = FolderMan::instance()->folderForPath(localFile); - if (!shareFolder) { - const QString message = QLatin1String("SHARE:NOP:") + QDir::toNativeSeparators(localFile); - // files that are not within a sync folder are not synced. +void SocketApi::command_MANAGE_PUBLIC_LINKS(const QString &localFile, SocketListener *listener) +{ + processShareRequest(localFile, listener, ShareDialogStartPage::PublicLinks); +} + +void SocketApi::command_VERSION(const QString &, SocketListener *listener) +{ + listener->sendMessage(QLatin1String("VERSION:" MIRALL_VERSION_STRING ":" MIRALL_SOCKET_API_VERSION)); +} + +void SocketApi::command_SHARE_STATUS(const QString &localFile, SocketListener *listener) +{ + auto fileData = FileData::get(localFile); + if (!fileData.folder) { + const QString message = QLatin1String("SHARE_STATUS:NOP:") + QDir::toNativeSeparators(localFile); listener->sendMessage(message); - } else if (!shareFolder->accountState()->isConnected()) { - const QString message = QLatin1String("SHARE:NOTCONNECTED:") + QDir::toNativeSeparators(localFile); - // if the folder isn't connected, don't open the share dialog + return; + } + + SyncFileStatus fileStatus = fileData.syncFileStatus(); + + // Verify the file is on the server (to our knowledge of course) + if (fileStatus.tag() != SyncFileStatus::StatusUpToDate) { + const QString message = QLatin1String("SHARE_STATUS:NOTSYNCED:") + QDir::toNativeSeparators(localFile); listener->sendMessage(message); - } else if (!theme->linkSharing() && (!theme->userGroupSharing() || shareFolder->accountState()->account()->serverVersionInt() < Account::makeServerVersion(8, 2, 0))) { - const QString message = QLatin1String("SHARE:NOP:") + QDir::toNativeSeparators(localFile); + return; + } + + const Capabilities capabilities = fileData.folder->accountState()->account()->capabilities(); + + if (!capabilities.shareAPI()) { + const QString message = QLatin1String("SHARE_STATUS:DISABLED:") + QDir::toNativeSeparators(localFile); listener->sendMessage(message); } else { - const QString localFileClean = QDir::cleanPath(localFile); - const QString file = localFileClean.mid(shareFolder->cleanPath().length() + 1); - SyncFileStatus fileStatus = shareFolder->syncEngine().syncFileStatusTracker().fileStatus(file); + auto theme = Theme::instance(); + QString available; - // Verify the file is on the server (to our knowledge of course) - if (fileStatus.tag() != SyncFileStatus::StatusUpToDate) { - const QString message = QLatin1String("SHARE:NOTSYNCED:") + QDir::toNativeSeparators(localFile); - listener->sendMessage(message); - return; + if (theme->userGroupSharing()) { + available = "USER,GROUP"; } - const QString remotePath = QDir(shareFolder->remotePath()).filePath(file); + if (theme->linkSharing() && capabilities.sharePublicLink()) { + if (available.isEmpty()) { + available = "LINK"; + } else { + available += ",LINK"; + } + } - // Can't share root folder - if (remotePath == "/") { - const QString message = QLatin1String("SHARE:CANNOTSHAREROOT:") + QDir::toNativeSeparators(localFile); + if (available.isEmpty()) { + const QString message = QLatin1String("SHARE_STATUS:DISABLED") + ":" + QDir::toNativeSeparators(localFile); + listener->sendMessage(message); + } else { + const QString message = QLatin1String("SHARE_STATUS:") + available + ":" + QDir::toNativeSeparators(localFile); listener->sendMessage(message); - return; } - - const QString message = QLatin1String("SHARE:OK:") + QDir::toNativeSeparators(localFile); - listener->sendMessage(message); - - emit shareCommandReceived(remotePath, localFileClean); } } -void SocketApi::command_VERSION(const QString &, SocketListener *listener) +void SocketApi::command_SHARE_MENU_TITLE(const QString &, SocketListener *listener) { - listener->sendMessage(QLatin1String("VERSION:" MIRALL_VERSION_STRING ":" MIRALL_SOCKET_API_VERSION)); + listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + tr("Share with %1", "parameter is ownCloud").arg(Theme::instance()->appNameGUI())); } -void SocketApi::command_SHARE_STATUS(const QString &localFile, SocketListener *listener) +// don't pull the share manager into socketapi unittests +#ifndef OWNCLOUD_TEST + +class GetOrCreatePublicLinkShare : public QObject { - Folder *shareFolder = FolderMan::instance()->folderForPath(localFile); + Q_OBJECT +public: + GetOrCreatePublicLinkShare(const AccountPtr &account, const QString &localFile, + std::function targetFun, QObject *parent) + : QObject(parent) + , _shareManager(account) + , _localFile(localFile) + , _targetFun(targetFun) + { + connect(&_shareManager, &ShareManager::sharesFetched, + this, &GetOrCreatePublicLinkShare::sharesFetched); + connect(&_shareManager, &ShareManager::linkShareCreated, + this, &GetOrCreatePublicLinkShare::linkShareCreated); + connect(&_shareManager, &ShareManager::serverError, + this, &GetOrCreatePublicLinkShare::serverError); + } - if (!shareFolder) { - const QString message = QLatin1String("SHARE_STATUS:NOP:") + QDir::toNativeSeparators(localFile); - listener->sendMessage(message); - } else { - const QString file = QDir::cleanPath(localFile).mid(shareFolder->cleanPath().length() + 1); - SyncFileStatus fileStatus = shareFolder->syncEngine().syncFileStatusTracker().fileStatus(file); + void run() + { + qCDebug(lcPublicLink) << "Fetching shares"; + _shareManager.fetchShares(_localFile); + } - // Verify the file is on the server (to our knowledge of course) - if (fileStatus.tag() != SyncFileStatus::StatusUpToDate) { - const QString message = QLatin1String("SHARE_STATUS:NOTSYNCED:") + QDir::toNativeSeparators(localFile); - listener->sendMessage(message); - return; +private slots: + void sharesFetched(const QList> &shares) + { + auto shareName = SocketApi::tr("Context menu share"); + // If there already is a context menu share, reuse it + for (const auto &share : shares) { + const auto linkShare = qSharedPointerDynamicCast(share); + if (!linkShare) + continue; + + if (linkShare->getName() == shareName) { + qCDebug(lcPublicLink) << "Found existing share, reusing"; + return success(linkShare->getLink().toString()); + } } - const Capabilities capabilities = shareFolder->accountState()->account()->capabilities(); + // otherwise create a new one + qCDebug(lcPublicLink) << "Creating new share"; + _shareManager.createLinkShare(_localFile, shareName, QString()); + } - if (!capabilities.shareAPI()) { - const QString message = QLatin1String("SHARE_STATUS:DISABLED:") + QDir::toNativeSeparators(localFile); - listener->sendMessage(message); - } else { - auto theme = Theme::instance(); - QString available; + void linkShareCreated(const QSharedPointer &share) + { + qCDebug(lcPublicLink) << "New share created"; + success(share->getLink().toString()); + } - if (theme->userGroupSharing()) { - available = "USER,GROUP"; - } + void serverError(int code, const QString &message) + { + qCWarning(lcPublicLink) << "Share fetch/create error" << code << message; + QMessageBox::warning( + 0, + tr("Sharing error"), + tr("Could not retrieve or create the public link share. Error:\n\n%1").arg(message), + QMessageBox::Ok, + QMessageBox::NoButton); + deleteLater(); + } - if (theme->linkSharing() && capabilities.sharePublicLink()) { - if (available.isEmpty()) { - available = "LINK"; - } else { - available += ",LINK"; - } - } +private: + void success(const QString &link) + { + _targetFun(link); + deleteLater(); + } - if (available.isEmpty()) { - const QString message = QLatin1String("SHARE_STATUS:DISABLED") + ":" + QDir::toNativeSeparators(localFile); - listener->sendMessage(message); - } else { - const QString message = QLatin1String("SHARE_STATUS:") + available + ":" + QDir::toNativeSeparators(localFile); - listener->sendMessage(message); - } - } + ShareManager _shareManager; + QString _localFile; + std::function _targetFun; +}; + +#else + +class GetOrCreatePublicLinkShare : public QObject +{ + Q_OBJECT +public: + GetOrCreatePublicLinkShare(const AccountPtr &, const QString &, + std::function, QObject *) + { } -} -void SocketApi::command_SHARE_MENU_TITLE(const QString &, SocketListener *listener) + void run() + { + } +}; + +#endif + +void SocketApi::command_COPY_PUBLIC_LINK(const QString &localFile, SocketListener *) { - listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + tr("Share with %1", "parameter is ownCloud").arg(Theme::instance()->appNameGUI())); + auto fileData = FileData::get(localFile); + if (!fileData.folder) + return; + + AccountPtr account = fileData.folder->accountState()->account(); + auto job = new GetOrCreatePublicLinkShare(account, fileData.accountRelativePath, [this](const QString &url) { copyUrlToClipboard(url); }, this); + job->run(); } // Fetches the private link url asynchronously and then calls the target slot -static void fetchPrivateLinkUrlHelper(const QString &localFile, SocketApi *target, void (SocketApi::*targetFun)(const QString &url) const) +void SocketApi::fetchPrivateLinkUrlHelper(const QString &localFile, const std::function &targetFun) { - Folder *shareFolder = FolderMan::instance()->folderForPath(localFile); - if (!shareFolder) { + auto fileData = FileData::get(localFile); + if (!fileData.folder) { qCWarning(lcSocketApi) << "Unknown path" << localFile; return; } - const QString localFileClean = QDir::cleanPath(localFile); - const QString file = localFileClean.mid(shareFolder->cleanPath().length() + 1); - - AccountPtr account = shareFolder->accountState()->account(); - - SyncJournalFileRecord rec; - if (!shareFolder->journalDb()->getFileRecord(file, &rec) || !rec.isValid()) + auto record = fileData.journalRecord(); + if (!record.isValid()) return; - fetchPrivateLinkUrl(account, file, rec.numericFileId(), target, [=](const QString &url) { - (target->*targetFun)(url); - }); + fetchPrivateLinkUrl( + fileData.folder->accountState()->account(), + fileData.accountRelativePath, + record.numericFileId(), + this, + targetFun); } void SocketApi::command_COPY_PRIVATE_LINK(const QString &localFile, SocketListener *) { - fetchPrivateLinkUrlHelper(localFile, this, &SocketApi::copyPrivateLinkToClipboard); + fetchPrivateLinkUrlHelper(localFile, &SocketApi::copyUrlToClipboard); } void SocketApi::command_EMAIL_PRIVATE_LINK(const QString &localFile, SocketListener *) { - fetchPrivateLinkUrlHelper(localFile, this, &SocketApi::emailPrivateLink); + fetchPrivateLinkUrlHelper(localFile, &SocketApi::emailPrivateLink); } void SocketApi::command_OPEN_PRIVATE_LINK(const QString &localFile, SocketListener *) { - fetchPrivateLinkUrlHelper(localFile, this, &SocketApi::openPrivateLink); + fetchPrivateLinkUrlHelper(localFile, &SocketApi::openPrivateLink); } -void SocketApi::copyPrivateLinkToClipboard(const QString &link) const +void SocketApi::copyUrlToClipboard(const QString &link) { QApplication::clipboard()->setText(link); } -void SocketApi::emailPrivateLink(const QString &link) const +void SocketApi::emailPrivateLink(const QString &link) { Utility::openEmailComposer( tr("I shared something with you"), @@ -551,7 +664,7 @@ void SocketApi::emailPrivateLink(const QString &link) const 0); } -void OCC::SocketApi::openPrivateLink(const QString &link) const +void OCC::SocketApi::openPrivateLink(const QString &link) { Utility::openBrowser(link, nullptr); } @@ -573,37 +686,89 @@ void SocketApi::command_GET_STRINGS(const QString &argument, SocketListener *lis listener->sendMessage(QString("GET_STRINGS:END")); } -void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListener *listener) +void SocketApi::sendSharingContextMenuOptions(const FileData &fileData, SocketListener *listener) { - listener->sendMessage(QString("GET_MENU_ITEMS:BEGIN")); - bool hasSeveralFiles = argument.contains(QLatin1Char('\x1e')); // Record Separator - Folder *syncFolder = hasSeveralFiles ? nullptr : FolderMan::instance()->folderForPath(argument); - if (syncFolder && syncFolder->accountState()->isConnected()) { - QString systemPath = QDir::cleanPath(argument); - if (systemPath.endsWith(QLatin1Char('/'))) { - systemPath.truncate(systemPath.length() - 1); - } + auto record = fileData.journalRecord(); + bool isOnTheServer = record.isValid(); + auto flagString = isOnTheServer ? QLatin1String("::") : QLatin1String(":d:"); - SyncJournalFileRecord rec; - QString relativePath = systemPath.mid(syncFolder->cleanPath().length() + 1); - // If the file is on the DB, it is on the server - bool isOnTheServer = syncFolder->journalDb()->getFileRecord(relativePath, &rec) && rec.isValid(); - auto flagString = isOnTheServer ? QLatin1String("::") : QLatin1String(":d:"); + auto capabilities = fileData.folder->accountState()->account()->capabilities(); + auto theme = Theme::instance(); + if (!capabilities.shareAPI() || !(theme->userGroupSharing() || (theme->linkSharing() && capabilities.sharePublicLink()))) + return; - auto capabilities = syncFolder->accountState()->account()->capabilities(); - auto theme = Theme::instance(); - if (capabilities.shareAPI() && (theme->userGroupSharing() || (theme->linkSharing() && capabilities.sharePublicLink()))) { - // If sharing is globally disabled, do not show any sharing entries. - // If there is no permission to share for this file, add a disabled entry saying so - if (isOnTheServer && !rec._remotePerm.isNull() && !rec._remotePerm.hasPermission(RemotePermissions::CanReshare)) { - listener->sendMessage(QLatin1String("MENU_ITEM:DISABLED:d:") + tr("Resharing this file is not allowed")); - } else { - listener->sendMessage(QLatin1String("MENU_ITEM:SHARE") + flagString + tr("Share...")); - } - listener->sendMessage(QLatin1String("MENU_ITEM:EMAIL_PRIVATE_LINK") + flagString + tr("Send private link by email...")); - listener->sendMessage(QLatin1String("MENU_ITEM:COPY_PRIVATE_LINK") + flagString + tr("Copy private link to clipboard")); + // If sharing is globally disabled, do not show any sharing entries. + // If there is no permission to share for this file, add a disabled entry saying so + if (isOnTheServer && !record._remotePerm.isNull() && !record._remotePerm.hasPermission(RemotePermissions::CanReshare)) { + listener->sendMessage(QLatin1String("MENU_ITEM:DISABLED:d:") + tr("Resharing this file is not allowed")); + } else { + listener->sendMessage(QLatin1String("MENU_ITEM:SHARE") + flagString + tr("Share...")); + + // Do we have public links? + bool publicLinksEnabled = theme->linkSharing() && capabilities.sharePublicLink(); + + // Is is possible to create a public link without user choices? + bool canCreateDefaultPublicLink = publicLinksEnabled + && !capabilities.sharePublicLinkEnforceExpireDate() + && !capabilities.sharePublicLinkEnforcePassword(); + + if (canCreateDefaultPublicLink) { + listener->sendMessage(QLatin1String("MENU_ITEM:COPY_PUBLIC_LINK") + flagString + tr("Copy public link to clipboard")); + } else if (publicLinksEnabled) { + listener->sendMessage(QLatin1String("MENU_ITEM:MANAGE_PUBLIC_LINKS") + flagString + tr("Copy public link to clipboard")); } + } + + listener->sendMessage(QLatin1String("MENU_ITEM:COPY_PRIVATE_LINK") + flagString + tr("Copy private link to clipboard")); + + // Disabled: only providing email option for private links would look odd, + // and the copy option is more general. + //listener->sendMessage(QLatin1String("MENU_ITEM:EMAIL_PRIVATE_LINK") + flagString + tr("Send private link by email...")); +} + +SocketApi::FileData SocketApi::FileData::get(const QString &localFile) +{ + FileData data; + + data.localPath = QDir::cleanPath(localFile); + if (data.localPath.endsWith(QLatin1Char('/'))) + data.localPath.chop(1); + + data.folder = FolderMan::instance()->folderForPath(data.localPath); + if (!data.folder) + return data; + + data.folderRelativePath = data.localPath.mid(data.folder->cleanPath().length() + 1); + data.accountRelativePath = QDir(data.folder->remotePath()).filePath(data.folderRelativePath); + + return data; +} +SyncFileStatus SocketApi::FileData::syncFileStatus() const +{ + if (!folder) + return SyncFileStatus::StatusNone; + return folder->syncEngine().syncFileStatusTracker().fileStatus(folderRelativePath); +} + +SyncJournalFileRecord SocketApi::FileData::journalRecord() const +{ + SyncJournalFileRecord record; + if (!folder) + return record; + folder->journalDb()->getFileRecord(folderRelativePath, &record); + return record; +} + +void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListener *listener) +{ + listener->sendMessage(QString("GET_MENU_ITEMS:BEGIN")); + bool hasSeveralFiles = argument.contains(QLatin1Char('\x1e')); // Record Separator + FileData fileData = hasSeveralFiles ? FileData{} : FileData::get(argument); + bool isOnTheServer = fileData.journalRecord().isValid(); + auto flagString = isOnTheServer ? QLatin1String("::") : QLatin1String(":d:"); + if (fileData.folder && fileData.folder->accountState()->isConnected()) { + sendSharingContextMenuOptions(fileData, listener); listener->sendMessage(QLatin1String("MENU_ITEM:OPEN_PRIVATE_LINK") + flagString + tr("Open in browser")); } listener->sendMessage(QString("GET_MENU_ITEMS:END")); @@ -618,3 +783,5 @@ QString SocketApi::buildRegisterPathMessage(const QString &path) } } // namespace OCC + +#include "socketapi.moc" diff --git a/src/gui/socketapi.h b/src/gui/socketapi.h index a3b13151790..c0ebeda94e9 100644 --- a/src/gui/socketapi.h +++ b/src/gui/socketapi.h @@ -18,7 +18,8 @@ #include "syncfileitem.h" #include "syncfilestatus.h" -// #include "ownsql.h" +#include "sharedialog.h" // for the ShareDialogStartPage +#include "common/syncjournalfilerecord.h" #if defined(Q_OS_MAC) #include "socketapisocket_mac.h" @@ -56,7 +57,7 @@ public slots: void broadcastStatusPushMessage(const QString &systemPath, SyncFileStatus fileStatus); signals: - void shareCommandReceived(const QString &sharePath, const QString &localPath); + void shareCommandReceived(const QString &sharePath, const QString &localPath, ShareDialogStartPage startPage); private slots: void slotNewConnection(); @@ -64,13 +65,31 @@ private slots: void slotSocketDestroyed(QObject *obj); void slotReadSocket(); - void copyPrivateLinkToClipboard(const QString &link) const; - void emailPrivateLink(const QString &link) const; - void openPrivateLink(const QString &link) const; + static void copyUrlToClipboard(const QString &link); + static void emailPrivateLink(const QString &link); + static void openPrivateLink(const QString &link); private: + // Helper structure for getting information on a file + // based on its local path - used for nearly all remote + // actions. + struct FileData + { + static FileData get(const QString &localFile); + SyncFileStatus syncFileStatus() const; + SyncJournalFileRecord journalRecord() const; + + Folder *folder; + QString localPath; + QString folderRelativePath; + QString accountRelativePath; + }; + void broadcastMessage(const QString &msg, bool doWait = false); + // opens share dialog, sends reply + void processShareRequest(const QString &localFile, SocketListener *listener, ShareDialogStartPage startPage); + Q_INVOKABLE void command_RETRIEVE_FOLDER_STATUS(const QString &argument, SocketListener *listener); Q_INVOKABLE void command_RETRIEVE_FILE_STATUS(const QString &argument, SocketListener *listener); @@ -81,13 +100,21 @@ private slots: // The context menu actions Q_INVOKABLE void command_SHARE(const QString &localFile, SocketListener *listener); + Q_INVOKABLE void command_MANAGE_PUBLIC_LINKS(const QString &localFile, SocketListener *listener); + Q_INVOKABLE void command_COPY_PUBLIC_LINK(const QString &localFile, SocketListener *listener); Q_INVOKABLE void command_COPY_PRIVATE_LINK(const QString &localFile, SocketListener *listener); Q_INVOKABLE void command_EMAIL_PRIVATE_LINK(const QString &localFile, SocketListener *listener); Q_INVOKABLE void command_OPEN_PRIVATE_LINK(const QString &localFile, SocketListener *listener); + // Fetch the private link and call targetFun + void fetchPrivateLinkUrlHelper(const QString &localFile, const std::function &targetFun); + /** Sends translated/branded strings that may be useful to the integration */ Q_INVOKABLE void command_GET_STRINGS(const QString &argument, SocketListener *listener); + // Sends the context menu options relating to sharing to listener + void sendSharingContextMenuOptions(const FileData &fileData, SocketListener *listener); + /** Send the list of menu item. (added in version 1.1) * argument is a list of files for which the menu should be shown, separated by '\x1e' * Reply with GET_MENU_ITEMS:BEGIN