Introduce private link sharing #5023
authorChristian Kamm <mail@ckamm.de>
Wed, 10 May 2017 07:37:10 +0000 (09:37 +0200)
committerChristian Kamm <mail@ckamm.de>
Fri, 7 Jul 2017 08:49:51 +0000 (10:49 +0200)
* SocketAPI has COPL_LOCAL_LINK / EMAIL_LOCAL_LINK commands
* The nautilus and dolphing shell integrations show a submenu from which
  one can share as well as access the private link.
* The SocketAPI provides a new GET_STRINGS command to access localized
  strings.
* The private link can also be accessed from the user/group sharing
  dialog.
* The numeric file id is extracted from the full id to create the
  private link url.

25 files changed:
shell_integration/dolphin/ownclouddolphinactionplugin.cpp
shell_integration/dolphin/ownclouddolphinpluginhelper.cpp
shell_integration/dolphin/ownclouddolphinpluginhelper.h
shell_integration/nautilus/syncstate.py
src/gui/CMakeLists.txt
src/gui/application.cpp
src/gui/clipboard.mm [deleted file]
src/gui/guiutility.cpp [new file with mode: 0644]
src/gui/guiutility.h [new file with mode: 0644]
src/gui/owncloudgui.cpp
src/gui/owncloudgui.h
src/gui/sharedialog.cpp
src/gui/sharedialog.h
src/gui/sharelinkwidget.cpp
src/gui/shareusergroupwidget.cpp
src/gui/shareusergroupwidget.h
src/gui/shareusergroupwidget.ui
src/gui/socketapi.cpp
src/gui/socketapi.h
src/libsync/account.cpp
src/libsync/account.h
src/libsync/propagatorjobs.cpp
src/libsync/syncjournalfilerecord.cpp
src/libsync/syncjournalfilerecord.h
test/CMakeLists.txt

index dfb2bdff77adf60da6582dba9f4aba848efaf3e2..c47cb783d5e252b85becb3c1651f5e2861897965 100644 (file)
@@ -24,6 +24,7 @@
 #include <KIOCore/kfileitem.h>
 #include <KIOCore/KFileItemListProperties>
 #include <QtWidgets/QAction>
+#include <QtWidgets/QMenu>
 #include <QtCore/QDir>
 #include <QtCore/QTimer>
 #include "ownclouddolphinpluginhelper.h"
@@ -53,12 +54,31 @@ public:
                         } ))
              return {};
 
-        auto act = new QAction(parentWidget);
-        act->setText(helper->shareActionString());
-        connect(act, &QAction::triggered, this, [localFile, helper] {
+        auto menuaction = new QAction(parentWidget);
+        menuaction->setText(helper->contextMenuTitle());
+        auto menu = new QMenu(parentWidget);
+        menuaction->setMenu(menu);
+
+        auto shareAction = menu->addAction(helper->shareActionTitle());
+        connect(shareAction, &QAction::triggered, this, [localFile, helper] {
             helper->sendCommand(QByteArray("SHARE:"+localFile.toUtf8()+"\n"));
         } );
-        return { act };
+
+        if (!helper->copyPrivateLinkTitle().isEmpty()) {
+            auto copyPrivateLinkAction = menu->addAction(helper->copyPrivateLinkTitle());
+            connect(copyPrivateLinkAction, &QAction::triggered, this, [localFile, helper] {
+                helper->sendCommand(QByteArray("COPY_PRIVATE_LINK:" + localFile.toUtf8() + "\n"));
+            });
+        }
+
+        if (!helper->emailPrivateLinkTitle().isEmpty()) {
+            auto emailPrivateLinkAction = menu->addAction(helper->emailPrivateLinkTitle());
+            connect(emailPrivateLinkAction, &QAction::triggered, this, [localFile, helper] {
+                helper->sendCommand(QByteArray("EMAIL_PRIVATE_LINK:" + localFile.toUtf8() + "\n"));
+            });
+        }
+
+        return { menuaction };
     }
 
 };
index 68c2a9c2975005fb5f917afc9f0275f67c7a7eaa..ae1705220f60d6e69a0143195a6afa061676fd5b 100644 (file)
@@ -59,7 +59,7 @@ void OwncloudDolphinPluginHelper::sendCommand(const char* data)
 
 void OwncloudDolphinPluginHelper::slotConnected()
 {
-    sendCommand("SHARE_MENU_TITLE:\n");
+    sendCommand("GET_STRINGS:\n");
 }
 
 void OwncloudDolphinPluginHelper::tryConnect()
@@ -92,9 +92,11 @@ void OwncloudDolphinPluginHelper::slotReadyRead()
             QString file = QString::fromUtf8(line.constData() + col + 1, line.size() - col - 1);
             _paths.append(file);
             continue;
-        } else if (line.startsWith("SHARE_MENU_TITLE:")) {
-            auto col = line.indexOf(':');
-            _shareActionString = QString::fromUtf8(line.constData() + col + 1, line.size() - col - 1);
+        } else if (line.startsWith("STRING:")) {
+            auto args = QString::fromUtf8(line).split(QLatin1Char(':'));
+            if (args.size() >= 3) {
+                _strings[args[1]] = args.mid(2).join(QLatin1Char(':'));
+            }
             continue;
         }
         emit commandRecieved(line);
index 26762caaf444a0dc58269246b508187572be94d7..7d4e7ba14ffd6e8fd2d816e8405cc470fbc3930b 100644 (file)
@@ -28,11 +28,22 @@ class OWNCLOUDDOLPHINPLUGINHELPER_EXPORT OwncloudDolphinPluginHelper : public QO
 public:
     static OwncloudDolphinPluginHelper *instance();
 
-    QString shareActionString() const { return _shareActionString; }
     bool isConnected() const;
     void sendCommand(const char *data);
     QVector<QString> paths() const { return _paths; }
 
+    QString contextMenuTitle() const
+    {
+        return _strings.value("CONTEXT_MENU_TITLE", "ownCloud");
+    }
+    QString shareActionTitle() const
+    {
+        return _strings.value("SHARE_MENU_TITLE", "Share...");
+    }
+
+    QString copyPrivateLinkTitle() const { return _strings["COPY_PRIVATE_LINK_TITLE"]; }
+    QString emailPrivateLinkTitle() const { return _strings["EMAIL_PRIVATE_LINK_TITLE"]; }
+
 signals:
     void commandRecieved(const QByteArray &cmd);
 
@@ -47,6 +58,7 @@ private:
     QLocalSocket _socket;
     QByteArray _line;
     QVector<QString> _paths;
-    QString _shareActionString;
     QBasicTimer _connectTimer;
+
+    QMap<QString, QString> _strings;
 };
index 7fb35b80c677a9bc915783d02576eb8f66237d8e..9a0f35ed74b444df96b9183d112b23a38dd2f3fd 100644 (file)
@@ -95,6 +95,9 @@ class SocketConnect(GObject.GObject):
                 print("Setting connected to %r." % self.connected )
                 self._watch_id = GObject.io_add_watch(self._sock, GObject.IO_IN, self._handle_notify)
                 print("Socket watch id: " + str(self._watch_id))
+
+                self.sendCommand('GET_STRINGS:\n')
+
                 return False  # Don't run again
             except Exception as e:
                 print("Could not connect to unix socket. " + str(e))
@@ -153,6 +156,13 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider):
     def __init__(self):
         GObject.GObject.__init__(self)
 
+        self.strings = {}
+        socketConnect.addListener(self.handle_commands)
+
+    def handle_commands(self, action, args):
+        if action == 'STRING':
+            self.strings[args[0]] = ':'.join(args[1:])
+
     def check_registered_paths(self, filename):
         topLevelFolder = False
         internalFile = False
@@ -178,7 +188,6 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider):
         if len(files) != 1:
             return
         file = files[0]
-        items = []
 
         filename = get_local_path(file.get_uri())
         # Check if its a folder (ends with an /), if yes add a "/"
@@ -190,12 +199,14 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider):
         # Check if toplevel folder, we need to ignore those as they cannot be shared
         topLevelFolder, internalFile = self.check_registered_paths(filename)
         if topLevelFolder or not internalFile:
-            return items
+            return []
 
         entry = socketConnect.nautilusVFSFile_table.get(filename)
         if not entry:
-            return items
+            return []
 
+        # Currently 'sharable' also controls access to private link actions,
+        # and we definitely don't want to show them for IGNORED.
         shareable = False
         state = entry['state']
         state_ok = state.startswith('OK')
@@ -212,22 +223,42 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider):
                         break
 
         if not shareable:
-            return items
-
-        # Create a menu item
-        labelStr = "Share with " + appname + "..."
-        item = Nautilus.MenuItem(name='NautilusPython::ShareItem', label=labelStr,
-                tip='Share file {} through {}'.format(file.get_name(), appname) )
-        item.connect("activate", self.menu_share, file)
-        items.append(item)
-
-        return items
-
-
-    def menu_share(self, menu, file):
+            return []
+
+        # Set up the 'ownCloud...' submenu
+        item_owncloud = Nautilus.MenuItem(
+            name='IntegrationMenu', label=self.strings.get('CONTEXT_MENU_TITLE', appname))
+        menu = Nautilus.Menu()
+        item_owncloud.set_submenu(menu)
+
+        # Add share menu option
+        item = Nautilus.MenuItem(
+            name='NautilusPython::ShareItem',
+            label=self.strings.get('SHARE_MENU_TITLE', 'Share...'))
+        item.connect("activate", self.context_menu_action, 'SHARE', file)
+        menu.append_item(item)
+
+        # Add permalink menu options, but hide these options for older clients
+        # that don't have these actions.
+        if 'COPY_PRIVATE_LINK_TITLE' in self.strings:
+            item_copyprivatelink = Nautilus.MenuItem(
+                name='CopyPrivateLink', label=self.strings.get('COPY_PRIVATE_LINK_TITLE', 'Copy private link to clipboard'))
+            item_copyprivatelink.connect("activate", self.context_menu_action, 'COPY_PRIVATE_LINK', file)
+            menu.append_item(item_copyprivatelink)
+
+        if 'EMAIL_PRIVATE_LINK_TITLE' in self.strings:
+            item_emailprivatelink = Nautilus.MenuItem(
+                name='EmailPrivateLink', label=self.strings.get('EMAIL_PRIVATE_LINK_TITLE', 'Send private link by email...'))
+            item_emailprivatelink.connect("activate", self.context_menu_action, 'EMAIL_PRIVATE_LINK', file)
+            menu.append_item(item_emailprivatelink)
+
+        return [item_owncloud]
+
+
+    def context_menu_action(self, menu, action, file):
         filename = get_local_path(file.get_uri())
-        print("Share file " + filename)
-        socketConnect.sendCommand("SHARE:" + filename + "\n")
+        print("Context menu: " + action + ' ' + filename)
+        socketConnect.sendCommand(action + ":" + filename + "\n")
 
 
 class SyncStateExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.InfoProvider):
index e40c0303467fcbd04939f5aba2eb12d588b399a6..d123d58c5258e547b76a35c15ded2fd105d02a65 100644 (file)
@@ -94,6 +94,7 @@ set(client_SRCS
     notificationwidget.cpp
     notificationconfirmjob.cpp
     servernotificationhandler.cpp
+    guiutility.cpp
     creds/credentialsfactory.cpp
     creds/httpcredentialsgui.cpp
     creds/oauth.cpp
@@ -129,7 +130,6 @@ IF( APPLE )
     list(APPEND client_SRCS settingsdialogmac.cpp)
     list(APPEND client_SRCS socketapisocket_mac.mm)
     list(APPEND client_SRCS systray.mm)
-    list(APPEND client_SRCS clipboard.mm)
 
     if(SPARKLE_FOUND)
        # Define this, we need to check in updater.cpp
index 40bbfb1d7ebf4f82a4f540cb8ff915f52f87b64b..2cb11ae30ba3a8476b2c7de43a567defc708266e 100644 (file)
@@ -206,8 +206,8 @@ Application::Application(int &argc, char **argv)
         slotAccountStateAdded(ai.data());
     }
 
-    connect(FolderMan::instance()->socketApi(), SIGNAL(shareCommandReceived(QString, QString, bool)),
-        _gui, SLOT(slotShowShareDialog(QString, QString, bool)));
+    connect(FolderMan::instance()->socketApi(), SIGNAL(shareCommandReceived(QString, QString)),
+        _gui, SLOT(slotShowShareDialog(QString, QString)));
 
     // startup procedure.
     connect(&_checkConnectionTimer, SIGNAL(timeout()), this, SLOT(slotCheckConnection()));
diff --git a/src/gui/clipboard.mm b/src/gui/clipboard.mm
deleted file mode 100644 (file)
index 48ea813..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-#include <QString>
-#import <Cocoa/Cocoa.h>
-
-namespace OCC {
-
-// https://github.com/owncloud/client/issues/3300
-void copyToPasteboard(const QString &string)
-{
-    [[NSPasteboard generalPasteboard] clearContents];
-    [[NSPasteboard generalPasteboard] setString:[NSString stringWithUTF8String:string.toUtf8().data()]
-                                      forType:NSStringPboardType];
-}
-
-}
diff --git a/src/gui/guiutility.cpp b/src/gui/guiutility.cpp
new file mode 100644 (file)
index 0000000..27fe548
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) by Christian Kamm <mail@ckamm.de>
+ *
+ * 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.
+ */
+
+#include "guiutility.h"
+
+#include <QClipboard>
+#include <QApplication>
+#include <QDesktopServices>
+#include <QMessageBox>
+
+using namespace OCC;
+
+bool Utility::openBrowser(const QUrl &url, QWidget *errorWidgetParent)
+{
+    if (!QDesktopServices::openUrl(url) && errorWidgetParent) {
+        QMessageBox::warning(
+            errorWidgetParent,
+            QCoreApplication::translate("utility", "Could not open browser"),
+            QCoreApplication::translate("utility",
+                "There was an error when launching the browser to go to "
+                "URL %1. Maybe no default browser is configured?")
+                .arg(url.toString()));
+        return false;
+    }
+    return true;
+}
+
+bool Utility::openEmailComposer(const QString &subject, const QString &body, QWidget *errorWidgetParent)
+{
+    QUrl url(QLatin1String("mailto: "));
+    url.setQueryItems({ { QLatin1String("subject"), subject },
+        { QLatin1String("body"), body } });
+
+    if (!QDesktopServices::openUrl(url) && errorWidgetParent) {
+        QMessageBox::warning(
+            errorWidgetParent,
+            QCoreApplication::translate("utility", "Could not open email client"),
+            QCoreApplication::translate("utility",
+                "There was an error when launching the email client to "
+                "create a new message. Maybe no default email client is "
+                "configured?"));
+        return false;
+    }
+    return true;
+}
diff --git a/src/gui/guiutility.h b/src/gui/guiutility.h
new file mode 100644 (file)
index 0000000..55f808a
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) by Christian Kamm <mail@ckamm.de>
+ *
+ * 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.
+ */
+
+#ifndef GUIUTILITY_H
+#define GUIUTILITY_H
+
+#include <QString>
+#include <QUrl>
+#include <QWidget>
+
+namespace OCC {
+namespace Utility {
+
+    /** Open an url in the browser.
+     *
+     * If launching the browser fails, display a message.
+     */
+    bool openBrowser(const QUrl &url, QWidget *errorWidgetParent);
+
+    /** Start composing a new email message.
+     *
+     * If launching the email program fails, display a message.
+     */
+    bool openEmailComposer(const QString &subject, const QString &body,
+        QWidget *errorWidgetParent);
+
+} // namespace Utility
+} // namespace OCC
+
+#endif
index 684689dae7fef437bbe6eea461203e4fd552def8..db005f42273292e61cf66906c06f6af005964391 100644 (file)
@@ -33,6 +33,7 @@
 #include "accountstate.h"
 #include "openfilemanager.h"
 #include "accountmanager.h"
+#include "syncjournalfilerecord.h"
 #include "creds/abstractcredentials.h"
 
 #include <QDesktopServices>
@@ -1039,7 +1040,7 @@ void ownCloudGui::raiseDialog(QWidget *raiseWidget)
 }
 
 
-void ownCloudGui::slotShowShareDialog(const QString &sharePath, const QString &localPath, bool resharingAllowed)
+void ownCloudGui::slotShowShareDialog(const QString &sharePath, const QString &localPath)
 {
     const auto folder = FolderMan::instance()->folderForPath(localPath);
     if (!folder) {
@@ -1052,6 +1053,17 @@ void ownCloudGui::slotShowShareDialog(const QString &sharePath, const QString &l
 
     const auto accountState = folder->accountState();
 
+    const QString file = localPath.mid(folder->cleanPath().length() + 1);
+    SyncJournalFileRecord fileRecord = folder->journalDb()->getFileRecord(file);
+
+    bool resharingAllowed = true; // lets assume the good
+    if (fileRecord.isValid()) {
+        // check the permission: Is resharing allowed?
+        if (!fileRecord._remotePerm.contains('R')) {
+            resharingAllowed = false;
+        }
+    }
+
     // As a first approximation, set the set of permissions that can be granted
     // either to everything (resharing allowed) or nothing (no resharing).
     //
@@ -1072,7 +1084,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);
+        w = new ShareDialog(accountState, sharePath, localPath, maxSharingPermissions, fileRecord.numericFileId());
         w->setAttribute(Qt::WA_DeleteOnClose, true);
 
         _shareDialogs[localPath] = w;
index c0238a76176f4e27736e38400eab9969d42fea8e..c24fa173a87ce4bdb227a4a5215625fa817b6165 100644 (file)
@@ -86,7 +86,16 @@ public slots:
     void slotOpenPath(const QString &path);
     void slotAccountStateChanged();
     void slotTrayMessageIfServerUnsupported(Account *account);
-    void slotShowShareDialog(const QString &sharePath, const QString &localPath, bool resharingAllowed);
+
+    /**
+     * Open a share dialog for a file or folder.
+     *
+     * sharePath is the full remote path to the item,
+     * localPath is the absolute local path to it (so not relative
+     * to the folder).
+     */
+    void slotShowShareDialog(const QString &sharePath, const QString &localPath);
+
     void slotRemoveDestroyedShareDialogs();
 
 private slots:
index 17399d1a41c993f34a0977030a3bb813c1aa755b..24a912fdc2d8756c4d07ec92f90a9c93dd76e070 100644 (file)
@@ -38,6 +38,7 @@ ShareDialog::ShareDialog(QPointer<AccountState> accountState,
     const QString &sharePath,
     const QString &localPath,
     SharePermissions maxSharingPermissions,
+    const QByteArray &numericFileId,
     QWidget *parent)
     : QDialog(parent)
     , _ui(new Ui::ShareDialog)
@@ -45,6 +46,7 @@ ShareDialog::ShareDialog(QPointer<AccountState> accountState,
     , _sharePath(sharePath)
     , _localPath(localPath)
     , _maxSharingPermissions(maxSharingPermissions)
+    , _numericFileId(numericFileId)
     , _linkWidget(NULL)
     , _userGroupWidget(NULL)
     , _progressIndicator(NULL)
@@ -192,7 +194,7 @@ void ShareDialog::showSharingUi()
         && _accountState->account()->serverVersionInt() >= Account::makeServerVersion(8, 2, 0);
 
     if (userGroupSharing) {
-        _userGroupWidget = new ShareUserGroupWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, this);
+        _userGroupWidget = new ShareUserGroupWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, _numericFileId, this);
         _ui->shareWidgets->addTab(_userGroupWidget, tr("Users and Groups"));
         _userGroupWidget->getShares();
     }
index dac7b9841dd5eceffbb24b923516477fa967d9dc..50aeea2e1704e20bf7cf09b9b8a800fbb402787d 100644 (file)
@@ -43,6 +43,7 @@ public:
         const QString &sharePath,
         const QString &localPath,
         SharePermissions maxSharingPermissions,
+        const QByteArray &numericFileId,
         QWidget *parent = 0);
     ~ShareDialog();
 
@@ -60,8 +61,8 @@ private:
     QPointer<AccountState> _accountState;
     QString _sharePath;
     QString _localPath;
-
     SharePermissions _maxSharingPermissions;
+    QByteArray _numericFileId;
 
     ShareLinkWidget *_linkWidget;
     ShareUserGroupWidget *_userGroupWidget;
index b2f6d7c7d6809bfbd140a68cb95b5202b55c1006..f3b8e33da52ed6a5c63c5a30a0a63428cecb94d9 100644 (file)
@@ -19,6 +19,7 @@
 #include "capabilities.h"
 
 #include "sharemanager.h"
+#include "guiutility.h"
 
 #include "QProgressIndicator.h"
 #include <QBuffer>
@@ -494,51 +495,18 @@ void ShareLinkWidget::slotCheckBoxExpireClicked()
     }
 }
 
-#ifdef Q_OS_MAC
-extern void copyToPasteboard(const QString &string);
-#endif
-
-void ShareLinkWidget::copyShareLink(const QUrl &url)
-{
-#ifdef Q_OS_MAC
-    copyToPasteboard(url.toString());
-#else
-    QClipboard *clipboard = QApplication::clipboard();
-    clipboard->setText(url.toString());
-#endif
-}
-
 void ShareLinkWidget::emailShareLink(const QUrl &url)
 {
     QString fileName = _sharePath.mid(_sharePath.lastIndexOf('/') + 1);
-
-    if (!QDesktopServices::openUrl(QUrl(QString(
-                                            "mailto: "
-                                            "?subject=I shared %1 with you"
-                                            "&body=%2")
-                                            .arg(
-                                                fileName,
-                                                url.toString()),
-            QUrl::TolerantMode))) {
-        QMessageBox::warning(
-            this,
-            tr("Could not open email client"),
-            tr("There was an error when launching the email client to "
-               "create a new message. Maybe no default email client is "
-               "configured?"));
-    }
+    Utility::openEmailComposer(
+        QString("I shared %1 with you").arg(fileName),
+        url.toString(),
+        this);
 }
 
 void ShareLinkWidget::openShareLink(const QUrl &url)
 {
-    if (!QDesktopServices::openUrl(url)) {
-        QMessageBox::warning(
-            this,
-            tr("Could not open browser"),
-            tr("There was an error when launching the browser to "
-               "view the public link share. Maybe no default browser is "
-               "configured?"));
-    }
+    Utility::openBrowser(url, this);
 }
 
 void ShareLinkWidget::slotShareLinkButtonTriggered(QAction *action)
@@ -546,9 +514,9 @@ void ShareLinkWidget::slotShareLinkButtonTriggered(QAction *action)
     auto share = sender()->property(propertyShareC).value<QSharedPointer<LinkShare>>();
 
     if (action == _copyLinkAction) {
-        copyShareLink(share->getLink());
+        QApplication::clipboard()->setText(share->getLink().toString());
     } else if (action == _copyDirectLinkAction) {
-        copyShareLink(share->getDirectDownloadLink());
+        QApplication::clipboard()->setText(share->getDirectDownloadLink().toString());
     } else if (action == _emailLinkAction) {
         emailShareLink(share->getLink());
     } else if (action == _emailDirectLinkAction) {
index 1351f888f38d651b9cae2b727c24483ed06b42b1..8cecac2a42fdec51c6b744872b463df32183769f 100644 (file)
@@ -22,7 +22,7 @@
 #include "theme.h"
 #include "configfile.h"
 #include "capabilities.h"
-
+#include "guiutility.h"
 #include "thumbnailjob.h"
 #include "sharee.h"
 #include "sharemanager.h"
@@ -39,6 +39,8 @@
 #include <QPropertyAnimation>
 #include <QMenu>
 #include <QAction>
+#include <QDesktopServices>
+#include <QMessageBox>
 
 namespace OCC {
 
@@ -46,6 +48,7 @@ ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account,
     const QString &sharePath,
     const QString &localPath,
     SharePermissions maxSharingPermissions,
+    const QByteArray &numericFileId,
     QWidget *parent)
     : QWidget(parent)
     , _ui(new Ui::ShareUserGroupWidget)
@@ -53,6 +56,7 @@ ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account,
     , _sharePath(sharePath)
     , _localPath(localPath)
     , _maxSharingPermissions(maxSharingPermissions)
+    , _numericFileId(numericFileId)
     , _disableCompleterActivated(false)
 {
     setAttribute(Qt::WA_DeleteOnClose);
@@ -80,6 +84,7 @@ ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account,
     connect(_manager, SIGNAL(shareCreated(QSharedPointer<Share>)), SLOT(getShares()));
     connect(_manager, SIGNAL(serverError(int, QString)), this, SLOT(displayError(int, QString)));
     connect(_ui->shareeLineEdit, SIGNAL(returnPressed()), SLOT(slotLineEditReturn()));
+    connect(_ui->privateLinkText, SIGNAL(linkActivated(QString)), SLOT(slotPrivateLinkShare()));
 
     // By making the next two QueuedConnections we can override
     // the strings the completer sets on the line edit.
@@ -222,6 +227,21 @@ void ShareUserGroupWidget::slotAdjustScrollWidgetSize()
     }
 }
 
+void ShareUserGroupWidget::slotPrivateLinkShare()
+{
+    auto menu = new QMenu(this);
+    menu->setAttribute(Qt::WA_DeleteOnClose);
+
+    menu->addAction(tr("Open link in browser"),
+        this, SLOT(slotPrivateLinkOpenBrowser()));
+    menu->addAction(tr("Copy link to clipboard"),
+        this, SLOT(slotPrivateLinkCopy()));
+    menu->addAction(tr("Send link by email"),
+        this, SLOT(slotPrivateLinkEmail()));
+
+    menu->exec(QCursor::pos());
+}
+
 void ShareUserGroupWidget::slotShareesReady()
 {
     _pi_sharee.stopAnimation();
@@ -301,6 +321,24 @@ void ShareUserGroupWidget::displayError(int code, const QString &message)
     _ui->shareeLineEdit->setEnabled(true);
 }
 
+void ShareUserGroupWidget::slotPrivateLinkOpenBrowser()
+{
+    Utility::openBrowser(_account->filePermalinkUrl(_numericFileId), this);
+}
+
+void ShareUserGroupWidget::slotPrivateLinkCopy()
+{
+    QApplication::clipboard()->setText(_account->filePermalinkUrl(_numericFileId).toString());
+}
+
+void ShareUserGroupWidget::slotPrivateLinkEmail()
+{
+    Utility::openEmailComposer(
+        tr("I shared something with you"),
+        _account->filePermalinkUrl(_numericFileId).toString(),
+        this);
+}
+
 ShareUserLine::ShareUserLine(QSharedPointer<Share> share,
     SharePermissions maxSharingPermissions,
     bool isFile,
index 36639196da821ef59cdf5b9fff16c08ec4de58e9..fbc2bdfa4d1f00589242de9bbd7fab70b85408d8 100644 (file)
@@ -57,6 +57,7 @@ public:
         const QString &sharePath,
         const QString &localPath,
         SharePermissions maxSharingPermissions,
+        const QByteArray &numericFileId,
         QWidget *parent = 0);
     ~ShareUserGroupWidget();
 
@@ -75,19 +76,25 @@ private slots:
     void slotCompleterHighlighted(const QModelIndex &index);
     void slotShareesReady();
     void slotAdjustScrollWidgetSize();
+    void slotPrivateLinkShare();
     void displayError(int code, const QString &message);
 
+    void slotPrivateLinkOpenBrowser();
+    void slotPrivateLinkCopy();
+    void slotPrivateLinkEmail();
+
 private:
     Ui::ShareUserGroupWidget *_ui;
     AccountPtr _account;
     QString _sharePath;
     QString _localPath;
+    SharePermissions _maxSharingPermissions;
+    QByteArray _numericFileId;
 
     QCompleter *_completer;
     ShareeModel *_completerModel;
     QTimer _completionTimer;
 
-    SharePermissions _maxSharingPermissions;
     bool _isFile;
     bool _disableCompleterActivated; // in order to avoid that we share the contents twice
     ShareManager *_manager;
index 615c5b2f199ea3d271da4e87f824d05f3c3f431a..028d897ce241103b8906b5d4eae14025d9499055 100644 (file)
        <rect>
         <x>0</x>
         <y>0</y>
-        <width>395</width>
-        <height>221</height>
+        <width>377</width>
+        <height>169</height>
        </rect>
       </property>
       <layout class="QVBoxLayout" name="verticalLayout_3"/>
      </widget>
     </widget>
    </item>
+   <item>
+    <widget class="QLabel" name="privateLinkText">
+     <property name="text">
+      <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can direct people to this shared file or folder &lt;a href=&quot;private link menu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;by giving them a private link&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
   </layout>
  </widget>
  <layoutdefault spacing="6" margin="11"/>
index 82705c59ae5ee2e19642f794528fba9258ce5496..bf4e75d3b067fd6ae6e26a1d0ef4449d35005512 100644 (file)
@@ -32,7 +32,9 @@
 #include "account.h"
 #include "capabilities.h"
 #include "asserts.h"
+#include "guiutility.h"
 
+#include <array>
 #include <QBitArray>
 #include <QUrl>
 #include <QMetaMethod>
@@ -45,6 +47,8 @@
 #include <QLocalSocket>
 #include <QStringBuilder>
 
+#include <QClipboard>
+
 #include <sqlite3.h>
 
 
@@ -436,19 +440,10 @@ void SocketApi::command_SHARE(const QString &localFile, SocketListener *listener
             return;
         }
 
-        SyncJournalFileRecord rec = shareFolder->journalDb()->getFileRecord(localFileClean);
-
-        bool allowReshare = true; // lets assume the good
-        if (rec.isValid()) {
-            // check the permission: Is resharing allowed?
-            if (!rec._remotePerm.contains('R')) {
-                allowReshare = false;
-            }
-        }
         const QString message = QLatin1String("SHARE:OK:") + QDir::toNativeSeparators(localFile);
         listener->sendMessage(message);
 
-        emit shareCommandReceived(remotePath, localFileClean, allowReshare);
+        emit shareCommandReceived(remotePath, localFileClean);
     }
 }
 
@@ -514,6 +509,39 @@ void SocketApi::command_SHARE_MENU_TITLE(const QString &, SocketListener *listen
     listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + tr("Share with %1", "parameter is ownCloud").arg(Theme::instance()->appNameGUI()));
 }
 
+void SocketApi::command_COPY_PRIVATE_LINK(const QString &localFile, SocketListener *)
+{
+    auto url = getPrivateLinkUrl(localFile);
+    if (!url.isEmpty()) {
+        QApplication::clipboard()->setText(url.toString());
+    }
+}
+
+void SocketApi::command_EMAIL_PRIVATE_LINK(const QString &localFile, SocketListener *)
+{
+    auto url = getPrivateLinkUrl(localFile);
+    if (!url.isEmpty()) {
+        Utility::openEmailComposer(
+            tr("I shared something with you"),
+            url.toString(),
+            0);
+    }
+}
+
+void SocketApi::command_GET_STRINGS(const QString &, SocketListener *listener)
+{
+    static std::array<std::pair<const char *, QString>, 5> strings { {
+        { "SHARE_MENU_TITLE", tr("Share with %1...", "parameter is ownCloud").arg(Theme::instance()->appNameGUI()) },
+        { "APPNAME", Theme::instance()->appNameGUI() },
+        { "CONTEXT_MENU_TITLE", Theme::instance()->appNameGUI() },
+        { "COPY_PRIVATE_LINK_TITLE", tr("Copy private link to clipboard") },
+        { "EMAIL_PRIVATE_LINK_TITLE", tr("Send private link by email...") },
+    } };
+    for (auto key_value : strings) {
+        listener->sendMessage(QString("STRING:%1:%2").arg(key_value.first, key_value.second));
+    }
+}
+
 QString SocketApi::buildRegisterPathMessage(const QString &path)
 {
     QFileInfo fi(path);
@@ -522,4 +550,22 @@ QString SocketApi::buildRegisterPathMessage(const QString &path)
     return message;
 }
 
+QUrl SocketApi::getPrivateLinkUrl(const QString &localFile) const
+{
+    Folder *shareFolder = FolderMan::instance()->folderForPath(localFile);
+    if (!shareFolder) {
+        qCWarning(lcSocketApi) << "Unknown path" << localFile;
+        return QUrl();
+    }
+
+    const QString localFileClean = QDir::cleanPath(localFile);
+    const QString file = localFileClean.mid(shareFolder->cleanPath().length() + 1);
+
+    SyncJournalFileRecord rec = shareFolder->journalDb()->getFileRecord(file);
+    if (rec.isValid()) {
+        return shareFolder->accountState()->account()->filePermalinkUrl(rec.numericFileId());
+    }
+    return QUrl();
+}
+
 } // namespace OCC
index e0b4c3ada3dc9d81096c5393dcf160e5d17ba97f..d4f6e7bf86b036839b612b6aceb94599e24fb1c4 100644 (file)
@@ -55,8 +55,7 @@ public slots:
     void slotRegisterPath(const QString &alias);
 
 signals:
-    void shareCommandReceived(const QString &sharePath, const QString &localPath, bool resharingAllowed);
-    void shareUserGroupCommandReceived(const QString &sharePath, const QString &localPath, bool resharingAllowed);
+    void shareCommandReceived(const QString &sharePath, const QString &localPath);
 
 private slots:
     void slotNewConnection();
@@ -70,13 +69,22 @@ private:
 
     Q_INVOKABLE void command_RETRIEVE_FOLDER_STATUS(const QString &argument, SocketListener *listener);
     Q_INVOKABLE void command_RETRIEVE_FILE_STATUS(const QString &argument, SocketListener *listener);
-    Q_INVOKABLE void command_SHARE(const QString &localFile, SocketListener *listener);
 
     Q_INVOKABLE void command_VERSION(const QString &argument, SocketListener *listener);
 
     Q_INVOKABLE void command_SHARE_STATUS(const QString &localFile, SocketListener *listener);
     Q_INVOKABLE void command_SHARE_MENU_TITLE(const QString &argument, SocketListener *listener);
+
+    // The context menu actions
+    Q_INVOKABLE void command_SHARE(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);
+
+    /** Sends translated/branded strings that may be useful to the integration */
+    Q_INVOKABLE void command_GET_STRINGS(const QString &argument, SocketListener *listener);
+
     QString buildRegisterPathMessage(const QString &path);
+    QUrl getPrivateLinkUrl(const QString &localFile) const;
 
     QSet<QString> _registeredAliases;
     QList<SocketListener> _listeners;
index 19ace5ed0a6ce3050793cd2bfdda26222371d2a2..ee83e3deecb98d14303aff347d45fc491fc0542d 100644 (file)
@@ -160,6 +160,12 @@ QUrl Account::davUrl() const
     return Utility::concatUrlPath(url(), davPath());
 }
 
+QUrl Account::filePermalinkUrl(const QByteArray &numericFileId) const
+{
+    return Utility::concatUrlPath(url(),
+        QLatin1String("/index.php/f/") + QUrl::toPercentEncoding(QString::fromLatin1(numericFileId)));
+}
+
 /**
  * clear all cookies. (Session cookies or not)
  */
index 66e0d1be6114546643c3bdcbcae643c477cfbf3c..d48b27b15c3620cda52bc64b8702db59d7363bf5 100644 (file)
@@ -107,6 +107,9 @@ public:
     /** Returns webdav entry URL, based on url() */
     QUrl davUrl() const;
 
+    /** Returns a permalink url for a file */
+    QUrl filePermalinkUrl(const QByteArray &numericFileId) const;
+
     /** Holds the accounts credentials */
     AbstractCredentials *credentials() const;
     void setCredentials(AbstractCredentials *cred);
index 5cf17b26acb1f2bfd089721530f3c7ee8202bd93..65d7ef86ad43d824b3b55968e25442082a900a9b 100644 (file)
@@ -42,6 +42,11 @@ Q_LOGGING_CATEGORY(lcPropagateLocalRemove, "sync.propagator.localremove", QtInfo
 Q_LOGGING_CATEGORY(lcPropagateLocalMkdir, "sync.propagator.localmkdir", QtInfoMsg)
 Q_LOGGING_CATEGORY(lcPropagateLocalRename, "sync.propagator.localrename", QtInfoMsg)
 
+QByteArray localFileIdFromFullId(const QByteArray &id)
+{
+    return id.left(8);
+}
+
 /**
  * Code inspired from Qt5's QDir::removeRecursively
  * The code will update the database in case of error.
index b2c65931d40d63faad457e7de270dde4bf4736e1..96bde3eb6d7aa30a3f017f58b686223cbc971aac 100644 (file)
@@ -109,6 +109,17 @@ SyncFileItem SyncJournalFileRecord::toSyncFileItem()
     return item;
 }
 
+QByteArray SyncJournalFileRecord::numericFileId() const
+{
+    // Use the id up until the first non-numeric character
+    for (int i = 0; i < _fileId.size(); ++i) {
+        if (_fileId[i] < '0' || _fileId[i] > '9') {
+            return _fileId.left(i);
+        }
+    }
+    return _fileId;
+}
+
 bool SyncJournalErrorBlacklistRecord::isValid() const
 {
     return !_file.isEmpty()
index c7579683b978ccbd56a7221e408a3f5368074c0f..8f333318038b181bf3329a0939763f092481e048 100644 (file)
@@ -48,6 +48,14 @@ public:
         return !_path.isEmpty();
     }
 
+    /** Returns the numeric part of the full id in _fileId.
+     *
+     * On the server this is sometimes known as the internal file id.
+     *
+     * It is used in the construction of private links.
+     */
+    QByteArray numericFileId() const;
+
     QString _path;
     quint64 _inode;
     QDateTime _modtime;
index ac0f1dca1eaa4dbac258285031a2a234280f3a4c..860f140e9b1b9a460f847551744599b72e45cf51 100644 (file)
@@ -55,6 +55,7 @@ list(APPEND FolderMan_SRC ../src/gui/socketapi.cpp )
 list(APPEND FolderMan_SRC ../src/gui/accountstate.cpp )
 list(APPEND FolderMan_SRC ../src/gui/syncrunfilelog.cpp )
 list(APPEND FolderMan_SRC ../src/gui/lockwatcher.cpp )
+list(APPEND FolderMan_SRC ../src/gui/guiutility.cpp )
 list(APPEND FolderMan_SRC ${FolderWatcher_SRC})
 list(APPEND FolderMan_SRC stub.cpp )
 owncloud_add_test(FolderMan "${FolderMan_SRC}")