nextcloud-desktop/src/gui/shareusergroupwidget.cpp
Christian Kamm dca2664707 Private links: Retrieve link through propfind property #6020
* The sharing ui does a propfind anyway: use that to query the new
property as well!
* For the socket api, asynchronously query the server for the right url
when an action that needs it is triggered.

The old, manually generated URL will be used as fallback in case the
server doesn't support the new property or the property can't be
retrieved for some reason.

Depends on owncloud/core#29021
2017-09-19 10:51:03 +02:00

562 lines
18 KiB
C++

/*
* Copyright (C) by Roeland Jago Douma <roeland@owncloud.com>
*
* 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 "shareusergroupwidget.h"
#include "ui_shareusergroupwidget.h"
#include "ui_shareuserline.h"
#include "account.h"
#include "folderman.h"
#include "folder.h"
#include "accountmanager.h"
#include "theme.h"
#include "configfile.h"
#include "capabilities.h"
#include "guiutility.h"
#include "thumbnailjob.h"
#include "sharee.h"
#include "sharemanager.h"
#include "QProgressIndicator.h"
#include <QBuffer>
#include <QFileIconProvider>
#include <QClipboard>
#include <QFileInfo>
#include <QAbstractProxyModel>
#include <QCompleter>
#include <qscrollarea.h>
#include <qlayout.h>
#include <QPropertyAnimation>
#include <QMenu>
#include <QAction>
#include <QDesktopServices>
#include <QMessageBox>
namespace OCC {
ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account,
const QString &sharePath,
const QString &localPath,
SharePermissions maxSharingPermissions,
const QString &privateLinkUrl,
QWidget *parent)
: QWidget(parent)
, _ui(new Ui::ShareUserGroupWidget)
, _account(account)
, _sharePath(sharePath)
, _localPath(localPath)
, _maxSharingPermissions(maxSharingPermissions)
, _privateLinkUrl(privateLinkUrl)
, _disableCompleterActivated(false)
{
setAttribute(Qt::WA_DeleteOnClose);
setObjectName("SharingDialogUG"); // required as group for saveGeometry call
_ui->setupUi(this);
//Is this a file or folder?
_isFile = QFileInfo(localPath).isFile();
_completer = new QCompleter(this);
_completerModel = new ShareeModel(_account,
_isFile ? QLatin1String("file") : QLatin1String("folder"),
_completer);
connect(_completerModel, SIGNAL(shareesReady()), this, SLOT(slotShareesReady()));
connect(_completerModel, SIGNAL(displayErrorMessage(int, QString)), this, SLOT(displayError(int, QString)));
_completer->setModel(_completerModel);
_completer->setCaseSensitivity(Qt::CaseInsensitive);
_completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
_ui->shareeLineEdit->setCompleter(_completer);
_manager = new ShareManager(_account, this);
connect(_manager, SIGNAL(sharesFetched(QList<QSharedPointer<Share>>)), SLOT(slotSharesFetched(QList<QSharedPointer<Share>>)));
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.
connect(_completer, SIGNAL(activated(QModelIndex)), SLOT(slotCompleterActivated(QModelIndex)),
Qt::QueuedConnection);
connect(_completer, SIGNAL(highlighted(QModelIndex)), SLOT(slotCompleterHighlighted(QModelIndex)),
Qt::QueuedConnection);
// Queued connection so this signal is recieved after textChanged
connect(_ui->shareeLineEdit, SIGNAL(textEdited(QString)),
this, SLOT(slotLineEditTextEdited(QString)), Qt::QueuedConnection);
connect(&_completionTimer, SIGNAL(timeout()), this, SLOT(searchForSharees()));
_completionTimer.setSingleShot(true);
_completionTimer.setInterval(600);
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding);
_ui->errorLabel->hide();
// Setup the sharee search progress indicator
_ui->shareeHorizontalLayout->addWidget(&_pi_sharee);
}
ShareUserGroupWidget::~ShareUserGroupWidget()
{
delete _ui;
}
void ShareUserGroupWidget::on_shareeLineEdit_textChanged(const QString &)
{
_completionTimer.stop();
}
void ShareUserGroupWidget::slotLineEditTextEdited(const QString &text)
{
_disableCompleterActivated = false;
// First textChanged is called first and we stopped the timer when the text is changed, programatically or not
// Then we restart the timer here if the user touched a key
if (!text.isEmpty()) {
_completionTimer.start();
}
}
void ShareUserGroupWidget::slotLineEditReturn()
{
_disableCompleterActivated = false;
// did the user type in one of the options?
const auto text = _ui->shareeLineEdit->text();
for (int i = 0; i < _completerModel->rowCount(); ++i) {
const auto sharee = _completerModel->getSharee(i);
if (sharee->format() == text
|| sharee->displayName() == text
|| sharee->shareWith() == text) {
slotCompleterActivated(_completerModel->index(i));
// make sure we do not send the same item twice (because return is called when we press
// return to activate an item inthe completer)
_disableCompleterActivated = true;
return;
}
}
// nothing found? try to refresh completion
_completionTimer.start();
}
void ShareUserGroupWidget::searchForSharees()
{
_completionTimer.stop();
_pi_sharee.startAnimation();
ShareeModel::ShareeSet blacklist;
// Add the current user to _sharees since we can't share with ourself
QSharedPointer<Sharee> currentUser(new Sharee(_account->credentials()->user(), "", Sharee::Type::User));
blacklist << currentUser;
foreach (auto sw, _ui->scrollArea->findChildren<ShareUserLine *>()) {
blacklist << sw->share()->getShareWith();
}
_ui->errorLabel->hide();
_completerModel->fetch(_ui->shareeLineEdit->text(), blacklist);
}
void ShareUserGroupWidget::getShares()
{
_manager->fetchShares(_sharePath);
}
void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>> &shares)
{
QScrollArea *scrollArea = _ui->scrollArea;
auto newViewPort = new QWidget(scrollArea);
auto layout = new QVBoxLayout(newViewPort);
QSize minimumSize = newViewPort->sizeHint();
int x = 0;
if (shares.isEmpty()) {
layout->addWidget(new QLabel(tr("The item is not shared with any users or groups")));
} else {
foreach (const auto &share, shares) {
// We don't handle link shares
if (share->getShareType() == Share::TypeLink) {
continue;
}
ShareUserLine *s = new ShareUserLine(share, _maxSharingPermissions, _isFile, _ui->scrollArea);
connect(s, SIGNAL(resizeRequested()), this, SLOT(slotAdjustScrollWidgetSize()));
connect(s, SIGNAL(visualDeletionDone()), this, SLOT(getShares()));
layout->addWidget(s);
x++;
if (x <= 3) {
minimumSize = newViewPort->sizeHint();
} else {
minimumSize.rwidth() = qMax(newViewPort->sizeHint().width(), minimumSize.width());
}
}
layout->addStretch(1);
}
minimumSize.rwidth() += layout->spacing();
minimumSize.rheight() += layout->spacing();
scrollArea->setMinimumSize(minimumSize);
scrollArea->setWidget(newViewPort);
_disableCompleterActivated = false;
_ui->shareeLineEdit->setEnabled(true);
}
void ShareUserGroupWidget::slotAdjustScrollWidgetSize()
{
QScrollArea *scrollArea = _ui->scrollArea;
if (scrollArea->findChildren<ShareUserLine *>().count() <= 3) {
auto minimumSize = scrollArea->widget()->sizeHint();
auto spacing = scrollArea->widget()->layout()->spacing();
minimumSize.rwidth() += spacing;
minimumSize.rheight() += spacing;
scrollArea->setMinimumSize(minimumSize);
}
}
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();
if (_completerModel->rowCount() == 0) {
displayError(0, tr("No results for '%1'").arg(_completerModel->currentSearch()));
return;
}
_completer->complete();
}
void ShareUserGroupWidget::slotCompleterActivated(const QModelIndex &index)
{
if (_disableCompleterActivated)
return;
// The index is an index from the QCompletion model which is itelf a proxy
// model proxying the _completerModel
auto sharee = qvariant_cast<QSharedPointer<Sharee>>(index.data(Qt::UserRole));
if (sharee.isNull()) {
return;
}
/*
* Add spinner to the bottom of the widget list
*/
auto viewPort = _ui->scrollArea->widget();
auto layout = qobject_cast<QVBoxLayout *>(viewPort->layout());
auto indicator = new QProgressIndicator(viewPort);
indicator->startAnimation();
if (layout->count() == 1) {
// No shares yet! Remove the label, add some stretch.
delete layout->itemAt(0)->widget();
layout->addStretch(1);
}
layout->insertWidget(layout->count() - 1, indicator);
/*
* Don't send the reshare permissions for federated shares for servers <9.1
* https://github.com/owncloud/core/issues/22122#issuecomment-185637344
* https://github.com/owncloud/client/issues/4996
*/
if (sharee->type() == Sharee::Federated
&& _account->serverVersionInt() < Account::makeServerVersion(9, 1, 0)) {
int permissions = SharePermissionRead | SharePermissionUpdate;
if (!_isFile) {
permissions |= SharePermissionCreate | SharePermissionDelete;
}
_manager->createShare(_sharePath, Share::ShareType(sharee->type()),
sharee->shareWith(), SharePermission(permissions));
} else {
_manager->createShare(_sharePath, Share::ShareType(sharee->type()),
sharee->shareWith(), SharePermissionDefault);
}
_ui->shareeLineEdit->setEnabled(false);
_ui->shareeLineEdit->setText(QString());
}
void ShareUserGroupWidget::slotCompleterHighlighted(const QModelIndex &index)
{
// By default the completer would set the text to EditRole,
// override that here.
_ui->shareeLineEdit->setText(index.data(Qt::DisplayRole).toString());
}
void ShareUserGroupWidget::displayError(int code, const QString &message)
{
_pi_sharee.stopAnimation();
// Also remove the spinner in the widget list, if any
foreach (auto pi, _ui->scrollArea->findChildren<QProgressIndicator *>()) {
delete pi;
}
qCWarning(lcSharing) << "Sharing error from server" << code << message;
_ui->errorLabel->setText(message);
_ui->errorLabel->show();
_ui->shareeLineEdit->setEnabled(true);
}
void ShareUserGroupWidget::slotPrivateLinkOpenBrowser()
{
Utility::openBrowser(_privateLinkUrl, this);
}
void ShareUserGroupWidget::slotPrivateLinkCopy()
{
QApplication::clipboard()->setText(_privateLinkUrl);
}
void ShareUserGroupWidget::slotPrivateLinkEmail()
{
Utility::openEmailComposer(
tr("I shared something with you"),
_privateLinkUrl,
this);
}
ShareUserLine::ShareUserLine(QSharedPointer<Share> share,
SharePermissions maxSharingPermissions,
bool isFile,
QWidget *parent)
: QWidget(parent)
, _ui(new Ui::ShareUserLine)
, _share(share)
, _isFile(isFile)
{
_ui->setupUi(this);
_ui->sharedWith->setText(share->getShareWith()->format());
// Create detailed permissions menu
QMenu *menu = new QMenu(this);
_permissionCreate = new QAction(tr("create"), this);
_permissionCreate->setCheckable(true);
_permissionCreate->setEnabled(maxSharingPermissions & SharePermissionCreate);
_permissionUpdate = new QAction(tr("change"), this);
_permissionUpdate->setCheckable(true);
_permissionUpdate->setEnabled(maxSharingPermissions & SharePermissionUpdate);
_permissionDelete = new QAction(tr("delete"), this);
_permissionDelete->setCheckable(true);
_permissionDelete->setEnabled(maxSharingPermissions & SharePermissionDelete);
menu->addAction(_permissionUpdate);
/*
* Files can't have create or delete permissions
*/
if (!_isFile) {
menu->addAction(_permissionCreate);
menu->addAction(_permissionDelete);
}
_ui->permissionToolButton->setMenu(menu);
_ui->permissionToolButton->setPopupMode(QToolButton::InstantPopup);
QIcon icon(QLatin1String(":/client/resources/more.png"));
_ui->permissionToolButton->setIcon(icon);
// If there's only a single entry in the detailed permission menu, hide it
if (menu->actions().size() == 1) {
_ui->permissionToolButton->hide();
}
// Set the permissions checkboxes
displayPermissions();
_ui->permissionShare->setEnabled(maxSharingPermissions & SharePermissionShare);
_ui->permissionsEdit->setEnabled(maxSharingPermissions
& (SharePermissionCreate | SharePermissionUpdate | SharePermissionDelete));
connect(_permissionUpdate, SIGNAL(triggered(bool)), SLOT(slotPermissionsChanged()));
connect(_permissionCreate, SIGNAL(triggered(bool)), SLOT(slotPermissionsChanged()));
connect(_permissionDelete, SIGNAL(triggered(bool)), SLOT(slotPermissionsChanged()));
connect(_ui->permissionShare, SIGNAL(clicked(bool)), SLOT(slotPermissionsChanged()));
connect(_ui->permissionsEdit, SIGNAL(clicked(bool)), SLOT(slotEditPermissionsChanged()));
/*
* We don't show permssion share for federated shares with server <9.1
* https://github.com/owncloud/core/issues/22122#issuecomment-185637344
* https://github.com/owncloud/client/issues/4996
*/
if (share->getShareType() == Share::TypeRemote
&& share->account()->serverVersionInt() < Account::makeServerVersion(9, 1, 0)) {
_ui->permissionShare->setVisible(false);
_ui->permissionToolButton->setVisible(false);
}
connect(share.data(), SIGNAL(permissionsSet()), SLOT(slotPermissionsSet()));
connect(share.data(), SIGNAL(shareDeleted()), SLOT(slotShareDeleted()));
_ui->deleteShareButton->setIcon(QIcon::fromTheme(QLatin1String("user-trash"),
QIcon(QLatin1String(":/client/resources/delete.png"))));
if (!share->account()->capabilities().shareResharing()) {
_ui->permissionShare->hide();
}
}
void ShareUserLine::on_deleteShareButton_clicked()
{
setEnabled(false);
_share->deleteShare();
}
ShareUserLine::~ShareUserLine()
{
delete _ui;
}
void ShareUserLine::slotEditPermissionsChanged()
{
setEnabled(false);
// Can never manually be set to "partial".
// This works because the state cycle for clicking is
// unchecked -> partial -> checked -> unchecked.
if (_ui->permissionsEdit->checkState() == Qt::PartiallyChecked) {
_ui->permissionsEdit->setCheckState(Qt::Checked);
}
Share::Permissions permissions = SharePermissionRead;
if (_ui->permissionShare->checkState() == Qt::Checked) {
permissions |= SharePermissionShare;
}
if (_ui->permissionsEdit->checkState() == Qt::Checked) {
if (_permissionUpdate->isEnabled())
permissions |= SharePermissionUpdate;
/*
* Files can't have create or delete permisisons
*/
if (!_isFile) {
if (_permissionCreate->isEnabled())
permissions |= SharePermissionCreate;
if (_permissionDelete->isEnabled())
permissions |= SharePermissionDelete;
}
}
_share->setPermissions(permissions);
}
void ShareUserLine::slotPermissionsChanged()
{
setEnabled(false);
Share::Permissions permissions = SharePermissionRead;
if (_permissionUpdate->isChecked()) {
permissions |= SharePermissionUpdate;
}
if (_permissionCreate->isChecked()) {
permissions |= SharePermissionCreate;
}
if (_permissionDelete->isChecked()) {
permissions |= SharePermissionDelete;
}
if (_ui->permissionShare->checkState() == Qt::Checked) {
permissions |= SharePermissionShare;
}
_share->setPermissions(permissions);
}
void ShareUserLine::slotDeleteAnimationFinished()
{
emit resizeRequested();
emit visualDeletionDone();
deleteLater();
// There is a painting bug where a small line of this widget isn't
// properly cleared. This explicit repaint() call makes sure any trace of
// the share widget is removed once it's destroyed. #4189
connect(this, SIGNAL(destroyed(QObject *)), parentWidget(), SLOT(repaint()));
}
void ShareUserLine::slotShareDeleted()
{
QPropertyAnimation *animation = new QPropertyAnimation(this, "maximumHeight", this);
animation->setDuration(500);
animation->setStartValue(height());
animation->setEndValue(0);
connect(animation, SIGNAL(finished()), SLOT(slotDeleteAnimationFinished()));
connect(animation, SIGNAL(valueChanged(QVariant)), this, SIGNAL(resizeRequested()));
animation->start();
}
void ShareUserLine::slotPermissionsSet()
{
displayPermissions();
setEnabled(true);
}
QSharedPointer<Share> ShareUserLine::share() const
{
return _share;
}
void ShareUserLine::displayPermissions()
{
auto perm = _share->getPermissions();
_permissionUpdate->setChecked(false);
_permissionCreate->setChecked(false);
_permissionDelete->setChecked(false);
if (perm & SharePermissionUpdate) {
_permissionUpdate->setChecked(true);
}
if (!_isFile && perm & SharePermissionCreate) {
_permissionCreate->setChecked(true);
}
if (!_isFile && perm & SharePermissionDelete) {
_permissionDelete->setChecked(true);
}
if (perm & SharePermissionUpdate
&& (_isFile
|| (perm & SharePermissionCreate
&& perm & SharePermissionDelete))) {
_ui->permissionsEdit->setCheckState(Qt::Checked);
} else if (perm & (SharePermissionUpdate | SharePermissionCreate | SharePermissionDelete)) {
_ui->permissionsEdit->setCheckState(Qt::PartiallyChecked);
} else {
_ui->permissionsEdit->setCheckState(Qt::Unchecked);
}
_ui->permissionShare->setCheckState(Qt::Unchecked);
if (_share->getPermissions() & SharePermissionShare) {
_ui->permissionShare->setCheckState(Qt::Checked);
}
}
}