nextcloud-desktop/src/gui/owncloudgui.cpp
Klaas Freitag 8cc5ff0e70 ShareDialog: Consider if resharing is not allowed on a share.
If a file or directory is shared without resharing permission, the
share dialog displays an error. This is not the optimal solution, but
best for now, as we do not have the permissions available for the file
manager plugin.

This fixes #2923
2015-03-11 14:12:08 +01:00

662 lines
22 KiB
C++

/*
* Copyright (C) by Klaas Freitag <freitag@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; version 2 of the License.
*
* 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 "application.h"
#include "owncloudgui.h"
#include "theme.h"
#include "folderman.h"
#include "configfile.h"
#include "utility.h"
#include "progressdispatcher.h"
#include "owncloudsetupwizard.h"
#include "sharedialog.h"
#if defined(Q_OS_MAC)
# include "settingsdialogmac.h"
# include "macwindow.h" // qtmacgoodies
#else
# include "settingsdialog.h"
#endif
#include "logger.h"
#include "logbrowser.h"
#include "account.h"
#include "accountstate.h"
#include "openfilemanager.h"
#include "creds/abstractcredentials.h"
#include <QDesktopServices>
#include <QMessageBox>
#include <QSignalMapper>
#if defined(Q_OS_X11)
#include <QX11Info>
#endif
namespace OCC {
ownCloudGui::ownCloudGui(Application *parent) :
QObject(parent),
_tray(0),
#if defined(Q_OS_MAC)
_settingsDialog(new SettingsDialogMac(this)),
#else
_settingsDialog(new SettingsDialog(this)),
#endif
_logBrowser(0),
_recentActionsMenu(0),
_folderOpenActionMapper(new QSignalMapper(this)),
_recentItemsMapper(new QSignalMapper(this)),
_app(parent)
{
_tray = new Systray();
_tray->setParent(this);
// for the beginning, set the offline icon until the account was verified
_tray->setIcon( Theme::instance()->folderOfflineIcon(true));
connect(_tray.data(), SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
SLOT(slotTrayClicked(QSystemTrayIcon::ActivationReason)));
setupActions();
setupContextMenu();
_tray->show();
/* use a signal mapper to map the open requests to the alias names */
connect(_folderOpenActionMapper, SIGNAL(mapped(QString)),
this, SLOT(slotFolderOpenAction(QString)));
connect(_recentItemsMapper, SIGNAL(mapped(QString)),
this, SLOT(slotOpenPath(QString)));
ProgressDispatcher *pd = ProgressDispatcher::instance();
connect( pd, SIGNAL(progressInfo(QString,Progress::Info)), this,
SLOT(slotUpdateProgress(QString,Progress::Info)) );
FolderMan *folderMan = FolderMan::instance();
connect( folderMan, SIGNAL(folderSyncStateChange(QString)),
this,SLOT(slotSyncStateChange(QString)));
connect( Logger::instance(), SIGNAL(guiLog(QString,QString)),
SLOT(slotShowTrayMessage(QString,QString)));
connect( Logger::instance(), SIGNAL(optionalGuiLog(QString,QString)),
SLOT(slotShowOptionalTrayMessage(QString,QString)));
connect( Logger::instance(), SIGNAL(guiMessage(QString,QString)),
SLOT(slotShowGuiMessage(QString,QString)));
setupOverlayIcons();
}
// Use this to do platform specific code to make overlay icons appear
// in the gui
// MacOSX: perform a AppleScript code peace to load the Finder Plugin.
void ownCloudGui::setupOverlayIcons()
{
#ifdef Q_OS_MAC
const QLatin1String finderExtension("/Library/ScriptingAdditions/SyncStateFinder.osax");
if(QFile::exists(finderExtension) ) {
QString aScript = QString::fromUtf8("tell application \"Finder\"\n"
" try\n"
" «event OWNCload»\n"
" end try\n"
"end tell\n");
QString osascript = "/usr/bin/osascript";
QStringList processArguments;
// processArguments << "-l" << "AppleScript";
QProcess p;
p.start(osascript, processArguments);
p.write(aScript.toUtf8());
p.closeWriteChannel();
p.waitForReadyRead(-1);
QByteArray result = p.readAll();
QString resultAsString(result); // if appropriate
qDebug() << "Laod Finder Overlay-Plugin: " << resultAsString << ": " << p.exitCode()
<< (p.exitCode() != 0 ? p.errorString() : QString::null);
} else {
qDebug() << finderExtension << "does not exist! Finder Overlay Plugin loading failed";
}
#endif
}
// This should rather be in application.... or rather in ConfigFile?
void ownCloudGui::slotOpenSettingsDialog( bool openSettings )
{
// if account is set up, start the configuration wizard.
if( AccountManager::instance()->account() ) {
if( openSettings ) {
if (_settingsDialog.isNull() || !_settingsDialog->isVisible()) {
slotShowSettings();
} else {
_settingsDialog->close();
}
}
} else {
qDebug() << "No configured folders yet, starting setup wizard";
OwncloudSetupWizard::runWizard(qApp, SLOT(slotownCloudWizardDone(int)));
}
}
void ownCloudGui::slotTrayClicked( QSystemTrayIcon::ActivationReason reason )
{
// A click on the tray icon should only open the status window on Win and
// Linux, not on Mac. They want a menu entry.
#if !defined Q_OS_MAC
if( reason == QSystemTrayIcon::Trigger ) {
slotOpenSettingsDialog(true); // start settings if config is existing.
}
#else
// On Mac, if the settings dialog is already visible but hidden
// by other applications, this will bring it to the front.
if( reason == QSystemTrayIcon::Trigger ) {
if (!_settingsDialog.isNull() && _settingsDialog->isVisible()) {
slotShowSettings();
}
}
#endif
}
void ownCloudGui::slotSyncStateChange( const QString& alias )
{
FolderMan *folderMan = FolderMan::instance();
const SyncResult& result = folderMan->syncResult( alias );
slotComputeOverallSyncStatus();
if( alias.isEmpty() ) {
return; // Valid, just a general GUI redraw was needed.
}
qDebug() << "Sync state changed for folder " << alias << ": " << result.statusString();
if (result.status() == SyncResult::Success || result.status() == SyncResult::Error) {
Logger::instance()->enterNextLogFile();
}
}
void ownCloudGui::slotFoldersChanged()
{
slotComputeOverallSyncStatus();
setupContextMenu();
}
void ownCloudGui::slotOpenPath(const QString &path)
{
showInFileManager(path);
}
void ownCloudGui::slotAccountStateChanged()
{
setupContextMenu();
slotComputeOverallSyncStatus();
}
void ownCloudGui::setConnectionErrors( bool /*connected*/, const QStringList& fails )
{
_startupFails = fails; // store that for the settings dialog once it appears.
if( !_settingsDialog.isNull() ) {
_settingsDialog->setGeneralErrors( _startupFails );
}
slotComputeOverallSyncStatus();
}
void ownCloudGui::slotComputeOverallSyncStatus()
{
if (AccountState *a = AccountStateManager::instance()->accountState()) {
if (a->isSignedOut()) {
_tray->setIcon(Theme::instance()->folderOfflineIcon(true));
_tray->setToolTip(tr("Please sign in"));
return;
}
if (!a->isConnectedOrMaintenance()) {
_tray->setIcon(Theme::instance()->folderOfflineIcon(true));
_tray->setToolTip(tr("Disconnected from server"));
return;
}
}
// display the info of the least successful sync (eg. not just display the result of the latest sync
QString trayMessage;
FolderMan *folderMan = FolderMan::instance();
Folder::Map map = folderMan->map();
SyncResult overallResult = FolderMan::accountStatus(map.values());
// if there have been startup problems, show an error message.
if( !_settingsDialog.isNull() )
_settingsDialog->setGeneralErrors( _startupFails );
if( !_startupFails.isEmpty() ) {
trayMessage = _startupFails.join(QLatin1String("\n"));
QIcon statusIcon;
if (_app->_startupNetworkError) {
statusIcon = Theme::instance()->syncStateIcon( SyncResult::NotYetStarted, true );
} else {
statusIcon = Theme::instance()->syncStateIcon( SyncResult::Error, true );
}
_tray->setIcon( statusIcon );
_tray->setToolTip(trayMessage);
} else {
// create the tray blob message, check if we have an defined state
if( overallResult.status() != SyncResult::Undefined ) {
QStringList allStatusStrings;
if( map.count() > 0 ) {
foreach(Folder* folder, map.values()) {
qDebug() << "Folder in overallStatus Message: " << folder << " with name " << folder->alias();
QString folderMessage = folderMan->statusToString(folder->syncResult().status(), folder->syncPaused());
allStatusStrings += tr("Folder %1: %2").arg(folder->alias(), folderMessage);
}
trayMessage = allStatusStrings.join(QLatin1String("\n"));
} else {
trayMessage = tr("No sync folders configured.");
}
QIcon statusIcon = Theme::instance()->syncStateIcon( overallResult.status(), true);
_tray->setIcon( statusIcon );
_tray->setToolTip(trayMessage);
} else {
// undefined because there are no folders.
QIcon icon = Theme::instance()->syncStateIcon(SyncResult::Problem, true);
_tray->setIcon( icon );
_tray->setToolTip(tr("There are no sync folders configured."));
}
}
}
void ownCloudGui::setupContextMenu()
{
FolderMan *folderMan = FolderMan::instance();
AccountState *a = AccountStateManager::instance()->accountState();
bool isConfigured = (a != 0);
_actionOpenoC->setEnabled(isConfigured);
bool isConnected = false;
if (isConfigured) {
isConnected = a->isConnected();
}
if ( _contextMenu ) {
_contextMenu->clear();
_recentActionsMenu->clear();
_recentActionsMenu->addAction(tr("None."));
_recentActionsMenu->addAction(_actionRecent);
} else {
_contextMenu.reset(new QMenu());
_recentActionsMenu = new QMenu(tr("Recent Changes"), _contextMenu.data());
// this must be called only once after creating the context menu, or
// it will trigger a bug in Ubuntu's SNI bridge patch (11.10, 12.04).
_tray->setContextMenu(_contextMenu.data());
}
_contextMenu->setTitle(Theme::instance()->appNameGUI() );
_contextMenu->addAction(_actionOpenoC);
int folderCnt = folderMan->map().size();
// add open actions for all sync folders to the tray menu
if( Theme::instance()->singleSyncFolder() ) {
// there should be exactly one folder. No sync-folder add action will be shown.
QStringList li = folderMan->map().keys();
if( li.size() == 1 ) {
Folder *folder = folderMan->map().value(li.first());
if( folder ) {
// if there is singleFolder mode, a generic open action is displayed.
QAction *action = new QAction( tr("Open %1 folder").arg(Theme::instance()->appNameGUI()), this);
connect( action, SIGNAL(triggered()),_folderOpenActionMapper,SLOT(map()));
_folderOpenActionMapper->setMapping( action, folder->alias() );
_contextMenu->addAction(action);
}
}
} else {
// show a grouping with more than one folder.
if ( folderCnt > 1) {
_contextMenu->addAction(tr("Managed Folders:"))->setDisabled(true);
}
// Put everything first in a QMap to properly sort
QMap<QString, Folder*> folders;
for( auto i = folderMan->map().constBegin(); i != folderMan->map().constEnd(); i++) {
folders.insert(i.key(), i.value());
}
foreach (auto folder, folders) {
QAction *action = new QAction( tr("Open folder '%1'").arg(folder->alias()), this );
connect( action, SIGNAL(triggered()),_folderOpenActionMapper,SLOT(map()));
_folderOpenActionMapper->setMapping( action, folder->alias() );
_contextMenu->addAction(action);
}
}
_contextMenu->addSeparator();
if (isConfigured && isConnected) {
_contextMenu->addAction(_actionQuota);
_contextMenu->addSeparator();
_contextMenu->addAction(_actionStatus);
_contextMenu->addMenu(_recentActionsMenu);
_contextMenu->addSeparator();
}
_contextMenu->addAction(_actionSettings);
if (!Theme::instance()->helpUrl().isEmpty()) {
_contextMenu->addAction(_actionHelp);
}
if(_actionCrash) {
_contextMenu->addAction(_actionCrash);
}
_contextMenu->addSeparator();
if (isConfigured && isConnected) {
_contextMenu->addAction(_actionLogout);
} else {
_contextMenu->addAction(_actionLogin);
}
_contextMenu->addAction(_actionQuit);
}
void ownCloudGui::slotShowTrayMessage(const QString &title, const QString &msg)
{
if( _tray )
_tray->showMessage(title, msg);
else
qDebug() << "Tray not ready: " << msg;
}
void ownCloudGui::slotShowOptionalTrayMessage(const QString &title, const QString &msg)
{
ConfigFile cfg;
if (cfg.optionalDesktopNotifications()) {
slotShowTrayMessage(title, msg);
}
}
/*
* open the folder with the given Alais
*/
void ownCloudGui::slotFolderOpenAction( const QString& alias )
{
Folder *f = FolderMan::instance()->folder(alias);
if( f ) {
qDebug() << "opening local url " << f->path();
QUrl url = QUrl::fromLocalFile(f->path());
#ifdef Q_OS_WIN
// work around a bug in QDesktopServices on Win32, see i-net
QString filePath = f->path();
if (filePath.startsWith(QLatin1String("\\\\")) || filePath.startsWith(QLatin1String("//")))
url = QUrl::fromLocalFile(QDir::toNativeSeparators(filePath));
else
url = QUrl::fromLocalFile(filePath);
#endif
QDesktopServices::openUrl(url);
}
}
void ownCloudGui::setupActions()
{
_actionOpenoC = new QAction(tr("Open %1 in browser").arg(Theme::instance()->appNameGUI()), this);
QObject::connect(_actionOpenoC, SIGNAL(triggered(bool)), SLOT(slotOpenOwnCloud()));
_actionQuota = new QAction(tr("Calculating quota..."), this);
_actionQuota->setEnabled( false );
_actionStatus = new QAction(tr("Unknown status"), this);
_actionStatus->setEnabled( false );
_actionSettings = new QAction(tr("Settings..."), this);
_actionRecent = new QAction(tr("Details..."), this);
_actionRecent->setEnabled( true );
QObject::connect(_actionRecent, SIGNAL(triggered(bool)), SLOT(slotShowSyncProtocol()));
QObject::connect(_actionSettings, SIGNAL(triggered(bool)), SLOT(slotShowSettings()));
_actionHelp = new QAction(tr("Help"), this);
QObject::connect(_actionHelp, SIGNAL(triggered(bool)), SLOT(slotHelp()));
_actionQuit = new QAction(tr("Quit %1").arg(Theme::instance()->appNameGUI()), this);
QObject::connect(_actionQuit, SIGNAL(triggered(bool)), _app, SLOT(quit()));
_actionLogin = new QAction(tr("Sign in..."), this);
connect(_actionLogin, SIGNAL(triggered()), _app, SLOT(slotLogin()));
_actionLogout = new QAction(tr("Sign out"), this);
connect(_actionLogout, SIGNAL(triggered()), _app, SLOT(slotLogout()));
if(_app->debugMode()) {
_actionCrash = new QAction(tr("Crash now", "Only shows in debug mode to allow testing the crash handler"), this);
connect(_actionCrash, SIGNAL(triggered()), _app, SLOT(slotCrash()));
} else {
_actionCrash = 0;
}
}
void ownCloudGui::slotRefreshQuotaDisplay( qint64 total, qint64 used )
{
if (total == 0) {
_actionQuota->setText(tr("Quota n/a"));
return;
}
double percent = used/(double)total*100;
QString percentFormatted = Utility::compactFormatDouble(percent, 1);
QString totalFormatted = Utility::octetsToString(total);
_actionQuota->setText(tr("%1% of %2 in use").arg(percentFormatted).arg(totalFormatted));
}
void ownCloudGui::slotRebuildRecentMenus()
{
_recentActionsMenu->clear();
if (!_recentItemsActions.isEmpty()) {
foreach(QAction *a, _recentItemsActions) {
_recentActionsMenu->addAction(a);
}
_recentActionsMenu->addSeparator();
} else {
_recentActionsMenu->addAction(tr("No items synced recently"))->setEnabled(false);
}
// add a more... entry.
_recentActionsMenu->addAction(_actionRecent);
}
void ownCloudGui::slotUpdateProgress(const QString &folder, const Progress::Info& progress)
{
Q_UNUSED(folder);
if (!progress._currentDiscoveredFolder.isEmpty()) {
_actionStatus->setText( tr("Discovering '%1'")
.arg( progress._currentDiscoveredFolder ));
} else if (progress._totalSize == 0 ) {
quint64 currentFile = progress._completedFileCount + progress._currentItems.count();
quint64 totalFileCount = qMax(progress._totalFileCount, currentFile);
_actionStatus->setText( tr("Syncing %1 of %2 (%3 left)")
.arg( currentFile ).arg( totalFileCount )
.arg( Utility::timeToDescriptiveString(progress.totalEstimate().getEtaEstimate(), 2, " ",true) ) );
} else {
QString totalSizeStr = Utility::octetsToString( progress._totalSize );
_actionStatus->setText( tr("Syncing %1 (%2 left)")
.arg( totalSizeStr )
.arg( Utility::timeToDescriptiveString(progress.totalEstimate().getEtaEstimate(), 2, " ",true) ) );
}
_actionRecent->setIcon( QIcon() ); // Fixme: Set a "in-progress"-item eventually.
if (!progress._lastCompletedItem.isEmpty() && !Progress::isIgnoredKind(progress._lastCompletedItem._status)) {
if (Progress::isWarningKind(progress._lastCompletedItem._status)) {
// display a warn icon if warnings happend.
QIcon warnIcon(":/client/resources/warning");
_actionRecent->setIcon(warnIcon);
}
QString kindStr = Progress::asResultString(progress._lastCompletedItem);
QString timeStr = QTime::currentTime().toString("hh:mm");
QString actionText = tr("%1 (%2, %3)").arg(progress._lastCompletedItem._file, kindStr, timeStr);
QAction *action = new QAction(actionText, this);
Folder *f = FolderMan::instance()->folder(folder);
if (f) {
QString fullPath = f->path() + '/' + progress._lastCompletedItem._file;
if (QFile(fullPath).exists()) {
_recentItemsMapper->setMapping(action, fullPath);
connect(action, SIGNAL(triggered()), _recentItemsMapper, SLOT(map()));
} else {
action->setEnabled(false);
}
}
if (_recentItemsActions.length() > 5) {
_recentItemsActions.takeFirst()->deleteLater();
}
_recentItemsActions.append(action);
slotRebuildRecentMenus();
}
if (progress._completedFileCount != ULLONG_MAX
&& progress._completedFileCount >= progress._totalFileCount
&& progress._currentDiscoveredFolder.isEmpty()) {
QTimer::singleShot(2000, this, SLOT(slotDisplayIdle()));
}
}
void ownCloudGui::slotDisplayIdle()
{
_actionStatus->setText(tr("Up to date"));
}
void ownCloudGui::slotShowGuiMessage(const QString &title, const QString &message)
{
QMessageBox *msgBox = new QMessageBox;
msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->setText(message);
msgBox->setWindowTitle(title);
msgBox->setIcon(QMessageBox::Information);
msgBox->open();
}
void ownCloudGui::slotShowSettings()
{
qDebug() << Q_FUNC_INFO;
if (_settingsDialog.isNull()) {
_settingsDialog =
#if defined(Q_OS_MAC)
new SettingsDialogMac(this);
#else
new SettingsDialog(this);
#endif
_settingsDialog->setAttribute( Qt::WA_DeleteOnClose, true );
_settingsDialog->show();
}
_settingsDialog->setGeneralErrors( _startupFails );
raiseDialog(_settingsDialog.data());
}
void ownCloudGui::slotShowSyncProtocol()
{
slotShowSettings();
_settingsDialog->showActivityPage();
}
void ownCloudGui::slotShutdown()
{
// those do delete on close
if (!_settingsDialog.isNull()) _settingsDialog->close();
if (!_logBrowser.isNull()) _logBrowser->deleteLater();
}
void ownCloudGui::slotToggleLogBrowser()
{
if (_logBrowser.isNull()) {
// init the log browser.
_logBrowser = new LogBrowser;
// ## TODO: allow new log name maybe?
}
if (_logBrowser->isVisible() ) {
_logBrowser->hide();
} else {
raiseDialog(_logBrowser);
}
}
void ownCloudGui::slotOpenOwnCloud()
{
if (AccountPtr account = AccountManager::instance()->account()) {
QDesktopServices::openUrl(account->url());
}
}
void ownCloudGui::slotHelp()
{
QDesktopServices::openUrl(QUrl(Theme::instance()->helpUrl()));
}
void ownCloudGui::raiseDialog( QWidget *raiseWidget )
{
if( raiseWidget && raiseWidget->parentWidget() == 0) {
// Qt has a bug which causes parent-less dialogs to pop-under.
raiseWidget->showNormal();
raiseWidget->raise();
raiseWidget->activateWindow();
#if defined(Q_OS_MAC)
// viel hilft viel ;-)
MacWindow::bringToFront(raiseWidget);
#endif
#if defined(Q_OS_X11)
WId wid = widget->winId();
NETWM::init();
XEvent e;
e.xclient.type = ClientMessage;
e.xclient.message_type = NETWM::NET_ACTIVE_WINDOW;
e.xclient.display = QX11Info::display();
e.xclient.window = wid;
e.xclient.format = 32;
e.xclient.data.l[0] = 2;
e.xclient.data.l[1] = QX11Info::appTime();
e.xclient.data.l[2] = 0;
e.xclient.data.l[3] = 0l;
e.xclient.data.l[4] = 0l;
Display *display = QX11Info::display();
XSendEvent(display,
RootWindow(display, DefaultScreen(display)),
False, // propagate
SubstructureRedirectMask|SubstructureNotifyMask,
&e);
#endif
}
}
void ownCloudGui::slotShowShareDialog(const QString &sharePath, const QString &localPath, bool resharingAllowed)
{
AccountPtr account = AccountManager::instance()->account();
if (!account) {
qDebug() << "Could not open share dialog because no account is configured";
return;
}
qDebug() << Q_FUNC_INFO << "Opening share dialog";
ShareDialog *w = new ShareDialog(account, sharePath, localPath, resharingAllowed);
w->getShares();
w->setAttribute( Qt::WA_DeleteOnClose, true );
raiseDialog(w);
}
} // end namespace