mirror of
https://github.com/nextcloud/desktop.git
synced 2024-12-22 21:50:30 +03:00
4d72d375f2
Signed-off-by: Felix Weilbach <felix.weilbach@nextcloud.com>
298 lines
11 KiB
C++
298 lines
11 KiB
C++
/*
|
|
* 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.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <QObject>
|
|
#include "discoveryphase.h"
|
|
#include "syncfileitem.h"
|
|
#include "common/asserts.h"
|
|
#include "common/syncjournaldb.h"
|
|
|
|
class ExcludedFiles;
|
|
|
|
namespace OCC {
|
|
class SyncJournalDb;
|
|
|
|
/**
|
|
* Job that handles discovery of a directory.
|
|
*
|
|
* This includes:
|
|
* - Do a DiscoverySingleDirectoryJob network job which will do a PROPFIND of this directory
|
|
* - Stat all the entries in the local file system for this directory
|
|
* - Merge all information (and the information from the database) in order to know what needs
|
|
* to be done for every file within this directory.
|
|
* - For every sub-directory within this directory, "recursively" create a new ProcessDirectoryJob.
|
|
*
|
|
* This job is tightly coupled with the DiscoveryPhase class.
|
|
*
|
|
* After being start()'ed this job will perform work asynchronously and emit finished() when done.
|
|
*
|
|
* Internally, this job will call DiscoveryPhase::scheduleMoreJobs when one of its sub-jobs is
|
|
* finished. DiscoveryPhase::scheduleMoreJobs will call processSubJobs() to continue work until
|
|
* the job is finished.
|
|
*
|
|
* Results are fed outwards via the DiscoveryPhase::itemDiscovered() signal.
|
|
*/
|
|
class ProcessDirectoryJob : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
struct PathTuple;
|
|
public:
|
|
enum QueryMode {
|
|
NormalQuery,
|
|
ParentDontExist, // Do not query this folder because it does not exist
|
|
ParentNotChanged, // No need to query this folder because it has not changed from what is in the DB
|
|
InBlackList // Do not query this folder because it is in the blacklist (remote entries only)
|
|
};
|
|
Q_ENUM(QueryMode)
|
|
|
|
/** For creating the root job
|
|
*
|
|
* The base pin state is used if the root dir's pin state can't be retrieved.
|
|
*/
|
|
explicit ProcessDirectoryJob(DiscoveryPhase *data, PinState basePinState,
|
|
qint64 lastSyncTimestamp, QObject *parent)
|
|
: QObject(parent)
|
|
, _lastSyncTimestamp(lastSyncTimestamp)
|
|
, _discoveryData(data)
|
|
{
|
|
computePinState(basePinState);
|
|
}
|
|
|
|
/// For creating subjobs
|
|
explicit ProcessDirectoryJob(const PathTuple &path, const SyncFileItemPtr &dirItem,
|
|
QueryMode queryLocal, QueryMode queryServer, qint64 lastSyncTimestamp,
|
|
ProcessDirectoryJob *parent)
|
|
: QObject(parent)
|
|
, _dirItem(dirItem)
|
|
, _lastSyncTimestamp(lastSyncTimestamp)
|
|
, _queryServer(queryServer)
|
|
, _queryLocal(queryLocal)
|
|
, _discoveryData(parent->_discoveryData)
|
|
, _currentFolder(path)
|
|
{
|
|
computePinState(parent->_pinState);
|
|
}
|
|
|
|
void start();
|
|
/** Start up to nbJobs, return the number of job started; emit finished() when done */
|
|
int processSubJobs(int nbJobs);
|
|
|
|
void setInsideEncryptedTree(bool isInsideEncryptedTree)
|
|
{
|
|
_isInsideEncryptedTree = isInsideEncryptedTree;
|
|
}
|
|
|
|
bool isInsideEncryptedTree() const
|
|
{
|
|
return _isInsideEncryptedTree;
|
|
}
|
|
|
|
SyncFileItemPtr _dirItem;
|
|
|
|
private:
|
|
|
|
/** 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;
|
|
}
|
|
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
|
|
* Calls processFile() for each non-excluded one.
|
|
* Will start scheduling subdir jobs when done.
|
|
*/
|
|
void process();
|
|
|
|
// return true if the file is excluded.
|
|
// path is the full relative path of the file. localName is the base name of the local entry.
|
|
bool handleExcluded(const QString &path, const QString &localName, bool isDirectory,
|
|
bool isHidden, bool isSymlink);
|
|
|
|
/** Reconcile local/remote/db information for a single item.
|
|
*
|
|
* Can be a file or a directory.
|
|
* Usually ends up emitting itemDiscovered() or creating a subdirectory job.
|
|
*
|
|
* This main function delegates some work to the processFile* functions.
|
|
*/
|
|
void processFile(PathTuple, const LocalInfo &, const RemoteInfo &, const SyncJournalFileRecord &);
|
|
|
|
/// processFile helper for when remote information is available, typically flows into AnalyzeLocalInfo when done
|
|
void processFileAnalyzeRemoteInfo(const SyncFileItemPtr &item, PathTuple, const LocalInfo &, const RemoteInfo &, const SyncJournalFileRecord &);
|
|
|
|
/// processFile helper for reconciling local changes
|
|
void processFileAnalyzeLocalInfo(const SyncFileItemPtr &item, PathTuple, const LocalInfo &, const RemoteInfo &, const SyncJournalFileRecord &, QueryMode recurseQueryServer);
|
|
|
|
/// processFile helper for local/remote conflicts
|
|
void processFileConflict(const SyncFileItemPtr &item, PathTuple, const LocalInfo &, const RemoteInfo &, const SyncJournalFileRecord &);
|
|
|
|
/// processFile helper for common final processing
|
|
void processFileFinalize(const SyncFileItemPtr &item, PathTuple, bool recurse, QueryMode recurseQueryLocal, QueryMode recurseQueryServer);
|
|
|
|
|
|
/** Checks the permission for this item, if needed, change the item to a restoration item.
|
|
* @return false indicate that this is an error and if it is a directory, one should not recurse
|
|
* inside it.
|
|
*/
|
|
bool checkPermissions(const SyncFileItemPtr &item);
|
|
|
|
struct MovePermissionResult
|
|
{
|
|
// whether moving/renaming the source is ok
|
|
bool sourceOk = false;
|
|
// whether the destination accepts (always true for renames)
|
|
bool destinationOk = false;
|
|
// whether creating a new file/dir in the destination is ok
|
|
bool destinationNewOk = false;
|
|
};
|
|
|
|
/**
|
|
* Check if the move is of a specified file within this directory is allowed.
|
|
* Return true if it is allowed, false otherwise
|
|
*/
|
|
MovePermissionResult checkMovePermissions(RemotePermissions srcPerm, const QString &srcPath, bool isDirectory);
|
|
|
|
void processBlacklisted(const PathTuple &, const LocalInfo &, const SyncJournalFileRecord &dbEntry);
|
|
void subJobFinished();
|
|
|
|
/** An DB operation failed */
|
|
void dbError();
|
|
|
|
void addVirtualFileSuffix(QString &str) const;
|
|
bool hasVirtualFileSuffix(const QString &str) const;
|
|
void chopVirtualFileSuffix(QString &str) const;
|
|
|
|
/** Convenience to detect suffix-vfs modes */
|
|
bool isVfsWithSuffix() const;
|
|
|
|
/** Start a remote discovery network job
|
|
*
|
|
* It fills _serverNormalQueryEntries and sets _serverQueryDone when done.
|
|
*/
|
|
DiscoverySingleDirectoryJob *startAsyncServerQuery();
|
|
|
|
/** Discover the local directory
|
|
*
|
|
* Fills _localNormalQueryEntries.
|
|
*/
|
|
void startAsyncLocalQuery();
|
|
|
|
|
|
/** Sets _pinState, the directory's pin state
|
|
*
|
|
* If the folder exists locally its state is retrieved, otherwise the
|
|
* parent's pin state is inherited.
|
|
*/
|
|
void computePinState(PinState parentState);
|
|
|
|
/** Adjust record._type if the db pin state suggests it.
|
|
*
|
|
* If the pin state is stored in the database (suffix vfs only right now)
|
|
* its effects won't be seen in localEntry._type. Instead the effects
|
|
* should materialize in dbEntry._type.
|
|
*
|
|
* This function checks whether the combination of file type and pin
|
|
* state suggests a hydration or dehydration action and changes the
|
|
* _type field accordingly.
|
|
*/
|
|
void setupDbPinStateActions(SyncJournalFileRecord &record);
|
|
|
|
qint64 _lastSyncTimestamp = 0;
|
|
|
|
QueryMode _queryServer = QueryMode::NormalQuery;
|
|
QueryMode _queryLocal = QueryMode::NormalQuery;
|
|
|
|
// Holds entries that resulted from a NormalQuery
|
|
QVector<RemoteInfo> _serverNormalQueryEntries;
|
|
QVector<LocalInfo> _localNormalQueryEntries;
|
|
|
|
// Whether the local/remote directory item queries are done. Will be set
|
|
// even even for do-nothing (!= NormalQuery) queries.
|
|
bool _serverQueryDone = false;
|
|
bool _localQueryDone = false;
|
|
|
|
RemotePermissions _rootPermissions;
|
|
QPointer<DiscoverySingleDirectoryJob> _serverJob;
|
|
|
|
|
|
/** Number of currently running async jobs.
|
|
*
|
|
* These "async jobs" have nothing to do with the jobs for subdirectories
|
|
* which are being tracked by _queuedJobs and _runningJobs.
|
|
*
|
|
* They are jobs that need to be completed to finish processing of directory
|
|
* entries. This variable is used to ensure this job doesn't finish while
|
|
* these jobs are still in flight.
|
|
*/
|
|
int _pendingAsyncJobs = 0;
|
|
|
|
/** The queued and running jobs for subdirectories.
|
|
*
|
|
* The jobs are enqueued while processind directory entries and
|
|
* then gradually run via calls to processSubJobs().
|
|
*/
|
|
std::deque<ProcessDirectoryJob *> _queuedJobs;
|
|
QVector<ProcessDirectoryJob *> _runningJobs;
|
|
|
|
DiscoveryPhase *_discoveryData;
|
|
|
|
PathTuple _currentFolder;
|
|
bool _childModified = false; // the directory contains modified item what would prevent deletion
|
|
bool _childIgnored = false; // The directory contains ignored item that would prevent deletion
|
|
PinState _pinState = PinState::Unspecified; // The directory's pin-state, see computePinState()
|
|
bool _isInsideEncryptedTree = false; // this directory is encrypted or is within the tree of directories with root directory encrypted
|
|
|
|
signals:
|
|
void finished();
|
|
// The root etag of this directory was fetched
|
|
void etag(const QByteArray &, const QDateTime &time);
|
|
};
|
|
}
|