Introduce private link sharing #5023

* 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.
This commit is contained in:
Christian Kamm 2017-05-10 09:37:10 +02:00
parent d01065b9a1
commit 0238a29c7c
25 changed files with 386 additions and 103 deletions

View file

@ -24,6 +24,7 @@
#include <KIOCore/kfileitem.h> #include <KIOCore/kfileitem.h>
#include <KIOCore/KFileItemListProperties> #include <KIOCore/KFileItemListProperties>
#include <QtWidgets/QAction> #include <QtWidgets/QAction>
#include <QtWidgets/QMenu>
#include <QtCore/QDir> #include <QtCore/QDir>
#include <QtCore/QTimer> #include <QtCore/QTimer>
#include "ownclouddolphinpluginhelper.h" #include "ownclouddolphinpluginhelper.h"
@ -53,12 +54,31 @@ public:
} )) } ))
return {}; return {};
auto act = new QAction(parentWidget); auto menuaction = new QAction(parentWidget);
act->setText(helper->shareActionString()); menuaction->setText(helper->contextMenuTitle());
connect(act, &QAction::triggered, this, [localFile, helper] { 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")); 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 };
} }
}; };

View file

@ -59,7 +59,7 @@ void OwncloudDolphinPluginHelper::sendCommand(const char* data)
void OwncloudDolphinPluginHelper::slotConnected() void OwncloudDolphinPluginHelper::slotConnected()
{ {
sendCommand("SHARE_MENU_TITLE:\n"); sendCommand("GET_STRINGS:\n");
} }
void OwncloudDolphinPluginHelper::tryConnect() void OwncloudDolphinPluginHelper::tryConnect()
@ -92,9 +92,11 @@ void OwncloudDolphinPluginHelper::slotReadyRead()
QString file = QString::fromUtf8(line.constData() + col + 1, line.size() - col - 1); QString file = QString::fromUtf8(line.constData() + col + 1, line.size() - col - 1);
_paths.append(file); _paths.append(file);
continue; continue;
} else if (line.startsWith("SHARE_MENU_TITLE:")) { } else if (line.startsWith("STRING:")) {
auto col = line.indexOf(':'); auto args = QString::fromUtf8(line).split(QLatin1Char(':'));
_shareActionString = QString::fromUtf8(line.constData() + col + 1, line.size() - col - 1); if (args.size() >= 3) {
_strings[args[1]] = args.mid(2).join(QLatin1Char(':'));
}
continue; continue;
} }
emit commandRecieved(line); emit commandRecieved(line);

View file

@ -28,11 +28,22 @@ class OWNCLOUDDOLPHINPLUGINHELPER_EXPORT OwncloudDolphinPluginHelper : public QO
public: public:
static OwncloudDolphinPluginHelper *instance(); static OwncloudDolphinPluginHelper *instance();
QString shareActionString() const { return _shareActionString; }
bool isConnected() const; bool isConnected() const;
void sendCommand(const char *data); void sendCommand(const char *data);
QVector<QString> paths() const { return _paths; } 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: signals:
void commandRecieved(const QByteArray &cmd); void commandRecieved(const QByteArray &cmd);
@ -47,6 +58,7 @@ private:
QLocalSocket _socket; QLocalSocket _socket;
QByteArray _line; QByteArray _line;
QVector<QString> _paths; QVector<QString> _paths;
QString _shareActionString;
QBasicTimer _connectTimer; QBasicTimer _connectTimer;
QMap<QString, QString> _strings;
}; };

View file

@ -95,6 +95,9 @@ class SocketConnect(GObject.GObject):
print("Setting connected to %r." % self.connected ) print("Setting connected to %r." % self.connected )
self._watch_id = GObject.io_add_watch(self._sock, GObject.IO_IN, self._handle_notify) self._watch_id = GObject.io_add_watch(self._sock, GObject.IO_IN, self._handle_notify)
print("Socket watch id: " + str(self._watch_id)) print("Socket watch id: " + str(self._watch_id))
self.sendCommand('GET_STRINGS:\n')
return False # Don't run again return False # Don't run again
except Exception as e: except Exception as e:
print("Could not connect to unix socket. " + str(e)) print("Could not connect to unix socket. " + str(e))
@ -153,6 +156,13 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider):
def __init__(self): def __init__(self):
GObject.GObject.__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): def check_registered_paths(self, filename):
topLevelFolder = False topLevelFolder = False
internalFile = False internalFile = False
@ -178,7 +188,6 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider):
if len(files) != 1: if len(files) != 1:
return return
file = files[0] file = files[0]
items = []
filename = get_local_path(file.get_uri()) filename = get_local_path(file.get_uri())
# Check if its a folder (ends with an /), if yes add a "/" # 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 # Check if toplevel folder, we need to ignore those as they cannot be shared
topLevelFolder, internalFile = self.check_registered_paths(filename) topLevelFolder, internalFile = self.check_registered_paths(filename)
if topLevelFolder or not internalFile: if topLevelFolder or not internalFile:
return items return []
entry = socketConnect.nautilusVFSFile_table.get(filename) entry = socketConnect.nautilusVFSFile_table.get(filename)
if not entry: 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 shareable = False
state = entry['state'] state = entry['state']
state_ok = state.startswith('OK') state_ok = state.startswith('OK')
@ -212,22 +223,42 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider):
break break
if not shareable: if not shareable:
return items return []
# Create a menu item # Set up the 'ownCloud...' submenu
labelStr = "Share with " + appname + "..." item_owncloud = Nautilus.MenuItem(
item = Nautilus.MenuItem(name='NautilusPython::ShareItem', label=labelStr, name='IntegrationMenu', label=self.strings.get('CONTEXT_MENU_TITLE', appname))
tip='Share file {} through {}'.format(file.get_name(), appname) ) menu = Nautilus.Menu()
item.connect("activate", self.menu_share, file) item_owncloud.set_submenu(menu)
items.append(item)
return items # 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 menu_share(self, menu, file): def context_menu_action(self, menu, action, file):
filename = get_local_path(file.get_uri()) filename = get_local_path(file.get_uri())
print("Share file " + filename) print("Context menu: " + action + ' ' + filename)
socketConnect.sendCommand("SHARE:" + filename + "\n") socketConnect.sendCommand(action + ":" + filename + "\n")
class SyncStateExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.InfoProvider): class SyncStateExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.InfoProvider):

View file

@ -94,6 +94,7 @@ set(client_SRCS
notificationwidget.cpp notificationwidget.cpp
notificationconfirmjob.cpp notificationconfirmjob.cpp
servernotificationhandler.cpp servernotificationhandler.cpp
guiutility.cpp
creds/credentialsfactory.cpp creds/credentialsfactory.cpp
creds/httpcredentialsgui.cpp creds/httpcredentialsgui.cpp
creds/oauth.cpp creds/oauth.cpp
@ -129,7 +130,6 @@ IF( APPLE )
list(APPEND client_SRCS settingsdialogmac.cpp) list(APPEND client_SRCS settingsdialogmac.cpp)
list(APPEND client_SRCS socketapisocket_mac.mm) list(APPEND client_SRCS socketapisocket_mac.mm)
list(APPEND client_SRCS systray.mm) list(APPEND client_SRCS systray.mm)
list(APPEND client_SRCS clipboard.mm)
if(SPARKLE_FOUND) if(SPARKLE_FOUND)
# Define this, we need to check in updater.cpp # Define this, we need to check in updater.cpp

View file

@ -206,8 +206,8 @@ Application::Application(int &argc, char **argv)
slotAccountStateAdded(ai.data()); slotAccountStateAdded(ai.data());
} }
connect(FolderMan::instance()->socketApi(), SIGNAL(shareCommandReceived(QString, QString, bool)), connect(FolderMan::instance()->socketApi(), SIGNAL(shareCommandReceived(QString, QString)),
_gui, SLOT(slotShowShareDialog(QString, QString, bool))); _gui, SLOT(slotShowShareDialog(QString, QString)));
// startup procedure. // startup procedure.
connect(&_checkConnectionTimer, SIGNAL(timeout()), this, SLOT(slotCheckConnection())); connect(&_checkConnectionTimer, SIGNAL(timeout()), this, SLOT(slotCheckConnection()));

View file

@ -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];
}
}

56
src/gui/guiutility.cpp Normal file
View file

@ -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;
}

41
src/gui/guiutility.h Normal file
View file

@ -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

View file

@ -33,6 +33,7 @@
#include "accountstate.h" #include "accountstate.h"
#include "openfilemanager.h" #include "openfilemanager.h"
#include "accountmanager.h" #include "accountmanager.h"
#include "syncjournalfilerecord.h"
#include "creds/abstractcredentials.h" #include "creds/abstractcredentials.h"
#include <QDesktopServices> #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); const auto folder = FolderMan::instance()->folderForPath(localPath);
if (!folder) { if (!folder) {
@ -1052,6 +1053,17 @@ void ownCloudGui::slotShowShareDialog(const QString &sharePath, const QString &l
const auto accountState = folder->accountState(); 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 // As a first approximation, set the set of permissions that can be granted
// either to everything (resharing allowed) or nothing (no resharing). // 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]; w = _shareDialogs[localPath];
} else { } else {
qCInfo(lcApplication) << "Opening share dialog" << sharePath << localPath << maxSharingPermissions; 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); w->setAttribute(Qt::WA_DeleteOnClose, true);
_shareDialogs[localPath] = w; _shareDialogs[localPath] = w;

View file

@ -86,7 +86,16 @@ public slots:
void slotOpenPath(const QString &path); void slotOpenPath(const QString &path);
void slotAccountStateChanged(); void slotAccountStateChanged();
void slotTrayMessageIfServerUnsupported(Account *account); 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(); void slotRemoveDestroyedShareDialogs();
private slots: private slots:

View file

@ -38,6 +38,7 @@ ShareDialog::ShareDialog(QPointer<AccountState> accountState,
const QString &sharePath, const QString &sharePath,
const QString &localPath, const QString &localPath,
SharePermissions maxSharingPermissions, SharePermissions maxSharingPermissions,
const QByteArray &numericFileId,
QWidget *parent) QWidget *parent)
: QDialog(parent) : QDialog(parent)
, _ui(new Ui::ShareDialog) , _ui(new Ui::ShareDialog)
@ -45,6 +46,7 @@ ShareDialog::ShareDialog(QPointer<AccountState> accountState,
, _sharePath(sharePath) , _sharePath(sharePath)
, _localPath(localPath) , _localPath(localPath)
, _maxSharingPermissions(maxSharingPermissions) , _maxSharingPermissions(maxSharingPermissions)
, _numericFileId(numericFileId)
, _linkWidget(NULL) , _linkWidget(NULL)
, _userGroupWidget(NULL) , _userGroupWidget(NULL)
, _progressIndicator(NULL) , _progressIndicator(NULL)
@ -192,7 +194,7 @@ void ShareDialog::showSharingUi()
&& _accountState->account()->serverVersionInt() >= Account::makeServerVersion(8, 2, 0); && _accountState->account()->serverVersionInt() >= Account::makeServerVersion(8, 2, 0);
if (userGroupSharing) { 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")); _ui->shareWidgets->addTab(_userGroupWidget, tr("Users and Groups"));
_userGroupWidget->getShares(); _userGroupWidget->getShares();
} }

View file

@ -43,6 +43,7 @@ public:
const QString &sharePath, const QString &sharePath,
const QString &localPath, const QString &localPath,
SharePermissions maxSharingPermissions, SharePermissions maxSharingPermissions,
const QByteArray &numericFileId,
QWidget *parent = 0); QWidget *parent = 0);
~ShareDialog(); ~ShareDialog();
@ -60,8 +61,8 @@ private:
QPointer<AccountState> _accountState; QPointer<AccountState> _accountState;
QString _sharePath; QString _sharePath;
QString _localPath; QString _localPath;
SharePermissions _maxSharingPermissions; SharePermissions _maxSharingPermissions;
QByteArray _numericFileId;
ShareLinkWidget *_linkWidget; ShareLinkWidget *_linkWidget;
ShareUserGroupWidget *_userGroupWidget; ShareUserGroupWidget *_userGroupWidget;

View file

@ -19,6 +19,7 @@
#include "capabilities.h" #include "capabilities.h"
#include "sharemanager.h" #include "sharemanager.h"
#include "guiutility.h"
#include "QProgressIndicator.h" #include "QProgressIndicator.h"
#include <QBuffer> #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) void ShareLinkWidget::emailShareLink(const QUrl &url)
{ {
QString fileName = _sharePath.mid(_sharePath.lastIndexOf('/') + 1); QString fileName = _sharePath.mid(_sharePath.lastIndexOf('/') + 1);
Utility::openEmailComposer(
if (!QDesktopServices::openUrl(QUrl(QString( QString("I shared %1 with you").arg(fileName),
"mailto: " url.toString(),
"?subject=I shared %1 with you" this);
"&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?"));
}
} }
void ShareLinkWidget::openShareLink(const QUrl &url) void ShareLinkWidget::openShareLink(const QUrl &url)
{ {
if (!QDesktopServices::openUrl(url)) { Utility::openBrowser(url, this);
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?"));
}
} }
void ShareLinkWidget::slotShareLinkButtonTriggered(QAction *action) void ShareLinkWidget::slotShareLinkButtonTriggered(QAction *action)
@ -546,9 +514,9 @@ void ShareLinkWidget::slotShareLinkButtonTriggered(QAction *action)
auto share = sender()->property(propertyShareC).value<QSharedPointer<LinkShare>>(); auto share = sender()->property(propertyShareC).value<QSharedPointer<LinkShare>>();
if (action == _copyLinkAction) { if (action == _copyLinkAction) {
copyShareLink(share->getLink()); QApplication::clipboard()->setText(share->getLink().toString());
} else if (action == _copyDirectLinkAction) { } else if (action == _copyDirectLinkAction) {
copyShareLink(share->getDirectDownloadLink()); QApplication::clipboard()->setText(share->getDirectDownloadLink().toString());
} else if (action == _emailLinkAction) { } else if (action == _emailLinkAction) {
emailShareLink(share->getLink()); emailShareLink(share->getLink());
} else if (action == _emailDirectLinkAction) { } else if (action == _emailDirectLinkAction) {

View file

@ -22,7 +22,7 @@
#include "theme.h" #include "theme.h"
#include "configfile.h" #include "configfile.h"
#include "capabilities.h" #include "capabilities.h"
#include "guiutility.h"
#include "thumbnailjob.h" #include "thumbnailjob.h"
#include "sharee.h" #include "sharee.h"
#include "sharemanager.h" #include "sharemanager.h"
@ -39,6 +39,8 @@
#include <QPropertyAnimation> #include <QPropertyAnimation>
#include <QMenu> #include <QMenu>
#include <QAction> #include <QAction>
#include <QDesktopServices>
#include <QMessageBox>
namespace OCC { namespace OCC {
@ -46,6 +48,7 @@ ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account,
const QString &sharePath, const QString &sharePath,
const QString &localPath, const QString &localPath,
SharePermissions maxSharingPermissions, SharePermissions maxSharingPermissions,
const QByteArray &numericFileId,
QWidget *parent) QWidget *parent)
: QWidget(parent) : QWidget(parent)
, _ui(new Ui::ShareUserGroupWidget) , _ui(new Ui::ShareUserGroupWidget)
@ -53,6 +56,7 @@ ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account,
, _sharePath(sharePath) , _sharePath(sharePath)
, _localPath(localPath) , _localPath(localPath)
, _maxSharingPermissions(maxSharingPermissions) , _maxSharingPermissions(maxSharingPermissions)
, _numericFileId(numericFileId)
, _disableCompleterActivated(false) , _disableCompleterActivated(false)
{ {
setAttribute(Qt::WA_DeleteOnClose); setAttribute(Qt::WA_DeleteOnClose);
@ -80,6 +84,7 @@ ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account,
connect(_manager, SIGNAL(shareCreated(QSharedPointer<Share>)), SLOT(getShares())); connect(_manager, SIGNAL(shareCreated(QSharedPointer<Share>)), SLOT(getShares()));
connect(_manager, SIGNAL(serverError(int, QString)), this, SLOT(displayError(int, QString))); connect(_manager, SIGNAL(serverError(int, QString)), this, SLOT(displayError(int, QString)));
connect(_ui->shareeLineEdit, SIGNAL(returnPressed()), SLOT(slotLineEditReturn())); connect(_ui->shareeLineEdit, SIGNAL(returnPressed()), SLOT(slotLineEditReturn()));
connect(_ui->privateLinkText, SIGNAL(linkActivated(QString)), SLOT(slotPrivateLinkShare()));
// By making the next two QueuedConnections we can override // By making the next two QueuedConnections we can override
// the strings the completer sets on the line edit. // 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() void ShareUserGroupWidget::slotShareesReady()
{ {
_pi_sharee.stopAnimation(); _pi_sharee.stopAnimation();
@ -301,6 +321,24 @@ void ShareUserGroupWidget::displayError(int code, const QString &message)
_ui->shareeLineEdit->setEnabled(true); _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, ShareUserLine::ShareUserLine(QSharedPointer<Share> share,
SharePermissions maxSharingPermissions, SharePermissions maxSharingPermissions,
bool isFile, bool isFile,

View file

@ -57,6 +57,7 @@ public:
const QString &sharePath, const QString &sharePath,
const QString &localPath, const QString &localPath,
SharePermissions maxSharingPermissions, SharePermissions maxSharingPermissions,
const QByteArray &numericFileId,
QWidget *parent = 0); QWidget *parent = 0);
~ShareUserGroupWidget(); ~ShareUserGroupWidget();
@ -75,19 +76,25 @@ private slots:
void slotCompleterHighlighted(const QModelIndex &index); void slotCompleterHighlighted(const QModelIndex &index);
void slotShareesReady(); void slotShareesReady();
void slotAdjustScrollWidgetSize(); void slotAdjustScrollWidgetSize();
void slotPrivateLinkShare();
void displayError(int code, const QString &message); void displayError(int code, const QString &message);
void slotPrivateLinkOpenBrowser();
void slotPrivateLinkCopy();
void slotPrivateLinkEmail();
private: private:
Ui::ShareUserGroupWidget *_ui; Ui::ShareUserGroupWidget *_ui;
AccountPtr _account; AccountPtr _account;
QString _sharePath; QString _sharePath;
QString _localPath; QString _localPath;
SharePermissions _maxSharingPermissions;
QByteArray _numericFileId;
QCompleter *_completer; QCompleter *_completer;
ShareeModel *_completerModel; ShareeModel *_completerModel;
QTimer _completionTimer; QTimer _completionTimer;
SharePermissions _maxSharingPermissions;
bool _isFile; bool _isFile;
bool _disableCompleterActivated; // in order to avoid that we share the contents twice bool _disableCompleterActivated; // in order to avoid that we share the contents twice
ShareManager *_manager; ShareManager *_manager;

View file

@ -94,14 +94,24 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>395</width> <width>377</width>
<height>221</height> <height>169</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_3"/> <layout class="QVBoxLayout" name="verticalLayout_3"/>
</widget> </widget>
</widget> </widget>
</item> </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> </layout>
</widget> </widget>
<layoutdefault spacing="6" margin="11"/> <layoutdefault spacing="6" margin="11"/>

View file

@ -32,7 +32,9 @@
#include "account.h" #include "account.h"
#include "capabilities.h" #include "capabilities.h"
#include "asserts.h" #include "asserts.h"
#include "guiutility.h"
#include <array>
#include <QBitArray> #include <QBitArray>
#include <QUrl> #include <QUrl>
#include <QMetaMethod> #include <QMetaMethod>
@ -45,6 +47,8 @@
#include <QLocalSocket> #include <QLocalSocket>
#include <QStringBuilder> #include <QStringBuilder>
#include <QClipboard>
#include <sqlite3.h> #include <sqlite3.h>
@ -436,19 +440,10 @@ void SocketApi::command_SHARE(const QString &localFile, SocketListener *listener
return; 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); const QString message = QLatin1String("SHARE:OK:") + QDir::toNativeSeparators(localFile);
listener->sendMessage(message); 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())); 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) QString SocketApi::buildRegisterPathMessage(const QString &path)
{ {
QFileInfo fi(path); QFileInfo fi(path);
@ -522,4 +550,22 @@ QString SocketApi::buildRegisterPathMessage(const QString &path)
return message; 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 } // namespace OCC

View file

@ -55,8 +55,7 @@ public slots:
void slotRegisterPath(const QString &alias); void slotRegisterPath(const QString &alias);
signals: signals:
void shareCommandReceived(const QString &sharePath, const QString &localPath, bool resharingAllowed); void shareCommandReceived(const QString &sharePath, const QString &localPath);
void shareUserGroupCommandReceived(const QString &sharePath, const QString &localPath, bool resharingAllowed);
private slots: private slots:
void slotNewConnection(); void slotNewConnection();
@ -70,13 +69,22 @@ private:
Q_INVOKABLE void command_RETRIEVE_FOLDER_STATUS(const QString &argument, SocketListener *listener); 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_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_VERSION(const QString &argument, SocketListener *listener);
Q_INVOKABLE void command_SHARE_STATUS(const QString &localFile, 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); 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); QString buildRegisterPathMessage(const QString &path);
QUrl getPrivateLinkUrl(const QString &localFile) const;
QSet<QString> _registeredAliases; QSet<QString> _registeredAliases;
QList<SocketListener> _listeners; QList<SocketListener> _listeners;

View file

@ -160,6 +160,12 @@ QUrl Account::davUrl() const
return Utility::concatUrlPath(url(), davPath()); 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) * clear all cookies. (Session cookies or not)
*/ */

View file

@ -107,6 +107,9 @@ public:
/** Returns webdav entry URL, based on url() */ /** Returns webdav entry URL, based on url() */
QUrl davUrl() const; QUrl davUrl() const;
/** Returns a permalink url for a file */
QUrl filePermalinkUrl(const QByteArray &numericFileId) const;
/** Holds the accounts credentials */ /** Holds the accounts credentials */
AbstractCredentials *credentials() const; AbstractCredentials *credentials() const;
void setCredentials(AbstractCredentials *cred); void setCredentials(AbstractCredentials *cred);

View file

@ -42,6 +42,11 @@ Q_LOGGING_CATEGORY(lcPropagateLocalRemove, "sync.propagator.localremove", QtInfo
Q_LOGGING_CATEGORY(lcPropagateLocalMkdir, "sync.propagator.localmkdir", QtInfoMsg) Q_LOGGING_CATEGORY(lcPropagateLocalMkdir, "sync.propagator.localmkdir", QtInfoMsg)
Q_LOGGING_CATEGORY(lcPropagateLocalRename, "sync.propagator.localrename", 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 * Code inspired from Qt5's QDir::removeRecursively
* The code will update the database in case of error. * The code will update the database in case of error.

View file

@ -109,6 +109,17 @@ SyncFileItem SyncJournalFileRecord::toSyncFileItem()
return item; 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 bool SyncJournalErrorBlacklistRecord::isValid() const
{ {
return !_file.isEmpty() return !_file.isEmpty()

View file

@ -48,6 +48,14 @@ public:
return !_path.isEmpty(); 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; QString _path;
quint64 _inode; quint64 _inode;
QDateTime _modtime; QDateTime _modtime;

View 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/accountstate.cpp )
list(APPEND FolderMan_SRC ../src/gui/syncrunfilelog.cpp ) list(APPEND FolderMan_SRC ../src/gui/syncrunfilelog.cpp )
list(APPEND FolderMan_SRC ../src/gui/lockwatcher.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 ${FolderWatcher_SRC})
list(APPEND FolderMan_SRC stub.cpp ) list(APPEND FolderMan_SRC stub.cpp )
owncloud_add_test(FolderMan "${FolderMan_SRC}") owncloud_add_test(FolderMan "${FolderMan_SRC}")