2014-08-11 19:47:16 +04:00
|
|
|
/*
|
|
|
|
* Copyright (C) by Olivier Goffart <ogoffart@woboq.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 "discoveryphase.h"
|
2017-02-07 15:52:15 +03:00
|
|
|
|
|
|
|
#include "account.h"
|
2017-09-01 19:11:43 +03:00
|
|
|
#include "common/asserts.h"
|
2017-11-20 10:18:52 +03:00
|
|
|
#include "common/checksums.h"
|
2017-02-07 15:52:15 +03:00
|
|
|
|
2014-08-11 20:41:42 +04:00
|
|
|
#include <csync_private.h>
|
2016-10-10 17:55:31 +03:00
|
|
|
#include <csync_rename.h>
|
2017-09-26 13:28:12 +03:00
|
|
|
#include <csync_exclude.h>
|
2014-08-11 20:41:42 +04:00
|
|
|
|
2017-05-09 15:24:11 +03:00
|
|
|
#include <QLoggingCategory>
|
2014-10-17 14:02:26 +04:00
|
|
|
#include <QUrl>
|
2014-12-02 14:25:51 +03:00
|
|
|
#include <QFileInfo>
|
2016-11-29 12:36:37 +03:00
|
|
|
#include <cstring>
|
|
|
|
|
2014-10-17 14:02:26 +04:00
|
|
|
|
2014-11-10 00:34:07 +03:00
|
|
|
namespace OCC {
|
2014-08-18 16:45:48 +04:00
|
|
|
|
2017-12-28 22:33:10 +03:00
|
|
|
Q_LOGGING_CATEGORY(lcDiscovery, "nextcloud.sync.discovery", QtInfoMsg)
|
2015-05-21 15:50:30 +03:00
|
|
|
|
2015-06-03 16:20:42 +03:00
|
|
|
/* Given a sorted list of paths ending with '/', return whether or not the given path is within one of the paths of the list*/
|
2015-05-21 15:50:30 +03:00
|
|
|
static bool findPathInList(const QStringList &list, const QString &path)
|
|
|
|
{
|
|
|
|
Q_ASSERT(std::is_sorted(list.begin(), list.end()));
|
2015-08-05 17:11:59 +03:00
|
|
|
|
|
|
|
if (list.size() == 1 && list.first() == QLatin1String("/")) {
|
|
|
|
// Special case for the case "/" is there, it matches everything
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-05-21 15:50:30 +03:00
|
|
|
QString pathSlash = path + QLatin1Char('/');
|
|
|
|
|
|
|
|
// Since the list is sorted, we can do a binary search.
|
|
|
|
// If the path is a prefix of another item or right after in the lexical order.
|
|
|
|
auto it = std::lower_bound(list.begin(), list.end(), pathSlash);
|
|
|
|
|
|
|
|
if (it != list.end() && *it == pathSlash) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (it == list.begin()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
--it;
|
|
|
|
Q_ASSERT(it->endsWith(QLatin1Char('/'))); // Folder::setSelectiveSyncBlackList makes sure of that
|
|
|
|
return pathSlash.startsWith(*it);
|
|
|
|
}
|
|
|
|
|
2018-07-10 15:50:32 +03:00
|
|
|
bool DiscoveryPhase::isInSelectiveSyncBlackList(const QString &path) const
|
2014-08-11 20:41:42 +04:00
|
|
|
{
|
2014-08-15 14:29:10 +04:00
|
|
|
if (_selectiveSyncBlackList.isEmpty()) {
|
|
|
|
// If there is no black list, everything is allowed
|
|
|
|
return false;
|
2014-08-11 20:41:42 +04:00
|
|
|
}
|
|
|
|
|
2015-05-21 15:50:30 +03:00
|
|
|
// Block if it is in the black list
|
2018-07-10 15:50:32 +03:00
|
|
|
if (findPathInList(_selectiveSyncBlackList, path)) {
|
2016-10-10 17:55:31 +03:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-07-10 15:50:32 +03:00
|
|
|
/** FIXME
|
2016-10-10 17:55:31 +03:00
|
|
|
// Also try to adjust the path if there was renames
|
|
|
|
if (csync_rename_count(_csync_ctx)) {
|
2017-12-05 15:42:55 +03:00
|
|
|
QByteArray adjusted = csync_rename_adjust_parent_path_source(_csync_ctx, path);
|
2017-08-23 20:16:12 +03:00
|
|
|
if (adjusted != path) {
|
|
|
|
return findPathInList(_selectiveSyncBlackList, QString::fromUtf8(adjusted));
|
2016-10-10 17:55:31 +03:00
|
|
|
}
|
|
|
|
}
|
2018-07-10 15:50:32 +03:00
|
|
|
*/
|
2014-08-11 20:41:42 +04:00
|
|
|
|
2016-10-10 17:55:31 +03:00
|
|
|
return false;
|
2015-05-21 15:50:30 +03:00
|
|
|
}
|
2014-08-11 20:41:42 +04:00
|
|
|
|
2018-07-10 15:50:32 +03:00
|
|
|
bool DiscoveryPhase::checkSelectiveSyncNewFolder(const QString &path, RemotePermissions remotePerm)
|
2015-05-21 15:50:30 +03:00
|
|
|
{
|
2017-09-19 11:53:51 +03:00
|
|
|
if (_syncOptions._confirmExternalStorage
|
|
|
|
&& remotePerm.hasPermission(RemotePermissions::IsMounted)) {
|
|
|
|
// external storage.
|
2017-01-24 12:16:10 +03:00
|
|
|
|
2017-01-24 15:25:03 +03:00
|
|
|
/* Note: DiscoverySingleDirectoryJob::directoryListingIteratedSlot make sure that only the
|
|
|
|
* root of a mounted storage has 'M', all sub entries have 'm' */
|
|
|
|
|
2017-01-24 12:16:10 +03:00
|
|
|
// Only allow it if the white list contains exactly this path (not parents)
|
|
|
|
// We want to ask confirmation for external storage even if the parents where selected
|
|
|
|
if (_selectiveSyncWhiteList.contains(path + QLatin1Char('/'))) {
|
|
|
|
return false;
|
2016-11-29 12:36:37 +03:00
|
|
|
}
|
2017-01-24 12:16:10 +03:00
|
|
|
|
2017-01-26 11:03:01 +03:00
|
|
|
emit newBigFolder(path, true);
|
2017-01-24 12:16:10 +03:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-05-17 11:55:42 +03:00
|
|
|
// If this path or the parent is in the white list, then we do not block this file
|
2017-01-24 12:16:10 +03:00
|
|
|
if (findPathInList(_selectiveSyncWhiteList, path)) {
|
|
|
|
return false;
|
2016-11-29 12:36:37 +03:00
|
|
|
}
|
|
|
|
|
2017-01-24 12:16:10 +03:00
|
|
|
auto limit = _syncOptions._newBigFolderSizeLimit;
|
|
|
|
if (limit < 0) {
|
2015-05-26 15:41:01 +03:00
|
|
|
// no limit, everything is allowed;
|
2014-08-11 20:41:42 +04:00
|
|
|
return false;
|
|
|
|
}
|
2015-05-26 15:41:01 +03:00
|
|
|
|
2015-09-07 09:51:22 +03:00
|
|
|
// Go in the main thread to do a PROPFIND to know the size of this folder
|
2015-05-21 15:50:30 +03:00
|
|
|
qint64 result = -1;
|
|
|
|
|
2018-07-10 15:50:32 +03:00
|
|
|
/* FIXME TOTO
|
2015-05-21 15:50:30 +03:00
|
|
|
{
|
|
|
|
QMutexLocker locker(&_vioMutex);
|
|
|
|
emit doGetSizeSignal(path, &result);
|
|
|
|
_vioWaitCondition.wait(&_vioMutex);
|
2018-07-10 15:50:32 +03:00
|
|
|
}*/
|
2015-05-21 15:50:30 +03:00
|
|
|
|
2015-08-04 16:43:23 +03:00
|
|
|
if (result >= limit) {
|
2015-05-21 15:50:30 +03:00
|
|
|
// we tell the UI there is a new folder
|
2017-01-26 11:03:01 +03:00
|
|
|
emit newBigFolder(path, false);
|
2014-08-11 20:41:42 +04:00
|
|
|
return true;
|
2015-05-21 15:50:30 +03:00
|
|
|
} else {
|
2015-06-03 16:20:42 +03:00
|
|
|
// it is not too big, put it in the white list (so we will not do more query for the children)
|
|
|
|
// and and do not block.
|
2015-05-21 15:50:30 +03:00
|
|
|
auto p = path;
|
2017-05-17 11:55:42 +03:00
|
|
|
if (!p.endsWith(QLatin1Char('/'))) {
|
|
|
|
p += QLatin1Char('/');
|
|
|
|
}
|
2015-05-21 15:50:30 +03:00
|
|
|
_selectiveSyncWhiteList.insert(std::upper_bound(_selectiveSyncWhiteList.begin(),
|
2017-05-17 11:55:42 +03:00
|
|
|
_selectiveSyncWhiteList.end(), p),
|
|
|
|
p);
|
2015-05-21 15:50:30 +03:00
|
|
|
|
|
|
|
return false;
|
2014-08-11 20:41:42 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-10 15:50:32 +03:00
|
|
|
/* FIXME (used to be called every time we were doing a propfind)
|
2017-05-17 11:55:42 +03:00
|
|
|
void DiscoveryJob::update_job_update_callback(bool local,
|
|
|
|
const char *dirUrl,
|
|
|
|
void *userdata)
|
2014-08-18 16:44:43 +04:00
|
|
|
{
|
2020-05-18 21:54:23 +03:00
|
|
|
auto *updateJob = static_cast<DiscoveryJob *>(userdata);
|
2014-08-18 16:44:43 +04:00
|
|
|
if (updateJob) {
|
|
|
|
// Don't wanna overload the UI
|
2020-08-18 18:41:02 +03:00
|
|
|
if (!updateJob->_lastUpdateProgressCallbackCall.isValid()
|
|
|
|
|| updateJob->_lastUpdateProgressCallbackCall.elapsed() >= 200) {
|
2017-05-29 11:48:21 +03:00
|
|
|
updateJob->_lastUpdateProgressCallbackCall.start();
|
2020-08-18 18:41:02 +03:00
|
|
|
} else {
|
|
|
|
return;
|
2014-08-18 16:44:43 +04:00
|
|
|
}
|
|
|
|
|
2015-03-03 18:00:45 +03:00
|
|
|
QByteArray pPath(dirUrl);
|
|
|
|
int indx = pPath.lastIndexOf('/');
|
2017-05-17 11:55:42 +03:00
|
|
|
if (indx > -1) {
|
|
|
|
const QString path = QUrl::fromPercentEncoding(pPath.mid(indx + 1));
|
2015-03-03 18:00:45 +03:00
|
|
|
emit updateJob->folderDiscovered(local, path);
|
|
|
|
}
|
2014-08-18 16:44:43 +04:00
|
|
|
}
|
2018-07-10 15:50:32 +03:00
|
|
|
}*/
|
2014-08-11 19:47:16 +04:00
|
|
|
|
2015-04-23 17:15:13 +03:00
|
|
|
// Only use for error cases! It will always set an error errno
|
2017-05-17 11:55:42 +03:00
|
|
|
int get_errno_from_http_errcode(int err, const QString &reason)
|
|
|
|
{
|
2015-04-23 17:15:13 +03:00
|
|
|
int new_errno = EIO;
|
2014-12-02 14:25:51 +03:00
|
|
|
|
2017-05-17 11:55:42 +03:00
|
|
|
switch (err) {
|
|
|
|
case 401: /* Unauthorized */
|
|
|
|
case 402: /* Payment Required */
|
|
|
|
case 407: /* Proxy Authentication Required */
|
2014-12-02 14:25:51 +03:00
|
|
|
case 405:
|
|
|
|
new_errno = EPERM;
|
|
|
|
break;
|
2017-05-17 11:55:42 +03:00
|
|
|
case 301: /* Moved Permanently */
|
|
|
|
case 303: /* See Other */
|
|
|
|
case 404: /* Not Found */
|
|
|
|
case 410: /* Gone */
|
2014-12-02 14:25:51 +03:00
|
|
|
new_errno = ENOENT;
|
|
|
|
break;
|
2017-05-17 11:55:42 +03:00
|
|
|
case 408: /* Request Timeout */
|
|
|
|
case 504: /* Gateway Timeout */
|
2014-12-02 14:25:51 +03:00
|
|
|
new_errno = EAGAIN;
|
|
|
|
break;
|
2017-05-17 11:55:42 +03:00
|
|
|
case 423: /* Locked */
|
2014-12-02 14:25:51 +03:00
|
|
|
new_errno = EACCES;
|
|
|
|
break;
|
2017-05-17 11:55:42 +03:00
|
|
|
case 403: /* Forbidden */
|
2015-10-12 18:06:49 +03:00
|
|
|
new_errno = ERRNO_FORBIDDEN;
|
|
|
|
break;
|
2017-05-17 11:55:42 +03:00
|
|
|
case 400: /* Bad Request */
|
|
|
|
case 409: /* Conflict */
|
|
|
|
case 411: /* Length Required */
|
|
|
|
case 412: /* Precondition Failed */
|
|
|
|
case 414: /* Request-URI Too Long */
|
|
|
|
case 415: /* Unsupported Media Type */
|
|
|
|
case 424: /* Failed Dependency */
|
|
|
|
case 501: /* Not Implemented */
|
2014-12-02 14:25:51 +03:00
|
|
|
new_errno = EINVAL;
|
|
|
|
break;
|
2017-05-17 11:55:42 +03:00
|
|
|
case 507: /* Insufficient Storage */
|
2014-12-02 14:25:51 +03:00
|
|
|
new_errno = ENOSPC;
|
|
|
|
break;
|
2017-05-17 11:55:42 +03:00
|
|
|
case 206: /* Partial Content */
|
|
|
|
case 300: /* Multiple Choices */
|
|
|
|
case 302: /* Found */
|
|
|
|
case 305: /* Use Proxy */
|
|
|
|
case 306: /* (Unused) */
|
|
|
|
case 307: /* Temporary Redirect */
|
|
|
|
case 406: /* Not Acceptable */
|
|
|
|
case 416: /* Requested Range Not Satisfiable */
|
|
|
|
case 417: /* Expectation Failed */
|
|
|
|
case 422: /* Unprocessable Entity */
|
|
|
|
case 500: /* Internal Server Error */
|
|
|
|
case 502: /* Bad Gateway */
|
|
|
|
case 505: /* HTTP Version Not Supported */
|
2014-12-02 14:25:51 +03:00
|
|
|
new_errno = EIO;
|
|
|
|
break;
|
2017-05-17 11:55:42 +03:00
|
|
|
case 503: /* Service Unavailable */
|
2016-10-06 15:45:04 +03:00
|
|
|
// https://github.com/owncloud/core/pull/26145/files
|
|
|
|
if (reason == "Storage not available" || reason == "Storage is temporarily not available") {
|
2015-02-25 10:57:39 +03:00
|
|
|
new_errno = ERRNO_STORAGE_UNAVAILABLE;
|
|
|
|
} else {
|
|
|
|
new_errno = ERRNO_SERVICE_UNAVAILABLE;
|
|
|
|
}
|
2014-12-02 14:25:51 +03:00
|
|
|
break;
|
2017-05-17 11:55:42 +03:00
|
|
|
case 413: /* Request Entity too Large */
|
2014-12-02 14:25:51 +03:00
|
|
|
new_errno = EFBIG;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
new_errno = EIO;
|
|
|
|
}
|
|
|
|
return new_errno;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-10-29 17:01:29 +03:00
|
|
|
DiscoverySingleDirectoryJob::DiscoverySingleDirectoryJob(const AccountPtr &account, const QString &path, QObject *parent)
|
2017-05-17 11:55:42 +03:00
|
|
|
: QObject(parent)
|
|
|
|
, _subPath(path)
|
|
|
|
, _account(account)
|
|
|
|
, _ignoredFirst(false)
|
|
|
|
, _isRootPath(false)
|
|
|
|
, _isExternalStorage(false)
|
2014-12-02 14:25:51 +03:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void DiscoverySingleDirectoryJob::start()
|
|
|
|
{
|
|
|
|
// Start the actual HTTP job
|
2020-05-18 21:54:23 +03:00
|
|
|
auto *lsColJob = new LsColJob(_account, _subPath, this);
|
2016-08-02 11:30:49 +03:00
|
|
|
|
|
|
|
QList<QByteArray> props;
|
2017-05-17 11:55:42 +03:00
|
|
|
props << "resourcetype"
|
|
|
|
<< "getlastmodified"
|
|
|
|
<< "getcontentlength"
|
|
|
|
<< "getetag"
|
|
|
|
<< "http://owncloud.org/ns:id"
|
|
|
|
<< "http://owncloud.org/ns:downloadURL"
|
|
|
|
<< "http://owncloud.org/ns:dDC"
|
2017-06-14 13:14:46 +03:00
|
|
|
<< "http://owncloud.org/ns:permissions"
|
|
|
|
<< "http://owncloud.org/ns:checksums";
|
2016-08-02 11:30:49 +03:00
|
|
|
if (_isRootPath)
|
|
|
|
props << "http://owncloud.org/ns:data-fingerprint";
|
2017-07-21 12:28:15 +03:00
|
|
|
if (_account->serverVersionInt() >= Account::makeServerVersion(10, 0, 0)) {
|
|
|
|
// Server older than 10.0 have performances issue if we ask for the share-types on every PROPFIND
|
|
|
|
props << "http://owncloud.org/ns:share-types";
|
|
|
|
}
|
2016-08-02 11:30:49 +03:00
|
|
|
|
|
|
|
lsColJob->setProperties(props);
|
2015-03-02 13:00:37 +03:00
|
|
|
|
2017-09-20 11:14:48 +03:00
|
|
|
QObject::connect(lsColJob, &LsColJob::directoryListingIterated,
|
|
|
|
this, &DiscoverySingleDirectoryJob::directoryListingIteratedSlot);
|
|
|
|
QObject::connect(lsColJob, &LsColJob::finishedWithError, this, &DiscoverySingleDirectoryJob::lsJobFinishedWithErrorSlot);
|
|
|
|
QObject::connect(lsColJob, &LsColJob::finishedWithoutError, this, &DiscoverySingleDirectoryJob::lsJobFinishedWithoutErrorSlot);
|
2014-12-02 14:25:51 +03:00
|
|
|
lsColJob->start();
|
|
|
|
|
|
|
|
_lsColJob = lsColJob;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DiscoverySingleDirectoryJob::abort()
|
|
|
|
{
|
|
|
|
if (_lsColJob && _lsColJob->reply()) {
|
|
|
|
_lsColJob->reply()->abort();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-28 15:20:39 +03:00
|
|
|
static void propertyMapToFileStat(const QMap<QString, QString> &map, csync_file_stat_t *file_stat)
|
2014-12-02 14:25:51 +03:00
|
|
|
{
|
|
|
|
for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
|
|
|
|
QString property = it.key();
|
|
|
|
QString value = it.value();
|
|
|
|
if (property == "resourcetype") {
|
|
|
|
if (value.contains("collection")) {
|
2017-12-14 17:08:53 +03:00
|
|
|
file_stat->type = ItemTypeDirectory;
|
2014-12-02 14:25:51 +03:00
|
|
|
} else {
|
2017-12-14 17:08:53 +03:00
|
|
|
file_stat->type = ItemTypeFile;
|
2014-12-02 14:25:51 +03:00
|
|
|
}
|
2017-05-17 11:55:42 +03:00
|
|
|
} else if (property == "getlastmodified") {
|
2017-09-05 17:12:32 +03:00
|
|
|
file_stat->modtime = oc_httpdate_parse(value.toUtf8());
|
2014-12-02 14:25:51 +03:00
|
|
|
} else if (property == "getcontentlength") {
|
2018-02-28 15:20:39 +03:00
|
|
|
// See #4573, sometimes negative size values are returned
|
2016-04-18 18:33:25 +03:00
|
|
|
bool ok = false;
|
|
|
|
qlonglong ll = value.toLongLong(&ok);
|
|
|
|
if (ok && ll >= 0) {
|
|
|
|
file_stat->size = ll;
|
2018-02-28 15:20:39 +03:00
|
|
|
} else {
|
|
|
|
file_stat->size = 0;
|
2016-04-18 18:33:25 +03:00
|
|
|
}
|
2014-12-02 14:25:51 +03:00
|
|
|
} else if (property == "getetag") {
|
2017-09-05 17:12:32 +03:00
|
|
|
file_stat->etag = Utility::normalizeEtag(value.toUtf8());
|
2014-12-02 14:25:51 +03:00
|
|
|
} else if (property == "id") {
|
2017-09-05 17:12:32 +03:00
|
|
|
file_stat->file_id = value.toUtf8();
|
2014-12-02 14:25:51 +03:00
|
|
|
} else if (property == "downloadURL") {
|
2017-09-05 17:12:32 +03:00
|
|
|
file_stat->directDownloadUrl = value.toUtf8();
|
2014-12-02 14:25:51 +03:00
|
|
|
} else if (property == "dDC") {
|
2017-09-05 17:12:32 +03:00
|
|
|
file_stat->directDownloadCookies = value.toUtf8();
|
2014-12-02 14:25:51 +03:00
|
|
|
} else if (property == "permissions") {
|
2017-09-19 11:53:51 +03:00
|
|
|
file_stat->remotePerm = RemotePermissions(value);
|
2017-06-14 13:14:46 +03:00
|
|
|
} else if (property == "checksums") {
|
2017-09-05 17:12:32 +03:00
|
|
|
file_stat->checksumHeader = findBestChecksum(value.toUtf8());
|
2017-07-21 12:28:15 +03:00
|
|
|
} else if (property == "share-types" && !value.isEmpty()) {
|
2017-09-19 11:53:51 +03:00
|
|
|
// Since QMap is sorted, "share-types" is always after "permissions".
|
|
|
|
if (file_stat->remotePerm.isNull()) {
|
2017-07-21 12:28:15 +03:00
|
|
|
qWarning() << "Server returned a share type, but no permissions?";
|
|
|
|
} else {
|
|
|
|
// S means shared with me.
|
|
|
|
// But for our purpose, we want to know if the file is shared. It does not matter
|
|
|
|
// if we are the owner or not.
|
2017-09-19 11:53:51 +03:00
|
|
|
// Piggy back on the persmission field
|
|
|
|
file_stat->remotePerm.setPermission(RemotePermissions::IsShared);
|
2017-07-21 12:28:15 +03:00
|
|
|
}
|
2014-12-02 14:25:51 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-17 11:55:42 +03:00
|
|
|
void DiscoverySingleDirectoryJob::directoryListingIteratedSlot(QString file, const QMap<QString, QString> &map)
|
2014-12-02 14:25:51 +03:00
|
|
|
{
|
|
|
|
if (!_ignoredFirst) {
|
2016-08-02 11:30:49 +03:00
|
|
|
// The first entry is for the folder itself, we should process it differently.
|
2014-12-02 14:25:51 +03:00
|
|
|
_ignoredFirst = true;
|
|
|
|
if (map.contains("permissions")) {
|
2017-09-19 11:53:51 +03:00
|
|
|
RemotePermissions perm(map.value("permissions"));
|
2017-01-24 15:25:03 +03:00
|
|
|
emit firstDirectoryPermissions(perm);
|
2017-09-19 11:53:51 +03:00
|
|
|
_isExternalStorage = perm.hasPermission(RemotePermissions::IsMounted);
|
2014-12-02 14:25:51 +03:00
|
|
|
}
|
2016-08-02 11:30:49 +03:00
|
|
|
if (map.contains("data-fingerprint")) {
|
|
|
|
_dataFingerprint = map.value("data-fingerprint").toUtf8();
|
2018-07-26 17:05:26 +03:00
|
|
|
if (_dataFingerprint.isEmpty()) {
|
|
|
|
// Placeholder that means that the server supports the feature even if it did not set one.
|
|
|
|
_dataFingerprint = "[empty]";
|
|
|
|
}
|
2016-08-02 11:30:49 +03:00
|
|
|
}
|
2014-12-02 14:25:51 +03:00
|
|
|
} else {
|
2015-02-18 19:49:07 +03:00
|
|
|
// Remove <webDAV-Url>/folder/ from <webDAV-Url>/folder/subfile.txt
|
2014-12-02 14:25:51 +03:00
|
|
|
file.remove(0, _lsColJob->reply()->request().url().path().length());
|
|
|
|
// remove trailing slash
|
|
|
|
while (file.endsWith('/')) {
|
|
|
|
file.chop(1);
|
|
|
|
}
|
|
|
|
// remove leading slash
|
|
|
|
while (file.startsWith('/')) {
|
|
|
|
file = file.remove(0, 1);
|
|
|
|
}
|
|
|
|
|
2018-02-28 15:20:39 +03:00
|
|
|
std::unique_ptr<csync_file_stat_t> file_stat(new csync_file_stat_t);
|
2017-09-05 17:12:32 +03:00
|
|
|
file_stat->path = file.toUtf8();
|
2018-02-28 15:20:39 +03:00
|
|
|
file_stat->size = -1;
|
|
|
|
propertyMapToFileStat(map, file_stat.get());
|
|
|
|
if (file_stat->type == ItemTypeDirectory)
|
|
|
|
file_stat->size = 0;
|
2020-01-14 22:28:11 +03:00
|
|
|
if (file_stat->remotePerm.hasPermission(RemotePermissions::IsShared) && file_stat->etag.isEmpty()) {
|
2020-01-13 14:25:28 +03:00
|
|
|
/* Handle broken shared file error gracefully instead of stopping sync in the desktop client.
|
|
|
|
DO not set _error */
|
|
|
|
qCWarning(lcDiscovery)
|
|
|
|
<< "Missing path to a share :" << file << file_stat->path << file_stat->type << file_stat->size
|
|
|
|
<< file_stat->modtime << file_stat->remotePerm.toString()
|
|
|
|
<< file_stat->etag << file_stat->file_id;
|
|
|
|
} else if (file_stat->type == ItemTypeSkip
|
2018-02-28 15:20:39 +03:00
|
|
|
|| file_stat->size == -1
|
|
|
|
|| file_stat->remotePerm.isNull()
|
|
|
|
|| file_stat->etag.isEmpty()
|
|
|
|
|| file_stat->file_id.isEmpty()) {
|
|
|
|
_error = tr("The server file discovery reply is missing data.");
|
|
|
|
qCWarning(lcDiscovery)
|
|
|
|
<< "Missing properties:" << file << file_stat->type << file_stat->size
|
|
|
|
<< file_stat->modtime << file_stat->remotePerm.toString()
|
|
|
|
<< file_stat->etag << file_stat->file_id;
|
2015-04-02 17:52:53 +03:00
|
|
|
}
|
2018-02-28 15:20:39 +03:00
|
|
|
|
2017-09-19 11:53:51 +03:00
|
|
|
if (_isExternalStorage && file_stat->remotePerm.hasPermission(RemotePermissions::IsMounted)) {
|
2017-01-24 15:25:03 +03:00
|
|
|
/* All the entries in a external storage have 'M' in their permission. However, for all
|
|
|
|
purposes in the desktop client, we only need to know about the mount points.
|
|
|
|
So replace the 'M' by a 'm' for every sub entries in an external storage */
|
2017-09-19 11:53:51 +03:00
|
|
|
file_stat->remotePerm.unsetPermission(RemotePermissions::IsMounted);
|
|
|
|
file_stat->remotePerm.setPermission(RemotePermissions::IsMountedSub);
|
2017-01-24 15:25:03 +03:00
|
|
|
}
|
2015-10-26 16:24:05 +03:00
|
|
|
|
|
|
|
QStringRef fileRef(&file);
|
|
|
|
int slashPos = file.lastIndexOf(QLatin1Char('/'));
|
2017-05-17 11:55:42 +03:00
|
|
|
if (slashPos > -1) {
|
|
|
|
fileRef = file.midRef(slashPos + 1);
|
2015-10-26 16:24:05 +03:00
|
|
|
}
|
2017-09-05 17:12:32 +03:00
|
|
|
_results.push_back(std::move(file_stat));
|
2014-12-02 14:25:51 +03:00
|
|
|
}
|
|
|
|
|
2015-03-05 19:49:12 +03:00
|
|
|
//This works in concerto with the RequestEtagJob and the Folder object to check if the remote folder changed.
|
|
|
|
if (map.contains("getetag")) {
|
2017-05-17 11:55:42 +03:00
|
|
|
_etagConcatenation += map.value("getetag");
|
2015-10-16 12:52:27 +03:00
|
|
|
|
2017-05-17 11:55:42 +03:00
|
|
|
if (_firstEtag.isEmpty()) {
|
|
|
|
_firstEtag = map.value("getetag"); // for directory itself
|
|
|
|
}
|
2015-03-05 19:49:12 +03:00
|
|
|
}
|
2014-12-02 14:25:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void DiscoverySingleDirectoryJob::lsJobFinishedWithoutErrorSlot()
|
|
|
|
{
|
2015-04-13 16:10:04 +03:00
|
|
|
if (!_ignoredFirst) {
|
|
|
|
// This is a sanity check, if we haven't _ignoredFirst then it means we never received any directoryListingIteratedSlot
|
|
|
|
// which means somehow the server XML was bogus
|
|
|
|
emit finishedWithError(ERRNO_WRONG_CONTENT, QLatin1String("Server error: PROPFIND reply is not XML formatted!"));
|
|
|
|
deleteLater();
|
|
|
|
return;
|
2018-02-28 15:20:39 +03:00
|
|
|
} else if (!_error.isEmpty()) {
|
2018-03-06 09:04:02 +03:00
|
|
|
emit finishedWithError(ERRNO_WRONG_CONTENT, _error);
|
2018-02-28 15:20:39 +03:00
|
|
|
deleteLater();
|
|
|
|
return;
|
2015-04-13 16:10:04 +03:00
|
|
|
}
|
2015-10-16 12:52:27 +03:00
|
|
|
emit etag(_firstEtag);
|
2015-03-05 19:49:12 +03:00
|
|
|
emit etagConcatenation(_etagConcatenation);
|
2017-09-05 17:12:32 +03:00
|
|
|
emit finishedWithResult();
|
2014-12-02 14:25:51 +03:00
|
|
|
deleteLater();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DiscoverySingleDirectoryJob::lsJobFinishedWithErrorSlot(QNetworkReply *r)
|
|
|
|
{
|
|
|
|
QString contentType = r->header(QNetworkRequest::ContentTypeHeader).toString();
|
|
|
|
int httpCode = r->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
2015-02-25 10:57:39 +03:00
|
|
|
QString httpReason = r->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
|
2014-12-02 14:25:51 +03:00
|
|
|
QString msg = r->errorString();
|
2015-04-13 16:36:07 +03:00
|
|
|
int errnoCode = EIO; // Something went wrong
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcDiscovery) << "LSCOL job error" << r->errorString() << httpCode << r->error();
|
2015-02-25 09:55:06 +03:00
|
|
|
if (httpCode != 0 && httpCode != 207) {
|
2015-02-25 10:57:39 +03:00
|
|
|
errnoCode = get_errno_from_http_errcode(httpCode, httpReason);
|
2015-02-25 09:55:06 +03:00
|
|
|
} else if (r->error() != QNetworkReply::NoError) {
|
|
|
|
errnoCode = EIO;
|
2014-12-02 14:25:51 +03:00
|
|
|
} else if (!contentType.contains("application/xml; charset=utf-8")) {
|
|
|
|
msg = QLatin1String("Server error: PROPFIND reply is not XML formatted!");
|
|
|
|
errnoCode = ERRNO_WRONG_CONTENT;
|
2015-04-13 16:36:07 +03:00
|
|
|
} else {
|
|
|
|
// Default keep at EIO, see above
|
2014-12-02 14:25:51 +03:00
|
|
|
}
|
|
|
|
|
2015-04-23 17:15:13 +03:00
|
|
|
emit finishedWithError(errnoCode == 0 ? EIO : errnoCode, msg);
|
2014-12-02 14:25:51 +03:00
|
|
|
deleteLater();
|
|
|
|
}
|
|
|
|
|
2018-07-10 15:50:32 +03:00
|
|
|
/*
|
2017-09-19 11:53:51 +03:00
|
|
|
void DiscoveryMainThread::singleDirectoryJobFirstDirectoryPermissionsSlot(RemotePermissions p)
|
2014-12-02 14:25:51 +03:00
|
|
|
{
|
|
|
|
// Should be thread safe since the sync thread is blocked
|
2017-09-19 11:53:51 +03:00
|
|
|
if (_discoveryJob->_csync_ctx->remote.root_perms.isNull()) {
|
|
|
|
qCDebug(lcDiscovery) << "Permissions for root dir:" << p.toString();
|
|
|
|
_discoveryJob->_csync_ctx->remote.root_perms = p;
|
2014-12-02 14:25:51 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-17 11:55:42 +03:00
|
|
|
void DiscoveryMainThread::doGetSizeSlot(const QString &path, qint64 *result)
|
2015-05-21 15:50:30 +03:00
|
|
|
{
|
|
|
|
QString fullPath = _pathPrefix;
|
|
|
|
if (!_pathPrefix.endsWith('/')) {
|
|
|
|
fullPath += '/';
|
|
|
|
}
|
|
|
|
fullPath += path;
|
|
|
|
// remove trailing slash
|
|
|
|
while (fullPath.endsWith('/')) {
|
|
|
|
fullPath.chop(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
_currentGetSizeResult = result;
|
|
|
|
|
|
|
|
// Schedule the DiscoverySingleDirectoryJob
|
|
|
|
auto propfindJob = new PropfindJob(_account, fullPath, this);
|
2017-05-17 11:55:42 +03:00
|
|
|
propfindJob->setProperties(QList<QByteArray>() << "resourcetype"
|
|
|
|
<< "http://owncloud.org/ns:size");
|
2017-09-20 11:14:48 +03:00
|
|
|
QObject::connect(propfindJob, &PropfindJob::finishedWithError,
|
|
|
|
this, &DiscoveryMainThread::slotGetSizeFinishedWithError);
|
|
|
|
QObject::connect(propfindJob, &PropfindJob::result,
|
|
|
|
this, &DiscoveryMainThread::slotGetSizeResult);
|
2015-05-21 15:50:30 +03:00
|
|
|
propfindJob->start();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DiscoveryMainThread::slotGetSizeFinishedWithError()
|
|
|
|
{
|
2017-05-17 11:55:42 +03:00
|
|
|
if (!_currentGetSizeResult) {
|
2015-05-21 15:50:30 +03:00
|
|
|
return; // possibly aborted
|
|
|
|
}
|
|
|
|
|
2017-05-09 15:24:11 +03:00
|
|
|
qCWarning(lcDiscovery) << "Error getting the size of the directory";
|
2015-05-21 15:50:30 +03:00
|
|
|
// just let let the discovery job continue then
|
2018-11-12 20:46:39 +03:00
|
|
|
_currentGetSizeResult = nullptr;
|
2015-05-21 15:50:30 +03:00
|
|
|
QMutexLocker locker(&_discoveryJob->_vioMutex);
|
|
|
|
_discoveryJob->_vioWaitCondition.wakeAll();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DiscoveryMainThread::slotGetSizeResult(const QVariantMap &map)
|
|
|
|
{
|
2017-05-17 11:55:42 +03:00
|
|
|
if (!_currentGetSizeResult) {
|
2015-05-21 15:50:30 +03:00
|
|
|
return; // possibly aborted
|
|
|
|
}
|
|
|
|
|
2016-02-11 17:06:00 +03:00
|
|
|
*_currentGetSizeResult = map.value(QLatin1String("size")).toLongLong();
|
2017-05-09 15:24:11 +03:00
|
|
|
qCDebug(lcDiscovery) << "Size of folder:" << *_currentGetSizeResult;
|
2018-11-12 20:46:39 +03:00
|
|
|
_currentGetSizeResult = nullptr;
|
2015-05-21 15:50:30 +03:00
|
|
|
QMutexLocker locker(&_discoveryJob->_vioMutex);
|
|
|
|
_discoveryJob->_vioWaitCondition.wakeAll();
|
|
|
|
}
|
|
|
|
|
2018-07-10 15:50:32 +03:00
|
|
|
*/
|
2014-08-18 16:45:48 +04:00
|
|
|
}
|