diff options
25 files changed, 390 insertions, 107 deletions
diff --git a/shell_integration/dolphin/ownclouddolphinactionplugin.cpp b/shell_integration/dolphin/ownclouddolphinactionplugin.cpp index dfb2bdff7..c47cb783d 100644 --- a/shell_integration/dolphin/ownclouddolphinactionplugin.cpp +++ b/shell_integration/dolphin/ownclouddolphinactionplugin.cpp @@ -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 }; } }; diff --git a/shell_integration/dolphin/ownclouddolphinpluginhelper.cpp b/shell_integration/dolphin/ownclouddolphinpluginhelper.cpp index 68c2a9c29..ae1705220 100644 --- a/shell_integration/dolphin/ownclouddolphinpluginhelper.cpp +++ b/shell_integration/dolphin/ownclouddolphinpluginhelper.cpp @@ -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); diff --git a/shell_integration/dolphin/ownclouddolphinpluginhelper.h b/shell_integration/dolphin/ownclouddolphinpluginhelper.h index 26762caaf..7d4e7ba14 100644 --- a/shell_integration/dolphin/ownclouddolphinpluginhelper.h +++ b/shell_integration/dolphin/ownclouddolphinpluginhelper.h @@ -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; }; diff --git a/shell_integration/nautilus/syncstate.py b/shell_integration/nautilus/syncstate.py index 7fb35b80c..9a0f35ed7 100644 --- a/shell_integration/nautilus/syncstate.py +++ b/shell_integration/nautilus/syncstate.py @@ -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): diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index e40c03034..d123d58c5 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -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 diff --git a/src/gui/application.cpp b/src/gui/application.cpp index 40bbfb1d7..2cb11ae30 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -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 index 48ea81387..000000000 --- a/src/gui/clipboard.mm +++ /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 index 000000000..27fe54897 --- /dev/null +++ b/src/gui/guiutility.cpp @@ -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 index 000000000..55f808ac2 --- /dev/null +++ b/src/gui/guiutility.h @@ -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 diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 684689dae..db005f422 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -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; diff --git a/src/gui/owncloudgui.h b/src/gui/owncloudgui.h index c0238a761..c24fa173a 100644 --- a/src/gui/owncloudgui.h +++ b/src/gui/owncloudgui.h @@ -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: diff --git a/src/gui/sharedialog.cpp b/src/gui/sharedialog.cpp index 17399d1a4..24a912fdc 100644 --- a/src/gui/sharedialog.cpp +++ b/src/gui/sharedialog.cpp @@ -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(); } diff --git a/src/gui/sharedialog.h b/src/gui/sharedialog.h index dac7b9841..50aeea2e1 100644 --- a/src/gui/sharedialog.h +++ b/src/gui/sharedialog.h @@ -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; diff --git a/src/gui/sharelinkwidget.cpp b/src/gui/sharelinkwidget.cpp index b2f6d7c7d..f3b8e33da 100644 --- a/src/gui/sharelinkwidget.cpp +++ b/src/gui/sharelinkwidget.cpp @@ -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) { diff --git a/src/gui/shareusergroupwidget.cpp b/src/gui/shareusergroupwidget.cpp index 1351f888f..8cecac2a4 100644 --- a/src/gui/shareusergroupwidget.cpp +++ b/src/gui/shareusergroupwidget.cpp @@ -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, diff --git a/src/gui/shareusergroupwidget.h b/src/gui/shareusergroupwidget.h index 36639196d..fbc2bdfa4 100644 --- a/src/gui/shareusergroupwidget.h +++ b/src/gui/shareusergroupwidget.h @@ -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; diff --git a/src/gui/shareusergroupwidget.ui b/src/gui/shareusergroupwidget.ui index 615c5b2f1..028d897ce 100644 --- a/src/gui/shareusergroupwidget.ui +++ b/src/gui/shareusergroupwidget.ui @@ -94,14 +94,24 @@ <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><html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline; color:#0000ff;">by giving them a private link</span></a>.</p></body></html></string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> </layout> </widget> <layoutdefault spacing="6" margin="11"/> diff --git a/src/gui/socketapi.cpp b/src/gui/socketapi.cpp index 82705c59a..bf4e75d3b 100644 --- a/src/gui/socketapi.cpp +++ b/src/gui/socketapi.cpp @@ -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 diff --git a/src/gui/socketapi.h b/src/gui/socketapi.h index e0b4c3ada..d4f6e7bf8 100644 --- a/src/gui/socketapi.h +++ b/src/gui/socketapi.h @@ -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; diff --git a/src/libsync/account.cpp b/src/libsync/account.cpp index 19ace5ed0..ee83e3dee 100644 --- a/src/libsync/account.cpp +++ b/src/libsync/account.cpp @@ -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) */ diff --git a/src/libsync/account.h b/src/libsync/account.h index 66e0d1be6..d48b27b15 100644 --- a/src/libsync/account.h +++ b/src/libsync/account.h @@ -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); diff --git a/src/libsync/propagatorjobs.cpp b/src/libsync/propagatorjobs.cpp index 5cf17b26a..65d7ef86a 100644 --- a/src/libsync/propagatorjobs.cpp +++ b/src/libsync/propagatorjobs.cpp @@ -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. diff --git a/src/libsync/syncjournalfilerecord.cpp b/src/libsync/syncjournalfilerecord.cpp index b2c65931d..96bde3eb6 100644 --- a/src/libsync/syncjournalfilerecord.cpp +++ b/src/libsync/syncjournalfilerecord.cpp @@ -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() diff --git a/src/libsync/syncjournalfilerecord.h b/src/libsync/syncjournalfilerecord.h index c7579683b..8f3333180 100644 --- a/src/libsync/syncjournalfilerecord.h +++ b/src/libsync/syncjournalfilerecord.h @@ -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; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ac0f1dca1..860f140e9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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}") |