Преглед изворни кода

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.
Christian Kamm пре 7 година
родитељ
комит
550b845037

+ 2 - 0
src/gui/ocsshareejob.cpp

@@ -14,6 +14,8 @@
 
 #include "ocsshareejob.h"
 
+#include <QJsonDocument>
+
 namespace OCC {
 
 OcsShareeJob::OcsShareeJob(AccountPtr account)

+ 2 - 0
src/gui/ocssharejob.h

@@ -21,6 +21,8 @@
 #include <QList>
 #include <QPair>
 
+class QJsonDocument;
+
 namespace OCC {
 
 /**

+ 2 - 2
src/gui/owncloudgui.cpp

@@ -1121,7 +1121,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) {
@@ -1165,7 +1165,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;

+ 6 - 1
src/gui/owncloudgui.h

@@ -40,6 +40,11 @@ class Application;
 class LogBrowser;
 class AccountState;
 
+enum class ShareDialogStartPage {
+    UsersAndGroups,
+    PublicLinks,
+};
+
 /**
  * @brief The ownCloudGui class
  * @ingroup gui
@@ -104,7 +109,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();
 

+ 5 - 0
src/gui/sharedialog.cpp

@@ -39,6 +39,7 @@ ShareDialog::ShareDialog(QPointer<AccountState> 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> accountState,
     , _localPath(localPath)
     , _maxSharingPermissions(maxSharingPermissions)
     , _privateLinkUrl(accountState->account()->deprecatedPrivateLinkUrl(numericFileId).toString(QUrl::FullyEncoded))
+    , _startPage(startPage)
     , _linkWidget(NULL)
     , _userGroupWidget(NULL)
     , _progressIndicator(NULL)
@@ -218,6 +220,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);
     }
 }
 

+ 3 - 0
src/gui/sharedialog.h

@@ -17,6 +17,7 @@
 
 #include "accountstate.h"
 #include "sharepermissions.h"
+#include "owncloudgui.h"
 
 #include <QPointer>
 #include <QString>
@@ -44,6 +45,7 @@ public:
         const QString &localPath,
         SharePermissions maxSharingPermissions,
         const QByteArray &numericFileId,
+        ShareDialogStartPage startPage,
         QWidget *parent = 0);
     ~ShareDialog();
 
@@ -64,6 +66,7 @@ private:
     SharePermissions _maxSharingPermissions;
     QByteArray _numericFileId;
     QString _privateLinkUrl;
+    ShareDialogStartPage _startPage;
 
     ShareLinkWidget *_linkWidget;
     ShareUserGroupWidget *_userGroupWidget;

+ 295 - 127
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 <array>
 #include <QBitArray>
@@ -45,6 +48,7 @@
 #include <QApplication>
 #include <QLocalSocket>
 #include <QStringBuilder>
+#include <QMessageBox>
 
 #include <QClipboard>
 
@@ -81,7 +85,9 @@ static QString buildMessage(const QString &verb, const QString &path, const QStr
 
 namespace OCC {
 
-Q_LOGGING_CATEGORY(lcSocketApi, "nextcloud.gui.socketapi", QtInfoMsg)
+Q_LOGGING_CATEGORY(lcSocketApi, "gui.socketapi", QtInfoMsg)
+Q_LOGGING_CATEGORY(lcPublicLink, "gui.socketapi.publiclink", QtInfoMsg)
+
 
 class BloomFilter
 {
@@ -352,6 +358,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 +421,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 +441,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<void(const QString &link)> 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<QSharedPointer<Share>> &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<LinkShare>(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<LinkShare> &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<void(const QString &url)> _targetFun;
+};
+
+#else
+
+class GetOrCreatePublicLinkShare : public QObject
+{
+    Q_OBJECT
+public:
+    GetOrCreatePublicLinkShare(const AccountPtr &, const QString &,
+        std::function<void(const QString &link)>, 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<void(const QString &url)> &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 +665,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 +687,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 +784,5 @@ QString SocketApi::buildRegisterPathMessage(const QString &path)
 }
 
 } // namespace OCC
+
+#include "socketapi.moc"

+ 32 - 5
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:
 
     // 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<void(const QString &url)> &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