Merge branch 'master' into slideshow

This commit is contained in:
Roeland Jago Douma 2018-11-02 10:43:01 +01:00 committed by GitHub
commit e6f1d7632a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
67 changed files with 39422 additions and 41851 deletions

View file

@ -14,7 +14,7 @@ pipeline:
cd /tmp && cd /tmp &&
git clone https://github.com/frankosterfeld/qtkeychain.git && git clone https://github.com/frankosterfeld/qtkeychain.git &&
cd qtkeychain && cd qtkeychain &&
git checkout v0.8.0 && git checkout v0.9.1 &&
mkdir build && mkdir build &&
cd build && cd build &&
cmake ../ && cmake ../ &&
@ -42,7 +42,7 @@ pipeline:
cd /tmp && cd /tmp &&
git clone https://github.com/frankosterfeld/qtkeychain.git && git clone https://github.com/frankosterfeld/qtkeychain.git &&
cd qtkeychain && cd qtkeychain &&
git checkout v0.8.0 && git checkout v0.9.1 &&
mkdir build && mkdir build &&
cd build && cd build &&
cmake ../ && cmake ../ &&
@ -70,7 +70,7 @@ pipeline:
cd /tmp && cd /tmp &&
git clone https://github.com/frankosterfeld/qtkeychain.git && git clone https://github.com/frankosterfeld/qtkeychain.git &&
cd qtkeychain && cd qtkeychain &&
git checkout v0.8.0 && git checkout v0.9.1 &&
mkdir build && mkdir build &&
cd build && cd build &&
cmake ../ && cmake ../ &&
@ -100,7 +100,7 @@ pipeline:
cd /tmp && cd /tmp &&
git clone https://github.com/frankosterfeld/qtkeychain.git && git clone https://github.com/frankosterfeld/qtkeychain.git &&
cd qtkeychain && cd qtkeychain &&
git checkout v0.8.0 && git checkout v0.9.1 &&
mkdir build && mkdir build &&
cd build && cd build &&
cmake ../ && cmake ../ &&
@ -132,7 +132,7 @@ pipeline:
cd /tmp && cd /tmp &&
git clone https://github.com/frankosterfeld/qtkeychain.git && git clone https://github.com/frankosterfeld/qtkeychain.git &&
cd qtkeychain && cd qtkeychain &&
git checkout v0.8.0 && git checkout v0.9.1 &&
mkdir build && mkdir build &&
cd build && cd build &&
cmake ../ && cmake ../ &&
@ -165,7 +165,7 @@ pipeline:
cd /tmp && cd /tmp &&
git clone https://github.com/frankosterfeld/qtkeychain.git && git clone https://github.com/frankosterfeld/qtkeychain.git &&
cd qtkeychain && cd qtkeychain &&
git checkout v0.8.0 && git checkout v0.9.1 &&
mkdir build && mkdir build &&
cd build && cd build &&
cmake ../ && cmake ../ &&

View file

@ -18,11 +18,11 @@ if [ $SUFFIX != "master" ]; then
SUFFIX="PR-$SUFFIX" SUFFIX="PR-$SUFFIX"
fi fi
#QtKeyChain 0.8.0 #QtKeyChain 0.9.1
cd /build cd /build
git clone https://github.com/frankosterfeld/qtkeychain.git git clone https://github.com/frankosterfeld/qtkeychain.git
cd qtkeychain cd qtkeychain
git checkout v0.8.0 git checkout v0.9.1
mkdir build mkdir build
cd build cd build
cmake -D CMAKE_INSTALL_PREFIX=/usr ../ cmake -D CMAKE_INSTALL_PREFIX=/usr ../

View file

@ -272,10 +272,12 @@ bool FileSystem::openAndSeekFileSharedRead(QFile *file, QString *errorOrNull, qi
int fd = _open_osfhandle((intptr_t)fileHandle, _O_RDONLY); int fd = _open_osfhandle((intptr_t)fileHandle, _O_RDONLY);
if (fd == -1) { if (fd == -1) {
error = "could not make fd from handle"; error = "could not make fd from handle";
CloseHandle(fileHandle);
return false; return false;
} }
if (!file->open(fd, QIODevice::ReadOnly, QFile::AutoCloseHandle)) { if (!file->open(fd, QIODevice::ReadOnly, QFile::AutoCloseHandle)) {
error = file->errorString(); error = file->errorString();
_close(fd); // implicitly closes fileHandle
return false; return false;
} }

View file

@ -666,7 +666,21 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
goto error; goto error;
} }
while ((dirent = csync_vio_readdir(ctx, dh))) { while (true) {
// Get the next item in the directory
errno = 0;
dirent = csync_vio_readdir(ctx, dh);
if (!dirent) {
if (errno != 0) {
// Note: Windows vio converts any error into EACCES
qCWarning(lcUpdate, "readdir failed for file in %s - errno %d", uri, errno);
goto error;
}
// Normal case: End of items in directory
break;
}
/* Conversion error */ /* Conversion error */
if (dirent->path.isEmpty() && !dirent->original_path.isEmpty()) { if (dirent->path.isEmpty() && !dirent->original_path.isEmpty()) {
ctx->status_code = CSYNC_STATUS_INVALID_CHARACTERS; ctx->status_code = CSYNC_STATUS_INVALID_CHARACTERS;

View file

@ -156,6 +156,7 @@ std::unique_ptr<csync_file_stat_t> csync_vio_local_readdir(csync_vio_handle_t *d
// might be error, check! // might be error, check!
int dwError = GetLastError(); int dwError = GetLastError();
if (dwError != ERROR_NO_MORE_FILES) { if (dwError != ERROR_NO_MORE_FILES) {
qCWarning(lcCSyncVIOLocal, "FindNextFile error %d", dwError);
errno = EACCES; // no more files is fine. Otherwise EACCESS errno = EACCES; // no more files is fine. Otherwise EACCESS
} }
return nullptr; return nullptr;

View file

@ -53,6 +53,7 @@ set(client_SRCS
folderman.cpp folderman.cpp
folderstatusmodel.cpp folderstatusmodel.cpp
folderstatusdelegate.cpp folderstatusdelegate.cpp
folderstatusview.cpp
folderwatcher.cpp folderwatcher.cpp
folderwizard.cpp folderwizard.cpp
generalsettings.cpp generalsettings.cpp

View file

@ -52,7 +52,7 @@ AccountManager *AccountManager::instance()
bool AccountManager::restore() bool AccountManager::restore()
{ {
auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC)); auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC));
if (settings->status() != QSettings::NoError) { if (settings->status() != QSettings::NoError || !settings->isWritable()) {
qCWarning(lcAccountManager) << "Could not read settings from" << settings->fileName() qCWarning(lcAccountManager) << "Could not read settings from" << settings->fileName()
<< settings->status(); << settings->status();
return false; return false;

View file

@ -117,7 +117,7 @@
</layout> </layout>
</item> </item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QTreeView" name="_folderList"> <widget class="OCC::FolderStatusView" name="_folderList">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding"> <sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch> <horstretch>0</horstretch>
@ -275,6 +275,11 @@
<extends>QToolButton</extends> <extends>QToolButton</extends>
<header>sslbutton.h</header> <header>sslbutton.h</header>
</customwidget> </customwidget>
<customwidget>
<class>OCC::FolderStatusView</class>
<extends>QTreeView</extends>
<header>folderstatusview.h</header>
</customwidget>
</customwidgets> </customwidgets>
<resources/> <resources/>
<connections/> <connections/>

View file

@ -125,7 +125,8 @@ Application::Application(int &argc, char **argv)
setAttribute(Qt::AA_UseHighDpiPixmaps, true); setAttribute(Qt::AA_UseHighDpiPixmaps, true);
auto confDir = ConfigFile().configPath(); auto confDir = ConfigFile().configPath();
if (!QFileInfo(confDir).exists()) { if (confDir.endsWith('/')) confDir.chop(1); // macOS 10.11.x does not like trailing slash for rename/move.
if (!QFileInfo(confDir).isDir()) {
// Migrate from version <= 2.4 // Migrate from version <= 2.4
setApplicationName(_theme->appNameGUI()); setApplicationName(_theme->appNameGUI());
#ifndef QT_WARNING_DISABLE_DEPRECATED // Was added in Qt 5.9 #ifndef QT_WARNING_DISABLE_DEPRECATED // Was added in Qt 5.9
@ -136,6 +137,7 @@ Application::Application(int &argc, char **argv)
// We need to use the deprecated QDesktopServices::storageLocation because of its Qt4 // We need to use the deprecated QDesktopServices::storageLocation because of its Qt4
// behavior of adding "data" to the path // behavior of adding "data" to the path
QString oldDir = QDesktopServices::storageLocation(QDesktopServices::DataLocation); QString oldDir = QDesktopServices::storageLocation(QDesktopServices::DataLocation);
if (oldDir.endsWith('/')) oldDir.chop(1); // macOS 10.11.x does not like trailing slash for rename/move.
QT_WARNING_POP QT_WARNING_POP
setApplicationName(_theme->appName()); setApplicationName(_theme->appName());
if (QFileInfo(oldDir).isDir()) { if (QFileInfo(oldDir).isDir()) {

View file

@ -645,9 +645,13 @@ void Folder::startSync(const QStringList &pathList)
} }
return interval; return interval;
}(); }();
if (_folderWatcher && _folderWatcher->isReliable() && _timeSinceLastFullLocalDiscovery.isValid() bool hasDoneFullLocalDiscovery = _timeSinceLastFullLocalDiscovery.isValid();
&& (fullLocalDiscoveryInterval.count() < 0 bool periodicFullLocalDiscoveryNow =
|| _timeSinceLastFullLocalDiscovery.hasExpired(fullLocalDiscoveryInterval.count()))) { fullLocalDiscoveryInterval.count() >= 0 // negative means we don't require periodic full runs
&& _timeSinceLastFullLocalDiscovery.hasExpired(fullLocalDiscoveryInterval.count());
if (_folderWatcher && _folderWatcher->isReliable()
&& hasDoneFullLocalDiscovery
&& !periodicFullLocalDiscoveryNow) {
qCInfo(lcFolder) << "Allowing local discovery to read from the database"; qCInfo(lcFolder) << "Allowing local discovery to read from the database";
_engine->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, _localDiscoveryPaths); _engine->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, _localDiscoveryPaths);

View file

@ -1242,25 +1242,45 @@ QString FolderMan::trayTooltipStatusString(
return folderMessage; return folderMessage;
} }
QString FolderMan::checkPathValidityForNewFolder(const QString &path, const QUrl &serverUrl, bool forNewDirectory) const static QString checkPathValidityRecursive(const QString &path)
{ {
if (path.isEmpty()) { if (path.isEmpty()) {
return tr("No valid folder selected!"); return FolderMan::tr("No valid folder selected!");
} }
QFileInfo selFile(path); QFileInfo selFile(path);
if (!selFile.exists()) { if (!selFile.exists()) {
return checkPathValidityForNewFolder(selFile.dir().path(), serverUrl, true); return checkPathValidityRecursive(selFile.dir().path());
} }
if (!selFile.isDir()) { if (!selFile.isDir()) {
return tr("The selected path is not a folder!"); return FolderMan::tr("The selected path is not a folder!");
} }
if (!selFile.isWritable()) { if (!selFile.isWritable()) {
return tr("You have no permission to write to the selected folder!"); return FolderMan::tr("You have no permission to write to the selected folder!");
} }
return QString();
}
// QFileInfo::canonicalPath returns an empty string if the file does not exist.
// This function also works with files that does not exist and resolve the symlinks in the
// parent directories.
static QString canonicalPath(const QString &path)
{
QFileInfo selFile(path);
if (!selFile.exists()) {
return canonicalPath(selFile.dir().path()) + '/' + selFile.fileName();
}
return selFile.canonicalFilePath();
}
QString FolderMan::checkPathValidityForNewFolder(const QString &path, const QUrl &serverUrl) const
{
QString recursiveValidity = checkPathValidityRecursive(path);
if (!recursiveValidity.isEmpty())
return recursiveValidity;
// check if the local directory isn't used yet in another ownCloud sync // check if the local directory isn't used yet in another ownCloud sync
Qt::CaseSensitivity cs = Qt::CaseSensitive; Qt::CaseSensitivity cs = Qt::CaseSensitive;
@ -1268,57 +1288,28 @@ QString FolderMan::checkPathValidityForNewFolder(const QString &path, const QUrl
cs = Qt::CaseInsensitive; cs = Qt::CaseInsensitive;
} }
const QString userDir = QDir::cleanPath(canonicalPath(path)) + '/';
for (auto i = _folderMap.constBegin(); i != _folderMap.constEnd(); ++i) { for (auto i = _folderMap.constBegin(); i != _folderMap.constEnd(); ++i) {
Folder *f = static_cast<Folder *>(i.value()); Folder *f = static_cast<Folder *>(i.value());
QString folderDir = QDir(f->path()).canonicalPath(); QString folderDir = QDir::cleanPath(canonicalPath(f->path())) + '/';
if (folderDir.isEmpty()) {
continue;
}
if (!folderDir.endsWith(QLatin1Char('/'), cs))
folderDir.append(QLatin1Char('/'));
const QString folderDirClean = QDir::cleanPath(folderDir) + '/'; bool differentPaths = QString::compare(folderDir, userDir, cs) != 0;
const QString userDirClean = QDir::cleanPath(path) + '/'; if (differentPaths && folderDir.startsWith(userDir, cs)) {
// folderDir follows sym links, path not.
bool differentPathes = !Utility::fileNamesEqual(QDir::cleanPath(folderDir), QDir::cleanPath(path));
if (!forNewDirectory && differentPathes && folderDirClean.startsWith(userDirClean, cs)) {
return tr("The local folder %1 already contains a folder used in a folder sync connection. " return tr("The local folder %1 already contains a folder used in a folder sync connection. "
"Please pick another one!") "Please pick another one!")
.arg(QDir::toNativeSeparators(path)); .arg(QDir::toNativeSeparators(path));
} }
// QDir::cleanPath keeps links if (differentPaths && userDir.startsWith(folderDir, cs)) {
// canonicalPath() remove symlinks and uses the symlink targets.
QString absCleanUserFolder = QDir::cleanPath(QDir(path).canonicalPath()) + '/';
if ((forNewDirectory || differentPathes) && userDirClean.startsWith(folderDirClean, cs)) {
return tr("The local folder %1 is already contained in a folder used in a folder sync connection. " return tr("The local folder %1 is already contained in a folder used in a folder sync connection. "
"Please pick another one!") "Please pick another one!")
.arg(QDir::toNativeSeparators(path)); .arg(QDir::toNativeSeparators(path));
} }
// both follow symlinks.
bool cleanUserEqualsCleanFolder = Utility::fileNamesEqual(absCleanUserFolder, folderDirClean);
if (differentPathes && absCleanUserFolder.startsWith(folderDirClean, cs) && !cleanUserEqualsCleanFolder) {
return tr("The local folder %1 is a symbolic link. "
"The link target is already contained in a folder used in a folder sync connection. "
"Please pick another one!")
.arg(QDir::toNativeSeparators(path));
}
if (differentPathes && folderDirClean.startsWith(absCleanUserFolder, cs) && !cleanUserEqualsCleanFolder && !forNewDirectory) {
return tr("The local folder %1 contains a symbolic link. "
"The link target contains an already synced folder. "
"Please pick another one!")
.arg(QDir::toNativeSeparators(path));
}
// if both pathes are equal, the server url needs to be different // if both pathes are equal, the server url needs to be different
// otherwise it would mean that a new connection from the same local folder // otherwise it would mean that a new connection from the same local folder
// to the same account is added which is not wanted. The account must differ. // to the same account is added which is not wanted. The account must differ.
if (serverUrl.isValid() && Utility::fileNamesEqual(absCleanUserFolder, folderDir)) { if (serverUrl.isValid() && !differentPaths) {
QUrl folderUrl = f->accountState()->account()->url(); QUrl folderUrl = f->accountState()->account()->url();
QString user = f->accountState()->account()->credentials()->user(); QString user = f->accountState()->account()->credentials()->user();
folderUrl.setUserName(user); folderUrl.setUserName(user);

View file

@ -127,11 +127,9 @@ public:
* *
* Note that different accounts are allowed to sync to the same folder. * Note that different accounts are allowed to sync to the same folder.
* *
* \a forNewDirectory is internal and is used for recursion.
*
* @returns an empty string if it is allowed, or an error if it is not allowed * @returns an empty string if it is allowed, or an error if it is not allowed
*/ */
QString checkPathValidityForNewFolder(const QString &path, const QUrl &serverUrl = QUrl(), bool forNewDirectory = false) const; QString checkPathValidityForNewFolder(const QString &path, const QUrl &serverUrl = QUrl()) const;
/** /**
* Attempts to find a non-existing, acceptable path for creating a new sync folder. * Attempts to find a non-existing, acceptable path for creating a new sync folder.

View file

@ -16,6 +16,7 @@
#include "folderstatusdelegate.h" #include "folderstatusdelegate.h"
#include "folderstatusmodel.h" #include "folderstatusmodel.h"
#include "folderstatusview.h"
#include "folderman.h" #include "folderman.h"
#include "accountstate.h" #include "accountstate.h"
#include <theme.h> #include <theme.h>
@ -24,6 +25,7 @@
#include <QFileIconProvider> #include <QFileIconProvider>
#include <QPainter> #include <QPainter>
#include <QApplication> #include <QApplication>
#include <QMouseEvent>
inline static QFont makeAliasFont(const QFont &normalFont) inline static QFont makeAliasFont(const QFont &normalFont)
{ {
@ -110,6 +112,10 @@ int FolderStatusDelegate::rootFolderHeightWithoutErrors(const QFontMetrics &fm,
void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const const QModelIndex &index) const
{ {
if (index.data(AddButton).toBool()) {
const_cast<QStyleOptionViewItem &>(option).showDecorationSelected = false;
}
QStyledItemDelegate::paint(painter, option, index); QStyledItemDelegate::paint(painter, option, index);
auto textAlign = Qt::AlignLeft; auto textAlign = Qt::AlignLeft;
@ -129,15 +135,15 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
int margin = subFm.height() / 4; int margin = subFm.height() / 4;
if (index.data(AddButton).toBool()) { if (index.data(AddButton).toBool()) {
QSize hint = sizeHint(option, index);
QStyleOptionButton opt; QStyleOptionButton opt;
static_cast<QStyleOption &>(opt) = option; static_cast<QStyleOption &>(opt) = option;
opt.state &= ~QStyle::State_Selected; if (opt.state & QStyle::State_Enabled && opt.state & QStyle::State_MouseOver && index == _pressedIndex) {
opt.state |= QStyle::State_Raised; opt.state |= QStyle::State_Sunken;
} else {
opt.state |= QStyle::State_Raised;
}
opt.text = addFolderText(); opt.text = addFolderText();
opt.rect.setWidth(qMin(opt.rect.width(), hint.width())); opt.rect = addButtonRect(option.rect, option.direction);
opt.rect.adjust(0, aliasMargin, 0, -aliasMargin);
opt.rect = QStyle::visualRect(option.direction, option.rect, opt.rect);
painter->save(); painter->save();
painter->setFont(qApp->font("QPushButton")); painter->setFont(qApp->font("QPushButton"));
QApplication::style()->drawControl(QStyle::CE_PushButton, &opt, painter, option.widget); QApplication::style()->drawControl(QStyle::CE_PushButton, &opt, painter, option.widget);
@ -352,6 +358,27 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
bool FolderStatusDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, bool FolderStatusDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
const QStyleOptionViewItem &option, const QModelIndex &index) const QStyleOptionViewItem &option, const QModelIndex &index)
{ {
switch (event->type()) {
case QEvent::MouseButtonPress:
case QEvent::MouseMove:
if (const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(option.widget)) {
QMouseEvent *me = static_cast<QMouseEvent *>(event);
QModelIndex index;
if (me->buttons()) {
index = view->indexAt(me->pos());
}
if (_pressedIndex != index) {
_pressedIndex = index;
view->viewport()->update();
}
}
break;
case QEvent::MouseButtonRelease:
_pressedIndex = QModelIndex();
break;
default:
break;
}
return QStyledItemDelegate::editorEvent(event, model, option, index); return QStyledItemDelegate::editorEvent(event, model, option, index);
} }
@ -375,6 +402,16 @@ QRect FolderStatusDelegate::optionsButtonRect(QRect within, Qt::LayoutDirection
return QStyle::visualRect(direction, within, r); return QStyle::visualRect(direction, within, r);
} }
QRect FolderStatusDelegate::addButtonRect(QRect within, Qt::LayoutDirection direction)
{
QFontMetrics fm(qApp->font("QPushButton"));
QStyleOptionButton opt;
opt.text = addFolderText();
QSize size = QApplication::style()->sizeFromContents(QStyle::CT_PushButton, &opt, fm.size(Qt::TextSingleLine, opt.text)).expandedTo(QApplication::globalStrut());
QRect r(QPoint(within.left(), within.top() + within.height() / 2 - size.height() / 2), size);
return QStyle::visualRect(direction, within, r);
}
QRect FolderStatusDelegate::errorsListRect(QRect within) QRect FolderStatusDelegate::errorsListRect(QRect within)
{ {
QFont font = QFont(); QFont font = QFont();

View file

@ -57,11 +57,13 @@ public:
* return the position of the option button within the item * return the position of the option button within the item
*/ */
static QRect optionsButtonRect(QRect within, Qt::LayoutDirection direction); static QRect optionsButtonRect(QRect within, Qt::LayoutDirection direction);
static QRect addButtonRect(QRect within, Qt::LayoutDirection direction);
static QRect errorsListRect(QRect within); static QRect errorsListRect(QRect within);
static int rootFolderHeightWithoutErrors(const QFontMetrics &fm, const QFontMetrics &aliasFm); static int rootFolderHeightWithoutErrors(const QFontMetrics &fm, const QFontMetrics &aliasFm);
private: private:
static QString addFolderText(); static QString addFolderText();
QPersistentModelIndex _pressedIndex;
}; };
} // namespace OCC } // namespace OCC

View file

@ -0,0 +1,42 @@
/*
* Copyright (C) 2018 by J-P Nurmi <jpnurmi@gmail.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 "folderstatusview.h"
#include "folderstatusdelegate.h"
namespace OCC {
FolderStatusView::FolderStatusView(QWidget *parent) : QTreeView(parent)
{
}
QModelIndex FolderStatusView::indexAt(const QPoint &point) const
{
QModelIndex index = QTreeView::indexAt(point);
if (index.data(FolderStatusDelegate::AddButton).toBool() && !visualRect(index).contains(point)) {
return QModelIndex();
}
return index;
}
QRect FolderStatusView::visualRect(const QModelIndex &index) const
{
QRect rect = QTreeView::visualRect(index);
if (index.data(FolderStatusDelegate::AddButton).toBool()) {
return FolderStatusDelegate::addButtonRect(rect, layoutDirection());
}
return rect;
}
} // namespace OCC

View file

@ -0,0 +1,39 @@
/*
* Copyright (C) 2018 by J-P Nurmi <jpnurmi@gmail.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.
*/
#ifndef FOLDERSTATUSVIEW_H
#define FOLDERSTATUSVIEW_H
#include <QTreeView>
namespace OCC {
/**
* @brief The FolderStatusView class
* @ingroup gui
*/
class FolderStatusView : public QTreeView
{
Q_OBJECT
public:
explicit FolderStatusView(QWidget *parent = nullptr);
QModelIndex indexAt(const QPoint &point) const override;
QRect visualRect(const QModelIndex &index) const override;
};
} // namespace OCC
#endif // FOLDERSTATUSVIEW_H

View file

@ -66,8 +66,10 @@ FolderWizardLocalPath::FolderWizardLocalPath(const AccountPtr &account)
connect(_ui.localFolderChooseBtn, &QAbstractButton::clicked, this, &FolderWizardLocalPath::slotChooseLocalFolder); connect(_ui.localFolderChooseBtn, &QAbstractButton::clicked, this, &FolderWizardLocalPath::slotChooseLocalFolder);
_ui.localFolderChooseBtn->setToolTip(tr("Click to select a local folder to sync.")); _ui.localFolderChooseBtn->setToolTip(tr("Click to select a local folder to sync."));
QUrl serverUrl = _account->url();
serverUrl.setUserName(_account->credentials()->user());
QString defaultPath = QDir::homePath() + QLatin1Char('/') + Theme::instance()->appName(); QString defaultPath = QDir::homePath() + QLatin1Char('/') + Theme::instance()->appName();
defaultPath = FolderMan::instance()->findGoodPathForNewSyncFolder(defaultPath, account->url()); defaultPath = FolderMan::instance()->findGoodPathForNewSyncFolder(defaultPath, serverUrl);
_ui.localFolderLineEdit->setText(QDir::toNativeSeparators(defaultPath)); _ui.localFolderLineEdit->setText(QDir::toNativeSeparators(defaultPath));
_ui.localFolderLineEdit->setToolTip(tr("Enter the path to the local folder.")); _ui.localFolderLineEdit->setToolTip(tr("Enter the path to the local folder."));

View file

@ -631,7 +631,7 @@ void ShareUserLine::displayPermissions()
// edit is independent of reshare // edit is independent of reshare
if (perm & SharePermissionShare) if (perm & SharePermissionShare)
_permissionReshare->setChecked(Qt::Checked); _permissionReshare->setChecked(true);
if(!_isFile){ if(!_isFile){
_permissionCreate->setChecked(perm & SharePermissionCreate); _permissionCreate->setChecked(perm & SharePermissionCreate);

View file

@ -13,6 +13,8 @@
*/ */
#include <QVariant> #include <QVariant>
#include <QMenu>
#include <QClipboard>
#include "wizard/owncloudoauthcredspage.h" #include "wizard/owncloudoauthcredspage.h"
#include "theme.h" #include "theme.h"
@ -48,6 +50,16 @@ OwncloudOAuthCredsPage::OwncloudOAuthCredsPage()
if (_asyncAuth) if (_asyncAuth)
_asyncAuth->openBrowser(); _asyncAuth->openBrowser();
}); });
_ui.openLinkButton->setContextMenuPolicy(Qt::CustomContextMenu);
QObject::connect(_ui.openLinkButton, &QWidget::customContextMenuRequested, [this](const QPoint &pos) {
auto menu = new QMenu(_ui.openLinkButton);
menu->addAction(tr("Copy link to clipboard"), this, [this] {
if (_asyncAuth)
QApplication::clipboard()->setText(_asyncAuth->authorisationLink().toString(QUrl::FullyEncoded));
});
menu->setAttribute(Qt::WA_DeleteOnClose);
menu->popup(_ui.openLinkButton->mapToGlobal(pos));
});
} }
void OwncloudOAuthCredsPage::initializePage() void OwncloudOAuthCredsPage::initializePage()

View file

@ -24,7 +24,6 @@
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QPropertyAnimation> #include <QPropertyAnimation>
#include <QGraphicsPixmapItem> #include <QGraphicsPixmapItem>
#include <QtSvg/QSvgRenderer>
#include "QProgressIndicator.h" #include "QProgressIndicator.h"
@ -278,12 +277,19 @@ QString OwncloudSetupPage::url() const
bool OwncloudSetupPage::validatePage() bool OwncloudSetupPage::validatePage()
{ {
if (!_authTypeKnown) { if (!_authTypeKnown) {
QString u = url();
QUrl qurl(u);
if (!qurl.isValid() || qurl.host().isEmpty()) {
setErrorString(tr("Invalid URL"), false);
return false;
}
setErrorString(QString(), false); setErrorString(QString(), false);
_checking = true; _checking = true;
startSpinner(); startSpinner();
emit completeChanged(); emit completeChanged();
emit determineAuthType(url()); emit determineAuthType(u);
return false; return false;
} else { } else {
// connecting is running // connecting is running

View file

@ -41,6 +41,13 @@ class WebEnginePage : public QWebEnginePage {
public: public:
WebEnginePage(QWebEngineProfile *profile, QObject* parent = nullptr); WebEnginePage(QWebEngineProfile *profile, QObject* parent = nullptr);
QWebEnginePage * createWindow(QWebEnginePage::WebWindowType type) override; QWebEnginePage * createWindow(QWebEnginePage::WebWindowType type) override;
void setUrl(const QUrl &url);
protected:
bool certificateError(const QWebEngineCertificateError &certificateError) override;
private:
QUrl _rootUrl;
}; };
// We need a separate class here, since we cannot simply return the same WebEnginePage object // We need a separate class here, since we cannot simply return the same WebEnginePage object
@ -146,6 +153,19 @@ QWebEnginePage * WebEnginePage::createWindow(QWebEnginePage::WebWindowType type)
return view; return view;
} }
void WebEnginePage::setUrl(const QUrl &url) {
QWebEnginePage::setUrl(url);
_rootUrl = url;
}
bool WebEnginePage::certificateError(const QWebEngineCertificateError &certificateError) {
if (certificateError.error() == QWebEngineCertificateError::CertificateAuthorityInvalid) {
return certificateError.url().host() == _rootUrl.host();
}
return false;
}
ExternalWebEnginePage::ExternalWebEnginePage(QWebEngineProfile *profile, QObject* parent) : QWebEnginePage(profile, parent) { ExternalWebEnginePage::ExternalWebEnginePage(QWebEngineProfile *profile, QObject* parent) : QWebEnginePage(profile, parent) {
} }

View file

@ -379,11 +379,12 @@ bool HttpCredentials::refreshAccessToken()
QJsonParseError jsonParseError; QJsonParseError jsonParseError;
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object(); QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
QString accessToken = json["access_token"].toString(); QString accessToken = json["access_token"].toString();
if (reply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError || json.isEmpty()) { if (jsonParseError.error != QJsonParseError::NoError || json.isEmpty()) {
// Network error maybe? // Invalid or empty JSON: Network error maybe?
qCWarning(lcHttpCredentials) << "Error while refreshing the token" << reply->errorString() << jsonData << jsonParseError.errorString(); qCWarning(lcHttpCredentials) << "Error while refreshing the token" << reply->errorString() << jsonData << jsonParseError.errorString();
} else if (accessToken.isEmpty()) { } else if (accessToken.isEmpty()) {
// The token is no longer valid. // If the json was valid, but the reply did not contain an access token, the token
// is considered expired. (Usually the HTTP reply code is 400)
qCDebug(lcHttpCredentials) << "Expired refresh token. Logging out"; qCDebug(lcHttpCredentials) << "Expired refresh token. Logging out";
_refreshToken.clear(); _refreshToken.clear();
} else { } else {

View file

@ -381,13 +381,11 @@ void DiscoverySingleDirectoryJob::directoryListingIteratedSlot(QString file, con
std::unique_ptr<csync_file_stat_t> file_stat(new csync_file_stat_t); std::unique_ptr<csync_file_stat_t> file_stat(new csync_file_stat_t);
file_stat->path = file.toUtf8(); file_stat->path = file.toUtf8();
file_stat->size = -1; file_stat->size = -1;
file_stat->modtime = -1;
propertyMapToFileStat(map, file_stat.get()); propertyMapToFileStat(map, file_stat.get());
if (file_stat->type == ItemTypeDirectory) if (file_stat->type == ItemTypeDirectory)
file_stat->size = 0; file_stat->size = 0;
if (file_stat->type == ItemTypeSkip if (file_stat->type == ItemTypeSkip
|| file_stat->size == -1 || file_stat->size == -1
|| file_stat->modtime == -1
|| file_stat->remotePerm.isNull() || file_stat->remotePerm.isNull()
|| file_stat->etag.isEmpty() || file_stat->etag.isEmpty()
|| file_stat->file_id.isEmpty()) { || file_stat->file_id.isEmpty()) {

View file

@ -338,7 +338,8 @@ QString Theme::about() const
.arg("http://" MIRALL_STRINGIFY(APPLICATION_DOMAIN)) .arg("http://" MIRALL_STRINGIFY(APPLICATION_DOMAIN))
.arg(MIRALL_STRINGIFY(APPLICATION_DOMAIN)); .arg(MIRALL_STRINGIFY(APPLICATION_DOMAIN));
devString += tr("<p>This release was supplied by the Nextcloud GmbH</p>"); devString += tr("<p>This release was supplied by %1</p>")
.arg(APPLICATION_VENDOR);
devString += gitSHA1(); devString += gitSHA1();

View file

@ -146,6 +146,12 @@ private slots:
// Invalid paths // Invalid paths
QVERIFY(!folderman->checkPathValidityForNewFolder("").isNull()); QVERIFY(!folderman->checkPathValidityForNewFolder("").isNull());
// REMOVE ownCloud2 from the filesystem, but keep a folder sync'ed to it.
QDir(dirPath + "/ownCloud2/").removeRecursively();
QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/ownCloud2/blublu").isNull());
QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/ownCloud2/sub/subsub/sub").isNull());
} }
void testFindGoodPathForNewSyncFolder() void testFindGoodPathForNewSyncFolder()
@ -169,6 +175,7 @@ private slots:
HttpCredentialsTest *cred = new HttpCredentialsTest("testuser", "secret"); HttpCredentialsTest *cred = new HttpCredentialsTest("testuser", "secret");
account->setCredentials(cred); account->setCredentials(cred);
account->setUrl( url ); account->setUrl( url );
url.setUserName(cred->user());
AccountStatePtr newAccountState(new AccountState(account)); AccountStatePtr newAccountState(new AccountState(account));
FolderMan *folderman = FolderMan::instance(); FolderMan *folderman = FolderMan::instance();
@ -190,6 +197,14 @@ private slots:
QString(dirPath + "/ownCloud2/bar")); QString(dirPath + "/ownCloud2/bar"));
QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath + "/sub", url), QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath + "/sub", url),
QString(dirPath + "/sub2")); QString(dirPath + "/sub2"));
// REMOVE ownCloud2 from the filesystem, but keep a folder sync'ed to it.
// We should still not suggest this folder as a new folder.
QDir(dirPath + "/ownCloud2/").removeRecursively();
QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath + "/ownCloud", url),
QString(dirPath + "/ownCloud3"));
QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath + "/ownCloud2", url),
QString(dirPath + "/ownCloud22"));
} }
}; };

View file

@ -22,7 +22,7 @@ signals:
void hooked(const QUrl &); void hooked(const QUrl &);
public: public:
DesktopServiceHook() { QDesktopServices::setUrlHandler("oauthtest", this, "hooked"); } DesktopServiceHook() { QDesktopServices::setUrlHandler("oauthtest", this, "hooked"); }
} desktopServiceHook; };
static const QUrl sOAuthTestServer("oauthtest://someserver/owncloud"); static const QUrl sOAuthTestServer("oauthtest://someserver/owncloud");
@ -90,6 +90,7 @@ public:
class OAuthTestCase : public QObject class OAuthTestCase : public QObject
{ {
Q_OBJECT Q_OBJECT
DesktopServiceHook desktopServiceHook;
public: public:
enum State { StartState, BrowserOpened, TokenAsked, CustomState } state = StartState; enum State { StartState, BrowserOpened, TokenAsked, CustomState } state = StartState;
Q_ENUM(State); Q_ENUM(State);

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff