Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/edit file locally restart sync #5175

Merged
merged 1 commit into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
305 changes: 270 additions & 35 deletions src/gui/editlocallyjob.cpp

Large diffs are not rendered by default.

25 changes: 21 additions & 4 deletions src/gui/editlocallyjob.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <QObject>

#include "accountstate.h"
#include "syncfileitem.h"

namespace OCC {

Expand All @@ -38,7 +39,7 @@ class EditLocallyJob : public QObject

[[nodiscard]] static bool isTokenValid(const QString &token);
[[nodiscard]] static bool isRelPathValid(const QString &relPath);
[[nodiscard]] static bool isRelPathExcluded(const QString &relPath);
[[nodiscard]] static OCC::Folder *findFolderForFile(const QString &relPath, const QString &userId);
[[nodiscard]] static QString prefixSlashToPath(const QString &path);

signals:
Expand All @@ -51,31 +52,47 @@ public slots:
void startEditLocally();

private slots:
void fetchRemoteFileParentInfo();
void startSyncBeforeOpening();
void eraseBlacklistRecordForItem();

void startTokenRemoteCheck();
void proceedWithSetup();
void findAfolderAndConstructPaths();

void showError(const QString &message, const QString &informativeText);
void showErrorNotification(const QString &message, const QString &informativeText) const;
void showErrorMessageBox(const QString &message, const QString &informativeText) const;

void remoteTokenCheckResultReceived(const int statusCode);
void folderSyncFinished(const OCC::SyncResult &result);
void slotItemDiscovered(const OCC::SyncFileItemPtr &item);
void slotItemCompleted(const OCC::SyncFileItemPtr &item);

void slotLsColJobFinishedWithError(QNetworkReply *reply);
void slotDirectoryListingIterated(const QString &name, const QMap<QString, QString> &properties);

void disconnectSyncFinished() const;
void openFile();

private:
[[nodiscard]] bool checkIfFileParentSyncIsNeeded(); // returns true if sync will be needed, false otherwise
[[nodiscard]] const QString getRelativePathToRemoteRootForFile() const; // returns either '/' or a (relative path - Folder::remotePath()) for folders pointing to a non-root remote path e.g. '/subfolder' instead of '/'
[[nodiscard]] const QString getRelativePathParent() const;

bool _tokenVerified = false;

AccountStatePtr _accountState;
QString _userId;
QString _relPath;
QString _relPath; // full remote path for a file (as on the server)
QString _relativePathToRemoteRoot; // (relative path - Folder::remotePath()) for folders pointing to a non-root remote path e.g. '/subfolder' instead of '/'
QString _relPathParent; // a folder where the file resides ('/' if it is in the first level of a remote root, or e.g. a '/subfolder/a/b/c if it resides in a nested folder)
QString _token;
SyncFileItemPtr _fileParentItem;

QString _fileName;
QString _localFilePath;
Folder *_folderForFile = nullptr;
std::unique_ptr<SimpleApiJob> _checkTokenJob;
QMetaObject::Connection _syncTerminatedConnection = {};
};

}
3 changes: 3 additions & 0 deletions src/gui/editlocallymanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ void EditLocallyManager::createJob(const QString &userId,
const QString &relPath,
const QString &token)
{
if (_jobs.contains(token)) {
return;
}
const EditLocallyJobPtr job(new EditLocallyJob(userId, relPath, token));
// We need to make sure the job sticks around until it is finished
_jobs.insert(token, job);
Expand Down
2 changes: 0 additions & 2 deletions src/gui/editlocallymanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ class EditLocallyManager : public QObject
public:
[[nodiscard]] static EditLocallyManager *instance();

QHash<QString, QMetaObject::Connection> folderSyncFinishedConnections;

public slots:
void editLocally(const QUrl &url);

Expand Down
15 changes: 12 additions & 3 deletions src/gui/folder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -831,8 +831,11 @@ bool Folder::reloadExcludes()

void Folder::startSync(const QStringList &pathList)
{
Q_UNUSED(pathList)

const auto singleItemDiscoveryOptions = _engine->singleItemDiscoveryOptions();
Q_ASSERT(!singleItemDiscoveryOptions.discoveryDirItem || singleItemDiscoveryOptions.discoveryDirItem->isDirectory());
if (singleItemDiscoveryOptions.discoveryDirItem && !singleItemDiscoveryOptions.discoveryDirItem->isDirectory()) {
qCCritical(lcFolder) << "startSync only accepts directory SyncFileItem, not a file.";
}
if (isBusy()) {
qCCritical(lcFolder) << "ERROR csync is still running and new sync requested.";
return;
Expand Down Expand Up @@ -868,7 +871,13 @@ void Folder::startSync(const QStringList &pathList)
bool periodicFullLocalDiscoveryNow =
fullLocalDiscoveryInterval.count() >= 0 // negative means we don't require periodic full runs
&& _timeSinceLastFullLocalDiscovery.hasExpired(fullLocalDiscoveryInterval.count());
if (_folderWatcher && _folderWatcher->isReliable()

if (!singleItemDiscoveryOptions.filePathRelative.isEmpty()
&& singleItemDiscoveryOptions.discoveryDirItem && !singleItemDiscoveryOptions.discoveryDirItem->isEmpty()) {
qCInfo(lcFolder) << "Going to sync just one file";
_engine->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, {singleItemDiscoveryOptions.discoveryPath});
_localDiscoveryTracker->startSyncPartialDiscovery();
} else if (_folderWatcher && _folderWatcher->isReliable()
&& hasDoneFullLocalDiscovery
&& !periodicFullLocalDiscoveryNow) {
qCInfo(lcFolder) << "Allowing local discovery to read from the database";
Expand Down
1 change: 1 addition & 0 deletions src/libsync/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ set(libsync_SRCS
encryptfolderjob.cpp
filesystem.h
filesystem.cpp
helpers.cpp
httplogger.h
httplogger.cpp
logger.h
Expand Down
20 changes: 19 additions & 1 deletion src/libsync/account.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ constexpr int checksumRecalculateRequestServerVersionMinSupportedMajor = 24;
}

namespace OCC {

Q_LOGGING_CATEGORY(lcAccount, "nextcloud.sync.account", QtInfoMsg)
const char app_password[] = "_app-password";

Expand Down Expand Up @@ -162,6 +161,25 @@ QString Account::displayName() const
return dn;
}

QString Account::userIdAtHostWithPort() const
{
const auto credentialsUserSplit = credentials() ? credentials()->user().split(QLatin1Char('@')) : QStringList{};

if (credentialsUserSplit.isEmpty()) {
return {};
}

const auto userName = credentialsUserSplit.first();

QString dn = QStringLiteral("%1@%2").arg(userName, _url.host());
const auto port = url().port();
if (port > 0 && port != 80 && port != 443) {
dn.append(QLatin1Char(':'));
dn.append(QString::number(port));
}
return dn;
}

QString Account::davDisplayName() const
{
return _displayName;
Expand Down
3 changes: 3 additions & 0 deletions src/libsync/account.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ class OWNCLOUDSYNC_EXPORT Account : public QObject
/// The name of the account as shown in the toolbar
[[nodiscard]] QString displayName() const;

/// User id in a form 'user@example.de, optionally port is added (if it is not 80 or 443)
[[nodiscard]] QString userIdAtHostWithPort() const;

/// The name of the account that is displayed as nicely as possible,
/// e.g. the actual name of the user (John Doe). If this cannot be
/// provided, defaults to davUser (e.g. johndoe)
Expand Down
17 changes: 17 additions & 0 deletions src/libsync/discovery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@ ProcessDirectoryJob::ProcessDirectoryJob(const PathTuple &path, const SyncFileIt
computePinState(parent->_pinState);
}

ProcessDirectoryJob::ProcessDirectoryJob(DiscoveryPhase *data, PinState basePinState, const PathTuple &path, const SyncFileItemPtr &dirItem, QueryMode queryLocal, qint64 lastSyncTimestamp, QObject *parent)
: QObject(parent)
, _dirItem(dirItem)
, _lastSyncTimestamp(lastSyncTimestamp)
, _queryLocal(queryLocal)
, _discoveryData(data)
, _currentFolder(path)
{
computePinState(basePinState);
}

void ProcessDirectoryJob::start()
{
qCInfo(lcDisco) << "STARTING" << _currentFolder._server << _queryServer << _currentFolder._local << _queryLocal;
Expand Down Expand Up @@ -162,6 +173,11 @@ void ProcessDirectoryJob::process()
PathTuple path;
path = _currentFolder.addName(e.nameOverride.isEmpty() ? f.first : e.nameOverride);

if (!_discoveryData->_listExclusiveFiles.isEmpty() && !_discoveryData->_listExclusiveFiles.contains(path._server)) {
qCInfo(lcDisco) << "Skipping a file:" << path._server << "as it is not listed in the _listExclusiveFiles";
continue;
}

if (isVfsWithSuffix()) {
// Without suffix vfs the paths would be good. But since the dbEntry and localEntry
// can have different names from f.first when suffix vfs is on, make sure the
Expand Down Expand Up @@ -213,6 +229,7 @@ void ProcessDirectoryJob::process()

processFile(std::move(path), e.localEntry, e.serverEntry, e.dbEntry);
}
_discoveryData->_listExclusiveFiles.clear();
QTimer::singleShot(0, _discoveryData, &DiscoveryPhase::scheduleMoreJobs);
}

Expand Down
80 changes: 41 additions & 39 deletions src/libsync/discovery.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,45 @@ class ProcessDirectoryJob : public QObject
{
Q_OBJECT

struct PathTuple;
public:

/** Structure representing a path during discovery. A same path may have different value locally
* or on the server in case of renames.
*
* These strings never start or ends with slashes. They are all relative to the folder's root.
* Usually they are all the same and are even shared instance of the same QString.
*
* _server and _local paths will differ if there are renames, example:
* remote renamed A/ to B/ and local renamed A/X to A/Y then
* target: B/Y/file
* original: A/X/file
* local: A/Y/file
* server: B/X/file
*/
struct PathTuple {
QString _original; // Path as in the DB (before the sync)
QString _target; // Path that will be the result after the sync (and will be in the DB)
QString _server; // Path on the server (before the sync)
QString _local; // Path locally (before the sync)
static QString pathAppend(const QString &base, const QString &name)
{
return base.isEmpty() ? name : base + QLatin1Char('/') + name;
}
[[nodiscard]] PathTuple addName(const QString &name) const
{
PathTuple result;
result._original = pathAppend(_original, name);
auto buildString = [&](const QString &other) {
// Optimize by trying to keep all string implicitly shared if they are the same (common case)
return other == _original ? result._original : pathAppend(other, name);
};
result._target = buildString(_target);
result._server = buildString(_server);
result._local = buildString(_local);
return result;
}
};

enum QueryMode {
NormalQuery,
ParentDontExist, // Do not query this folder because it does not exist
Expand All @@ -71,6 +108,9 @@ class ProcessDirectoryJob : public QObject
QueryMode queryLocal, QueryMode queryServer, qint64 lastSyncTimestamp,
ProcessDirectoryJob *parent);

explicit ProcessDirectoryJob(DiscoveryPhase *data, PinState basePinState, const PathTuple &path, const SyncFileItemPtr &dirItem,
QueryMode queryLocal, qint64 lastSyncTimestamp, QObject *parent);

void start();
/** Start up to nbJobs, return the number of job started; emit finished() when done */
int processSubJobs(int nbJobs);
Expand All @@ -96,44 +136,6 @@ class ProcessDirectoryJob : public QObject
LocalInfo localEntry;
};

/** Structure representing a path during discovery. A same path may have different value locally
* or on the server in case of renames.
*
* These strings never start or ends with slashes. They are all relative to the folder's root.
* Usually they are all the same and are even shared instance of the same QString.
*
* _server and _local paths will differ if there are renames, example:
* remote renamed A/ to B/ and local renamed A/X to A/Y then
* target: B/Y/file
* original: A/X/file
* local: A/Y/file
* server: B/X/file
*/
struct PathTuple
{
QString _original; // Path as in the DB (before the sync)
QString _target; // Path that will be the result after the sync (and will be in the DB)
QString _server; // Path on the server (before the sync)
QString _local; // Path locally (before the sync)
static QString pathAppend(const QString &base, const QString &name)
{
return base.isEmpty() ? name : base + QLatin1Char('/') + name;
}
[[nodiscard]] PathTuple addName(const QString &name) const
{
PathTuple result;
result._original = pathAppend(_original, name);
auto buildString = [&](const QString &other) {
// Optimize by trying to keep all string implicitly shared if they are the same (common case)
return other == _original ? result._original : pathAppend(other, name);
};
result._target = buildString(_target);
result._server = buildString(_server);
result._local = buildString(_local);
return result;
}
};

/** Iterate over entries inside the directory (non-recursively).
*
* Called once _serverEntries and _localEntries are filled
Expand Down
1 change: 1 addition & 0 deletions src/libsync/discoveryphase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include "discoveryphase.h"
#include "discovery.h"
#include "helpers.h"

#include "account.h"
#include "clientsideencryptionjobs.h"
Expand Down
2 changes: 2 additions & 0 deletions src/libsync/discoveryphase.h
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@ class DiscoveryPhase : public QObject
QHash<QString, long long> _filesNeedingScheduledSync;
QVector<QString> _filesUnscheduleSync;

QStringList _listExclusiveFiles;

signals:
void fatalError(const QString &errorString);
void itemDiscovered(const OCC::SyncFileItemPtr &item);
Expand Down
25 changes: 25 additions & 0 deletions src/libsync/helpers.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include "helpers.h"

namespace OCC
{
QByteArray parseEtag(const char *header)
{
if (!header) {
return {};
}
QByteArray result = header;

// Weak E-Tags can appear when gzip compression is on, see #3946
if (result.startsWith("W/")) {
result = result.mid(2);
}

// https://github.com/owncloud/client/issues/1195
result.replace("-gzip", "");

if (result.length() >= 2 && result.startsWith('"') && result.endsWith('"')) {
result = result.mid(1, result.length() - 2);
}
return result;
}
} // namespace OCC
25 changes: 25 additions & 0 deletions src/libsync/helpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (C) by Oleksandr Zolotov <alex@nextcloud.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.
*/

#pragma once

#include "owncloudlib.h"
#include <QByteArray>

namespace OCC
{
/** Strips quotes and gzip annotations */
OWNCLOUDSYNC_EXPORT QByteArray parseEtag(const char *header);

} // namespace OCC
Loading