Skip to content

Commit

Permalink
Introduce private link sharing #5023
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
ckamm committed Jul 7, 2017
1 parent d01065b commit 0238a29
Show file tree
Hide file tree
Showing 25 changed files with 390 additions and 107 deletions.
28 changes: 24 additions & 4 deletions shell_integration/dolphin/ownclouddolphinactionplugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -53,12 +54,31 @@ class OwncloudDolphinPluginAction : public KAbstractFileItemActionPlugin
} ))
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 };
}

};
Expand Down
10 changes: 6 additions & 4 deletions shell_integration/dolphin/ownclouddolphinpluginhelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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);
Expand Down
16 changes: 14 additions & 2 deletions shell_integration/dolphin/ownclouddolphinpluginhelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -47,6 +58,7 @@ class OWNCLOUDDOLPHINPLUGINHELPER_EXPORT OwncloudDolphinPluginHelper : public QO
QLocalSocket _socket;
QByteArray _line;
QVector<QString> _paths;
QString _shareActionString;
QBasicTimer _connectTimer;

QMap<QString, QString> _strings;
};
67 changes: 49 additions & 18 deletions shell_integration/nautilus/syncstate.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ def _connectToSocketServer(self):
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))
Expand Down Expand Up @@ -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
Expand All @@ -178,7 +188,6 @@ def get_file_items(self, window, files):
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 "/"
Expand All @@ -190,12 +199,14 @@ def get_file_items(self, window, files):
# 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')
Expand All @@ -212,22 +223,42 @@ def get_file_items(self, window, files):
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):
Expand Down
2 changes: 1 addition & 1 deletion src/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ set(client_SRCS
notificationwidget.cpp
notificationconfirmjob.cpp
servernotificationhandler.cpp
guiutility.cpp
creds/credentialsfactory.cpp
creds/httpcredentialsgui.cpp
creds/oauth.cpp
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/gui/application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Expand Down
14 changes: 0 additions & 14 deletions src/gui/clipboard.mm

This file was deleted.

56 changes: 56 additions & 0 deletions src/gui/guiutility.cpp
Original file line number Diff line number Diff line change
@@ -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 changes: 41 additions & 0 deletions src/gui/guiutility.h
Original file line number Diff line number Diff line change
@@ -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
16 changes: 14 additions & 2 deletions src/gui/owncloudgui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "accountstate.h"
#include "openfilemanager.h"
#include "accountmanager.h"
#include "syncjournalfilerecord.h"
#include "creds/abstractcredentials.h"

#include <QDesktopServices>
Expand Down Expand Up @@ -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) {
Expand All @@ -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).
//
Expand All @@ -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;
Expand Down
Loading

0 comments on commit 0238a29

Please sign in to comment.