mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-22 13:05:51 +03:00
Merge pull request #5175 from nextcloud/feature/edit-file-locally-restart-sync
Feature/edit file locally restart sync
This commit is contained in:
commit
f6ac52bcd7
21 changed files with 603 additions and 119 deletions
|
@ -118,32 +118,14 @@ void EditLocallyJob::remoteTokenCheckResultReceived(const int statusCode)
|
|||
return;
|
||||
}
|
||||
|
||||
proceedWithSetup();
|
||||
findAfolderAndConstructPaths();
|
||||
}
|
||||
|
||||
void EditLocallyJob::proceedWithSetup()
|
||||
{
|
||||
if (!_tokenVerified) {
|
||||
qCWarning(lcEditLocallyJob) << "Could not proceed with setup as token is not verified.";
|
||||
return;
|
||||
}
|
||||
|
||||
const auto foundFiles = FolderMan::instance()->findFileInLocalFolders(_relPath, _accountState->account());
|
||||
|
||||
if (foundFiles.isEmpty()) {
|
||||
if (isRelPathExcluded(_relPath)) {
|
||||
showError(tr("Could not find a file for local editing. Make sure it is not excluded via selective sync."), _relPath);
|
||||
} else {
|
||||
showError(tr("Could not find a file for local editing. Make sure its path is valid and it is synced locally."), _relPath);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_localFilePath = foundFiles.first();
|
||||
_folderForFile = FolderMan::instance()->folderForPath(_localFilePath);
|
||||
|
||||
if (!_folderForFile) {
|
||||
showError(tr("Could not find a folder to sync."), _relPath);
|
||||
showError(tr("Could not validate the request to open a file from server."), tr("Please try again."));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -155,15 +137,180 @@ void EditLocallyJob::proceedWithSetup()
|
|||
|
||||
_fileName = relPathSplit.last();
|
||||
|
||||
_folderForFile = findFolderForFile(_relPath, _userId);
|
||||
|
||||
if (!_folderForFile) {
|
||||
showError(tr("Could not find a file for local editing. Make sure it is not excluded via selective sync."), _relPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_relPathParent != QStringLiteral("/") && (!_fileParentItem || _fileParentItem->isEmpty())) {
|
||||
showError(tr("Could not find a file for local editing. Make sure its path is valid and it is synced locally."), _relPath);
|
||||
return;
|
||||
}
|
||||
|
||||
_localFilePath = _folderForFile->path() + _relativePathToRemoteRoot;
|
||||
|
||||
Systray::instance()->destroyEditFileLocallyLoadingDialog();
|
||||
Q_EMIT setupFinished();
|
||||
}
|
||||
|
||||
void EditLocallyJob::findAfolderAndConstructPaths()
|
||||
{
|
||||
_folderForFile = findFolderForFile(_relPath, _userId);
|
||||
|
||||
if (!_folderForFile) {
|
||||
showError(tr("Could not find a file for local editing. Make sure it is not excluded via selective sync."), _relPath);
|
||||
return;
|
||||
}
|
||||
|
||||
_relativePathToRemoteRoot = getRelativePathToRemoteRootForFile();
|
||||
|
||||
if (_relativePathToRemoteRoot.isEmpty()) {
|
||||
qCWarning(lcEditLocallyJob) << "_relativePathToRemoteRoot is empty for" << _relPath;
|
||||
showError(tr("Could not find a file for local editing. Make sure it is not excluded via selective sync."), _relPath);
|
||||
return;
|
||||
}
|
||||
|
||||
_relPathParent = getRelativePathParent();
|
||||
|
||||
if (_relPathParent.isEmpty()) {
|
||||
showError(tr("Could not find a file for local editing. Make sure it is not excluded via selective sync."), _relPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_relPathParent == QStringLiteral("/")) {
|
||||
proceedWithSetup();
|
||||
return;
|
||||
}
|
||||
|
||||
fetchRemoteFileParentInfo();
|
||||
}
|
||||
|
||||
QString EditLocallyJob::prefixSlashToPath(const QString &path)
|
||||
{
|
||||
return path.startsWith('/') ? path : QChar::fromLatin1('/') + path;
|
||||
}
|
||||
|
||||
void EditLocallyJob::fetchRemoteFileParentInfo()
|
||||
{
|
||||
Q_ASSERT(_relPathParent != QStringLiteral("/"));
|
||||
|
||||
if (_relPathParent == QStringLiteral("/")) {
|
||||
qCWarning(lcEditLocallyJob) << "LsColJob must only be used for nested folders.";
|
||||
return;
|
||||
}
|
||||
|
||||
const auto job = new LsColJob(_accountState->account(), QDir::cleanPath(_folderForFile->remotePathTrailingSlash() + _relPathParent), this);
|
||||
const QList<QByteArray> props{QByteArrayLiteral("resourcetype"),
|
||||
QByteArrayLiteral("getlastmodified"),
|
||||
QByteArrayLiteral("getetag"),
|
||||
QByteArrayLiteral("http://owncloud.org/ns:size"),
|
||||
QByteArrayLiteral("http://owncloud.org/ns:id"),
|
||||
QByteArrayLiteral("http://owncloud.org/ns:permissions"),
|
||||
QByteArrayLiteral("http://owncloud.org/ns:checksums")};
|
||||
|
||||
job->setProperties(props);
|
||||
connect(job, &LsColJob::directoryListingIterated, this, &EditLocallyJob::slotDirectoryListingIterated);
|
||||
connect(job, &LsColJob::finishedWithoutError, this, &EditLocallyJob::proceedWithSetup);
|
||||
connect(job, &LsColJob::finishedWithError, this, &EditLocallyJob::slotLsColJobFinishedWithError);
|
||||
job->start();
|
||||
}
|
||||
|
||||
bool EditLocallyJob::checkIfFileParentSyncIsNeeded()
|
||||
{
|
||||
if (_relPathParent == QLatin1String("/")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Q_ASSERT(_fileParentItem && !_fileParentItem->isEmpty());
|
||||
|
||||
if (!_fileParentItem || _fileParentItem->isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
SyncJournalFileRecord rec;
|
||||
if (!_folderForFile->journalDb()->getFileRecord(_fileParentItem->_file, &rec) || !rec.isValid()) {
|
||||
// we don't have this folder locally, so let's sync it
|
||||
_fileParentItem->_direction = SyncFileItem::Down;
|
||||
_fileParentItem->_instruction = CSYNC_INSTRUCTION_NEW;
|
||||
} else if (rec._etag != _fileParentItem->_etag && rec._modtime != _fileParentItem->_modtime) {
|
||||
// we just need to update metadata as the folder is already present locally
|
||||
_fileParentItem->_direction = rec._modtime < _fileParentItem->_modtime ? SyncFileItem::Down : SyncFileItem::Up;
|
||||
_fileParentItem->_instruction = CSYNC_INSTRUCTION_UPDATE_METADATA;
|
||||
} else {
|
||||
_fileParentItem->_direction = SyncFileItem::Down;
|
||||
_fileParentItem->_instruction = CSYNC_INSTRUCTION_UPDATE_METADATA;
|
||||
SyncJournalFileRecord recFile;
|
||||
if (_folderForFile->journalDb()->getFileRecord(_relativePathToRemoteRoot, &recFile) && recFile.isValid()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void EditLocallyJob::startSyncBeforeOpening()
|
||||
{
|
||||
eraseBlacklistRecordForItem();
|
||||
if (!checkIfFileParentSyncIsNeeded()) {
|
||||
openFile();
|
||||
return;
|
||||
}
|
||||
|
||||
// connect to a SyncEngine::itemDiscovered so we can complete the job as soon as the file in question is discovered
|
||||
QObject::connect(&_folderForFile->syncEngine(), &SyncEngine::itemDiscovered, this, &EditLocallyJob::slotItemDiscovered);
|
||||
_folderForFile->syncEngine().setSingleItemDiscoveryOptions({_relPathParent == QStringLiteral("/") ? QString{} : _relPathParent, _relativePathToRemoteRoot, _fileParentItem});
|
||||
FolderMan::instance()->forceSyncForFolder(_folderForFile);
|
||||
}
|
||||
|
||||
void EditLocallyJob::eraseBlacklistRecordForItem()
|
||||
{
|
||||
if (!_folderForFile || !_fileParentItem) {
|
||||
qCWarning(lcEditLocallyJob) << "_folderForFile or _fileParentItem is invalid!";
|
||||
return;
|
||||
}
|
||||
Q_ASSERT(!_folderForFile->isSyncRunning());
|
||||
if (_folderForFile->isSyncRunning()) {
|
||||
qCWarning(lcEditLocallyJob) << "_folderForFile is syncing";
|
||||
return;
|
||||
}
|
||||
if (_folderForFile->journalDb()->errorBlacklistEntry(_fileParentItem->_file).isValid()) {
|
||||
_folderForFile->journalDb()->wipeErrorBlacklistEntry(_fileParentItem->_file);
|
||||
}
|
||||
}
|
||||
|
||||
const QString EditLocallyJob::getRelativePathToRemoteRootForFile() const
|
||||
{
|
||||
Q_ASSERT(_folderForFile);
|
||||
if (!_folderForFile) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (_folderForFile->remotePathTrailingSlash().size() == 1) {
|
||||
return _relPath;
|
||||
} else {
|
||||
const auto remoteFolderPathWithTrailingSlash = _folderForFile->remotePathTrailingSlash();
|
||||
const auto remoteFolderPathWithoutLeadingSlash =
|
||||
remoteFolderPathWithTrailingSlash.startsWith(QLatin1Char('/')) ? remoteFolderPathWithTrailingSlash.mid(1) : remoteFolderPathWithTrailingSlash;
|
||||
|
||||
return _relPath.startsWith(remoteFolderPathWithoutLeadingSlash) ? _relPath.mid(remoteFolderPathWithoutLeadingSlash.size()) : _relPath;
|
||||
}
|
||||
}
|
||||
|
||||
const QString EditLocallyJob::getRelativePathParent() const
|
||||
{
|
||||
Q_ASSERT(!_relativePathToRemoteRoot.isEmpty());
|
||||
if (_relativePathToRemoteRoot.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
auto relativePathToRemoteRootSplit = _relativePathToRemoteRoot.split(QLatin1Char('/'));
|
||||
if (relativePathToRemoteRootSplit.size() > 1) {
|
||||
relativePathToRemoteRootSplit.removeLast();
|
||||
return relativePathToRemoteRootSplit.join(QLatin1Char('/'));
|
||||
}
|
||||
return QStringLiteral("/");
|
||||
}
|
||||
|
||||
bool EditLocallyJob::isTokenValid(const QString &token)
|
||||
{
|
||||
if (token.isEmpty()) {
|
||||
|
@ -201,24 +348,49 @@ bool EditLocallyJob::isRelPathValid(const QString &relPath)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool EditLocallyJob::isRelPathExcluded(const QString &relPath)
|
||||
OCC::Folder *EditLocallyJob::findFolderForFile(const QString &relPath, const QString &userId)
|
||||
{
|
||||
if (relPath.isEmpty()) {
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto folderMap = FolderMan::instance()->map();
|
||||
for (const auto &folder : folderMap) {
|
||||
bool result = false;
|
||||
const auto excludedThroughSelectiveSync = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &result);
|
||||
for (const auto &excludedPath : excludedThroughSelectiveSync) {
|
||||
if (relPath.startsWith(excludedPath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const auto relPathSplit = relPath.split(QLatin1Char('/'));
|
||||
|
||||
// a file is on the first level of remote root, so, we just need a proper folder that points to a remote root
|
||||
if (relPathSplit.size() == 1) {
|
||||
const auto foundIt = std::find_if(std::begin(folderMap), std::end(folderMap), [&userId](const OCC::Folder *folder) {
|
||||
return folder->remotePath() == QStringLiteral("/") && folder->accountState()->account()->userIdAtHostWithPort() == userId;
|
||||
});
|
||||
|
||||
return foundIt != std::end(folderMap) ? foundIt.value() : nullptr;
|
||||
}
|
||||
|
||||
return false;
|
||||
const auto relPathWithSlash = relPath.startsWith(QStringLiteral("/")) ? relPath : QStringLiteral("/") + relPath;
|
||||
|
||||
for (const auto &folder : folderMap) {
|
||||
// make sure we properly handle folders with non-root(nested) remote paths
|
||||
if ((folder->remotePath() != QStringLiteral("/") && !relPathWithSlash.startsWith(folder->remotePath()))
|
||||
|| folder->accountState()->account()->userIdAtHostWithPort() != userId) {
|
||||
continue;
|
||||
}
|
||||
auto result = false;
|
||||
const auto excludedThroughSelectiveSync = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &result);
|
||||
auto isExcluded = false;
|
||||
for (const auto &excludedPath : excludedThroughSelectiveSync) {
|
||||
if (relPath.startsWith(excludedPath)) {
|
||||
isExcluded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isExcluded) {
|
||||
continue;
|
||||
}
|
||||
return folder;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void EditLocallyJob::showError(const QString &message, const QString &informativeText)
|
||||
|
@ -271,32 +443,95 @@ void EditLocallyJob::startEditLocally()
|
|||
|
||||
Systray::instance()->createEditFileLocallyLoadingDialog(_fileName);
|
||||
|
||||
_folderForFile->startSync();
|
||||
const auto syncFinishedConnection = connect(_folderForFile, &Folder::syncFinished,
|
||||
this, &EditLocallyJob::folderSyncFinished);
|
||||
if (_folderForFile->isSyncRunning()) {
|
||||
// in case sync is already running - terminate it and start a new one
|
||||
_syncTerminatedConnection = connect(_folderForFile, &Folder::syncFinished, this, [this]() {
|
||||
disconnect(_syncTerminatedConnection);
|
||||
_syncTerminatedConnection = {};
|
||||
startSyncBeforeOpening();
|
||||
});
|
||||
_folderForFile->slotTerminateSync();
|
||||
|
||||
EditLocallyManager::instance()->folderSyncFinishedConnections.insert(_localFilePath,
|
||||
syncFinishedConnection);
|
||||
return;
|
||||
}
|
||||
startSyncBeforeOpening();
|
||||
}
|
||||
|
||||
void EditLocallyJob::folderSyncFinished(const OCC::SyncResult &result)
|
||||
void EditLocallyJob::slotItemCompleted(const OCC::SyncFileItemPtr &item)
|
||||
{
|
||||
Q_UNUSED(result)
|
||||
disconnectSyncFinished();
|
||||
openFile();
|
||||
Q_ASSERT(item && !item->isEmpty());
|
||||
if (!item || item->isEmpty()) {
|
||||
qCWarning(lcEditLocallyJob) << "invalid item";
|
||||
}
|
||||
if (item->_file == _relativePathToRemoteRoot) {
|
||||
disconnect(&_folderForFile->syncEngine(), &SyncEngine::itemCompleted, this, &EditLocallyJob::slotItemCompleted);
|
||||
disconnect(&_folderForFile->syncEngine(), &SyncEngine::itemDiscovered, this, &EditLocallyJob::slotItemDiscovered);
|
||||
openFile();
|
||||
}
|
||||
}
|
||||
|
||||
void EditLocallyJob::disconnectSyncFinished() const
|
||||
void EditLocallyJob::slotLsColJobFinishedWithError(QNetworkReply *reply)
|
||||
{
|
||||
if(_localFilePath.isEmpty()) {
|
||||
const auto contentType = reply->header(QNetworkRequest::ContentTypeHeader).toString();
|
||||
const auto invalidContentType = !contentType.contains(QStringLiteral("application/xml; charset=utf-8"))
|
||||
&& !contentType.contains(QStringLiteral("application/xml; charset=\"utf-8\"")) && !contentType.contains(QStringLiteral("text/xml; charset=utf-8"))
|
||||
&& !contentType.contains(QStringLiteral("text/xml; charset=\"utf-8\""));
|
||||
const auto httpCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
qCWarning(lcEditLocallyJob) << "LSCOL job error" << reply->errorString() << httpCode << reply->error();
|
||||
|
||||
const auto message = reply->error() == QNetworkReply::NoError && invalidContentType
|
||||
? tr("Server error: PROPFIND reply is not XML formatted!") : reply->errorString();
|
||||
qCWarning(lcEditLocallyJob) << "Could not proceed with setup as file PROPFIND job has failed." << httpCode << message;
|
||||
showError(tr("Could not find a remote file info for local editing. Make sure its path is valid."), _relPath);
|
||||
}
|
||||
|
||||
void EditLocallyJob::slotDirectoryListingIterated(const QString &name, const QMap<QString, QString> &properties)
|
||||
{
|
||||
Q_ASSERT(_relPathParent != QStringLiteral("/"));
|
||||
|
||||
if (_relPathParent == QStringLiteral("/")) {
|
||||
qCWarning(lcEditLocallyJob) << "LsColJob must only be used for nested folders.";
|
||||
return;
|
||||
}
|
||||
|
||||
const auto manager = EditLocallyManager::instance();
|
||||
const auto job = qobject_cast<LsColJob*>(sender());
|
||||
Q_ASSERT(job);
|
||||
if (!job) {
|
||||
qCWarning(lcEditLocallyJob) << "Must call slotDirectoryListingIterated from a signal.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto existingConnection = manager->folderSyncFinishedConnections.value(_localFilePath)) {
|
||||
disconnect(existingConnection);
|
||||
manager->folderSyncFinishedConnections.remove(_localFilePath);
|
||||
if (name.endsWith(_relPathParent)) {
|
||||
// let's remove remote dav path and remote root from the beginning of the name
|
||||
const auto nameWithoutDavPath = name.mid(_accountState->account()->davPath().size());
|
||||
|
||||
const auto remoteFolderPathWithTrailingSlash = _folderForFile->remotePathTrailingSlash();
|
||||
const auto remoteFolderPathWithoutLeadingSlash = remoteFolderPathWithTrailingSlash.startsWith(QLatin1Char('/'))
|
||||
? remoteFolderPathWithTrailingSlash.mid(1) : remoteFolderPathWithTrailingSlash;
|
||||
|
||||
const auto cleanName = nameWithoutDavPath.startsWith(remoteFolderPathWithoutLeadingSlash)
|
||||
? nameWithoutDavPath.mid(remoteFolderPathWithoutLeadingSlash.size()) : nameWithoutDavPath;
|
||||
disconnect(job, &LsColJob::directoryListingIterated, this, &EditLocallyJob::slotDirectoryListingIterated);
|
||||
_fileParentItem = SyncFileItem::fromProperties(cleanName, properties);
|
||||
}
|
||||
}
|
||||
|
||||
void EditLocallyJob::slotItemDiscovered(const OCC::SyncFileItemPtr &item)
|
||||
{
|
||||
Q_ASSERT(item && !item->isEmpty());
|
||||
if (!item || item->isEmpty()) {
|
||||
qCWarning(lcEditLocallyJob) << "invalid item";
|
||||
}
|
||||
if (item->_file == _relativePathToRemoteRoot) {
|
||||
disconnect(&_folderForFile->syncEngine(), &SyncEngine::itemDiscovered, this, &EditLocallyJob::slotItemDiscovered);
|
||||
if (item->_instruction == CSYNC_INSTRUCTION_NONE) {
|
||||
// return early if the file is already in sync
|
||||
slotItemCompleted(item);
|
||||
return;
|
||||
}
|
||||
// or connect to the SyncEngine::itemCompleted and wait till the file gets sycned
|
||||
QObject::connect(&_folderForFile->syncEngine(), &SyncEngine::itemCompleted, this, &EditLocallyJob::slotItemCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <QObject>
|
||||
|
||||
#include "accountstate.h"
|
||||
#include "syncfileitem.h"
|
||||
|
||||
namespace OCC {
|
||||
|
||||
|
@ -38,7 +39,7 @@ public:
|
|||
|
||||
[[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:
|
||||
|
@ -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 = {};
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -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";
|
||||
|
|
|
@ -34,6 +34,7 @@ set(libsync_SRCS
|
|||
encryptfolderjob.cpp
|
||||
filesystem.h
|
||||
filesystem.cpp
|
||||
helpers.cpp
|
||||
httplogger.h
|
||||
httplogger.cpp
|
||||
logger.h
|
||||
|
|
|
@ -64,7 +64,6 @@ constexpr int checksumRecalculateRequestServerVersionMinSupportedMajor = 24;
|
|||
}
|
||||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcAccount, "nextcloud.sync.account", QtInfoMsg)
|
||||
const char app_password[] = "_app-password";
|
||||
|
||||
|
@ -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;
|
||||
|
|
|
@ -115,6 +115,9 @@ public:
|
|||
/// 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)
|
||||
|
|
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -71,6 +108,9 @@ public:
|
|||
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);
|
||||
|
@ -96,44 +136,6 @@ private:
|
|||
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
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include "discoveryphase.h"
|
||||
#include "discovery.h"
|
||||
#include "helpers.h"
|
||||
|
||||
#include "account.h"
|
||||
#include "clientsideencryptionjobs.h"
|
||||
|
|
|
@ -294,6 +294,8 @@ public:
|
|||
QHash<QString, long long> _filesNeedingScheduledSync;
|
||||
QVector<QString> _filesUnscheduleSync;
|
||||
|
||||
QStringList _listExclusiveFiles;
|
||||
|
||||
signals:
|
||||
void fatalError(const QString &errorString);
|
||||
void itemDiscovered(const OCC::SyncFileItemPtr &item);
|
||||
|
|
25
src/libsync/helpers.cpp
Normal file
25
src/libsync/helpers.cpp
Normal file
|
@ -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
src/libsync/helpers.h
Normal file
25
src/libsync/helpers.h
Normal file
|
@ -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
|
|
@ -38,6 +38,7 @@
|
|||
|
||||
#include "networkjobs.h"
|
||||
#include "account.h"
|
||||
#include "helpers.h"
|
||||
#include "owncloudpropagator.h"
|
||||
#include "clientsideencryption.h"
|
||||
|
||||
|
@ -59,25 +60,6 @@ Q_LOGGING_CATEGORY(lcDetermineAuthTypeJob, "nextcloud.sync.networkjob.determinea
|
|||
Q_LOGGING_CATEGORY(lcSimpleFileJob, "nextcloud.sync.networkjob.simplefilejob", QtInfoMsg)
|
||||
const int notModifiedStatusCode = 304;
|
||||
|
||||
QByteArray parseEtag(const char *header)
|
||||
{
|
||||
if (!header)
|
||||
return QByteArray();
|
||||
QByteArray arr = header;
|
||||
|
||||
// Weak E-Tags can appear when gzip compression is on, see #3946
|
||||
if (arr.startsWith("W/"))
|
||||
arr = arr.mid(2);
|
||||
|
||||
// https://github.com/owncloud/client/issues/1195
|
||||
arr.replace("-gzip", "");
|
||||
|
||||
if (arr.length() >= 2 && arr.startsWith('"') && arr.endsWith('"')) {
|
||||
arr = arr.mid(1, arr.length() - 2);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
RequestEtagJob::RequestEtagJob(AccountPtr account, const QString &path, QObject *parent)
|
||||
: AbstractNetworkJob(account, path, parent)
|
||||
{
|
||||
|
|
|
@ -30,9 +30,6 @@ class QJsonObject;
|
|||
|
||||
namespace OCC {
|
||||
|
||||
/** Strips quotes and gzip annotations */
|
||||
OWNCLOUDSYNC_EXPORT QByteArray parseEtag(const char *header);
|
||||
|
||||
struct HttpError
|
||||
{
|
||||
int code; // HTTP error code
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "owncloudpropagator.h"
|
||||
#include "helpers.h"
|
||||
#include "syncfileitem.h"
|
||||
#include "networkjobs.h"
|
||||
#include "syncengine.h"
|
||||
|
|
|
@ -320,6 +320,8 @@ void SyncEngine::conflictRecordMaintenance()
|
|||
|
||||
void OCC::SyncEngine::slotItemDiscovered(const OCC::SyncFileItemPtr &item)
|
||||
{
|
||||
emit itemDiscovered(item);
|
||||
|
||||
if (Utility::isConflictFile(item->_file))
|
||||
_seenConflictFiles.insert(item->_file);
|
||||
if (item->_instruction == CSYNC_INSTRUCTION_UPDATE_METADATA && !item->isDirectory()) {
|
||||
|
@ -633,8 +635,54 @@ void SyncEngine::startSync()
|
|||
connect(_discoveryPhase.data(), &DiscoveryPhase::silentlyExcluded,
|
||||
_syncFileStatusTracker.data(), &SyncFileStatusTracker::slotAddSilentlyExcluded);
|
||||
|
||||
auto discoveryJob = new ProcessDirectoryJob(
|
||||
_discoveryPhase.data(), PinState::AlwaysLocal, _journal->keyValueStoreGetInt("last_sync", 0), _discoveryPhase.data());
|
||||
ProcessDirectoryJob *discoveryJob = nullptr;
|
||||
|
||||
if (!singleItemDiscoveryOptions().filePathRelative.isEmpty()) {
|
||||
_discoveryPhase->_listExclusiveFiles.clear();
|
||||
_discoveryPhase->_listExclusiveFiles.push_back(singleItemDiscoveryOptions().filePathRelative);
|
||||
}
|
||||
|
||||
if (!singleItemDiscoveryOptions().discoveryPath.isEmpty() && singleItemDiscoveryOptions().discoveryDirItem) {
|
||||
ProcessDirectoryJob::PathTuple path = {};
|
||||
path._local = path._original = path._server = path._target = singleItemDiscoveryOptions().discoveryPath;
|
||||
|
||||
SyncJournalFileRecord rec;
|
||||
const auto localQueryMode = _journal->getFileRecord(singleItemDiscoveryOptions().discoveryDirItem->_file, &rec) && rec.isValid()
|
||||
? ProcessDirectoryJob::NormalQuery
|
||||
: ProcessDirectoryJob::ParentDontExist;
|
||||
|
||||
const auto pinState = [this, &rec]() {
|
||||
if (!_syncOptions._vfs || _syncOptions._vfs->mode() == Vfs::Off) {
|
||||
return PinState::AlwaysLocal;
|
||||
}
|
||||
if (!rec.isValid()) {
|
||||
return PinState::OnlineOnly;
|
||||
}
|
||||
const auto pinStateInDb = _journal->internalPinStates().rawForPath(singleItemDiscoveryOptions().discoveryDirItem->_file.toUtf8());
|
||||
if (pinStateInDb) {
|
||||
return *pinStateInDb;
|
||||
}
|
||||
return PinState::Unspecified;
|
||||
}();
|
||||
|
||||
discoveryJob = new ProcessDirectoryJob(
|
||||
_discoveryPhase.data(),
|
||||
pinState,
|
||||
path,
|
||||
singleItemDiscoveryOptions().discoveryDirItem,
|
||||
localQueryMode,
|
||||
_journal->keyValueStoreGetInt("last_sync", 0),
|
||||
_discoveryPhase.data()
|
||||
);
|
||||
} else {
|
||||
discoveryJob = new ProcessDirectoryJob(
|
||||
_discoveryPhase.data(),
|
||||
PinState::AlwaysLocal,
|
||||
_journal->keyValueStoreGetInt("last_sync", 0),
|
||||
_discoveryPhase.data()
|
||||
);
|
||||
}
|
||||
|
||||
_discoveryPhase->startJob(discoveryJob);
|
||||
connect(discoveryJob, &ProcessDirectoryJob::etag, this, &SyncEngine::slotRootEtagReceived);
|
||||
connect(_discoveryPhase.data(), &DiscoveryPhase::addErrorToGui, this, &SyncEngine::addErrorToGui);
|
||||
|
@ -874,6 +922,8 @@ void SyncEngine::slotPropagationFinished(bool success)
|
|||
|
||||
void SyncEngine::finalize(bool success)
|
||||
{
|
||||
setSingleItemDiscoveryOptions({});
|
||||
|
||||
qCInfo(lcEngine) << "Sync run took " << _stopWatch.addLapTime(QLatin1String("Sync Finished")) << "ms";
|
||||
_stopWatch.stop();
|
||||
|
||||
|
@ -1003,6 +1053,16 @@ void SyncEngine::setLocalDiscoveryOptions(LocalDiscoveryStyle style, std::set<QS
|
|||
}
|
||||
}
|
||||
|
||||
void SyncEngine::setSingleItemDiscoveryOptions(const SingleItemDiscoveryOptions &singleItemDiscoveryOptions)
|
||||
{
|
||||
_singleItemDiscoveryOptions = singleItemDiscoveryOptions;
|
||||
}
|
||||
|
||||
const SyncEngine::SingleItemDiscoveryOptions &SyncEngine::singleItemDiscoveryOptions() const
|
||||
{
|
||||
return _singleItemDiscoveryOptions;
|
||||
}
|
||||
|
||||
bool SyncEngine::shouldDiscoverLocally(const QString &path) const
|
||||
{
|
||||
if (_localDiscoveryStyle == LocalDiscoveryStyle::FilesystemOnly)
|
||||
|
|
|
@ -57,6 +57,12 @@ class OWNCLOUDSYNC_EXPORT SyncEngine : public QObject
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
struct SingleItemDiscoveryOptions {
|
||||
QString discoveryPath;
|
||||
QString filePathRelative;
|
||||
SyncFileItemPtr discoveryDirItem;
|
||||
};
|
||||
|
||||
SyncEngine(AccountPtr account,
|
||||
const QString &localPath,
|
||||
const SyncOptions &syncOptions,
|
||||
|
@ -143,6 +149,9 @@ public slots:
|
|||
*/
|
||||
void setLocalDiscoveryOptions(OCC::LocalDiscoveryStyle style, std::set<QString> paths = {});
|
||||
|
||||
void setSingleItemDiscoveryOptions(const SingleItemDiscoveryOptions &singleItemDiscoveryOptions);
|
||||
[[nodiscard]] const SyncEngine::SingleItemDiscoveryOptions &singleItemDiscoveryOptions() const;
|
||||
|
||||
void addAcceptedInvalidFileName(const QString& filePath);
|
||||
|
||||
signals:
|
||||
|
@ -157,6 +166,8 @@ signals:
|
|||
|
||||
void transmissionProgress(const OCC::ProgressInfo &progress);
|
||||
|
||||
void itemDiscovered(const SyncFileItemPtr &);
|
||||
|
||||
/// We've produced a new sync error of a type.
|
||||
void syncError(const QString &message, OCC::ErrorCategory category = OCC::ErrorCategory::Normal);
|
||||
|
||||
|
@ -375,6 +386,8 @@ private:
|
|||
|
||||
// A vector of all the (unique) scheduled sync timers
|
||||
QVector<QSharedPointer<ScheduledSyncTimer>> _scheduledSyncTimers;
|
||||
|
||||
SingleItemDiscoveryOptions _singleItemDiscoveryOptions;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -13,8 +13,10 @@
|
|||
*/
|
||||
|
||||
#include "syncfileitem.h"
|
||||
#include "common/checksums.h"
|
||||
#include "common/syncjournalfilerecord.h"
|
||||
#include "common/utility.h"
|
||||
#include "helpers.h"
|
||||
#include "filesystem.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
@ -98,4 +100,73 @@ SyncFileItemPtr SyncFileItem::fromSyncJournalFileRecord(const SyncJournalFileRec
|
|||
return item;
|
||||
}
|
||||
|
||||
SyncFileItemPtr SyncFileItem::fromProperties(const QString &filePath, const QMap<QString, QString> &properties)
|
||||
{
|
||||
SyncFileItemPtr item(new SyncFileItem);
|
||||
item->_file = filePath;
|
||||
item->_originalFile = filePath;
|
||||
|
||||
const auto isDirectory = properties.value(QStringLiteral("resourcetype")).contains(QStringLiteral("collection"));
|
||||
item->_type = isDirectory ? ItemTypeDirectory : ItemTypeFile;
|
||||
|
||||
item->_size = isDirectory ? 0 : properties.value(QStringLiteral("size")).toInt();
|
||||
item->_fileId = properties.value(QStringLiteral("id")).toUtf8();
|
||||
|
||||
if (properties.contains(QStringLiteral("permissions"))) {
|
||||
item->_remotePerm = RemotePermissions::fromServerString(properties.value("permissions"));
|
||||
}
|
||||
|
||||
if (!properties.value(QStringLiteral("share-types")).isEmpty()) {
|
||||
item->_remotePerm.setPermission(RemotePermissions::IsShared);
|
||||
}
|
||||
|
||||
item->_isShared = item->_remotePerm.hasPermission(RemotePermissions::IsShared);
|
||||
item->_lastShareStateFetchedTimestamp = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
item->_isEncrypted = properties.value(QStringLiteral("is-encrypted")) == QStringLiteral("1");
|
||||
item->_locked =
|
||||
properties.value(QStringLiteral("lock")) == QStringLiteral("1") ? SyncFileItem::LockStatus::LockedItem : SyncFileItem::LockStatus::UnlockedItem;
|
||||
item->_lockOwnerDisplayName = properties.value(QStringLiteral("lock-owner-displayname"));
|
||||
item->_lockOwnerId = properties.value(QStringLiteral("lock-owner"));
|
||||
item->_lockEditorApp = properties.value(QStringLiteral("lock-owner-editor"));
|
||||
|
||||
{
|
||||
auto ok = false;
|
||||
const auto intConvertedValue = properties.value(QStringLiteral("lock-owner-type")).toULongLong(&ok);
|
||||
item->_lockOwnerType = ok ? static_cast<SyncFileItem::LockOwnerType>(intConvertedValue) : SyncFileItem::LockOwnerType::UserLock;
|
||||
}
|
||||
|
||||
{
|
||||
auto ok = false;
|
||||
const auto intConvertedValue = properties.value(QStringLiteral("lock-time")).toULongLong(&ok);
|
||||
item->_lockTime = ok ? intConvertedValue : 0;
|
||||
}
|
||||
|
||||
{
|
||||
auto ok = false;
|
||||
const auto intConvertedValue = properties.value(QStringLiteral("lock-timeout")).toULongLong(&ok);
|
||||
item->_lockTimeout = ok ? intConvertedValue : 0;
|
||||
}
|
||||
|
||||
const auto date = QDateTime::fromString(properties.value(QStringLiteral("getlastmodified")), Qt::RFC2822Date);
|
||||
Q_ASSERT(date.isValid());
|
||||
if (date.toSecsSinceEpoch() > 0) {
|
||||
item->_modtime = date.toSecsSinceEpoch();
|
||||
}
|
||||
|
||||
if (properties.contains(QStringLiteral("getetag"))) {
|
||||
item->_etag = parseEtag(properties.value(QStringLiteral("getetag")).toUtf8());
|
||||
}
|
||||
|
||||
if (properties.contains(QStringLiteral("checksums"))) {
|
||||
item->_checksumHeader = findBestChecksum(properties.value("checksums").toUtf8());
|
||||
}
|
||||
|
||||
// direction and instruction are decided later
|
||||
item->_direction = SyncFileItem::None;
|
||||
item->_instruction = CSYNC_INSTRUCTION_NONE;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -124,6 +124,10 @@ public:
|
|||
*/
|
||||
static SyncFileItemPtr fromSyncJournalFileRecord(const SyncJournalFileRecord &rec);
|
||||
|
||||
/** Creates a basic SyncFileItem from remote properties
|
||||
*/
|
||||
[[nodiscard]] static SyncFileItemPtr fromProperties(const QString &filePath, const QMap<QString, QString> &properties);
|
||||
|
||||
|
||||
SyncFileItem()
|
||||
: _type(ItemTypeSkip)
|
||||
|
|
Loading…
Reference in a new issue