this, &EditLocallyJob::fileLockError));
_folderForFile->accountState()->account()->setLockFileState(_relPath,
+ _folderForFile->remotePathTrailingSlash(),
+ _folderForFile->path(),
_folderForFile->journalDb(),
SyncFileItem::LockStatus::LockedItem);
}
disconnect(_officeFileLockReleaseUnlockFailure);
qCWarning(lcFolder) << "Failed to unlock a file:" << remoteFilePath << message;
});
- _accountState->account()->setLockFileState(remoteFilePath, journalDb(), SyncFileItem::LockStatus::UnlockedItem);
+ _accountState->account()->setLockFileState(remoteFilePath,
+ remotePathTrailingSlash(),
+ path(),
+ journalDb(),
+ SyncFileItem::LockStatus::UnlockedItem);
}
}
const auto fileRecordPath = fileFromLocalPath(file);
SyncJournalFileRecord rec;
- const auto canLockFile = journalDb()->getFileRecord(fileRecordPath, &rec) && rec.isValid()
- && (!rec._lockstate._locked
- || (rec._lockstate._lockOwnerType == static_cast<qint64>(SyncFileItem::LockOwnerType::UserLock)
- && rec._lockstate._lockOwnerId == _accountState->account()->davUser()));
+ const auto canLockFile = journalDb()->getFileRecord(fileRecordPath, &rec) && rec.isValid() && !rec._lockstate._locked;
if (!canLockFile) {
qCDebug(lcFolder) << "Skipping locking file" << file << "with rec.isValid():" << rec.isValid()
disconnect(_fileLockFailure);
qCWarning(lcFolder) << "Failed to lock a file:" << remoteFilePath << message;
});
- _accountState->account()->setLockFileState(remoteFilePath, journalDb(), SyncFileItem::LockStatus::LockedItem);
+ _accountState->account()->setLockFileState(remoteFilePath,
+ remotePathTrailingSlash(),
+ path(),
+ journalDb(),
+ SyncFileItem::LockStatus::LockedItem);
}
}
#include <QDir>
#include <QMutexLocker>
#include <QStringList>
-#include <QTimer>
#include <array>
#include <cstdint>
{
const std::array<const char *, 2> lockFilePatterns = {{".~lock.", "~$"}};
+constexpr auto lockChangeDebouncingTimerIntervalMs = 500;
+
QString filePathLockFilePatternMatch(const QString &path)
{
qCDebug(OCC::lcFolderWatcher) << "Checking if it is a lock file:" << path;
: QObject(folder)
, _folder(folder)
{
+ _lockChangeDebouncingTimer.setInterval(lockChangeDebouncingTimerIntervalMs);
+
if (_folder && _folder->accountState() && _folder->accountState()->account()) {
connect(_folder->accountState()->account().data(), &Account::capabilitiesChanged, this, &FolderWatcher::folderAccountCapabilitiesChanged);
folderAccountCapabilitiesChanged();
});
}
+void FolderWatcher::lockChangeDebouncingTimerTimedOut()
+{
+ if (!_unlockedFiles.isEmpty()) {
+ const auto unlockedFilesCopy = _unlockedFiles;
+ emit filesLockReleased(unlockedFilesCopy);
+ _unlockedFiles.clear();
+ }
+ if (!_lockedFiles.isEmpty()) {
+ const auto lockedFilesCopy = _lockedFiles;
+ emit filesLockImposed(lockedFilesCopy);
+ emit lockedFilesFound(lockedFilesCopy);
+ _lockedFiles.clear();
+ }
+}
int FolderWatcher::testLinuxWatchCount() const
{
_timer.restart();
QSet<QString> changedPaths;
- QSet<QString> unlockedFiles;
- QSet<QString> lockedFiles;
for (const auto &path : paths) {
if (!_testNotificationPath.isEmpty()
const auto checkResult = lockFileTargetFilePath(path,lockFileNamePattern);
if (_shouldWatchForFileUnlocking) {
// Lock file has been deleted, file now unlocked
- if (checkResult.type == FileLockingInfo::Type::Unlocked && !checkResult.path.isEmpty() && !QFile::exists(path)) {
- unlockedFiles.insert(checkResult.path);
- } else if (!checkResult.path.isEmpty() && QFile::exists(path)) { // Lock file found
- lockedFiles.insert(checkResult.path);
+ if (checkResult.type == FileLockingInfo::Type::Unlocked && !checkResult.path.isEmpty()) {
+ _lockedFiles.remove(checkResult.path);
+ _unlockedFiles.insert(checkResult.path);
}
}
if (checkResult.type == FileLockingInfo::Type::Locked && !checkResult.path.isEmpty()) {
- lockedFiles.insert(checkResult.path);
+ _unlockedFiles.remove(checkResult.path);
+ _lockedFiles.insert(checkResult.path);
}
- qCDebug(lcFolderWatcher) << "Locked files:" << lockedFiles.values();
+ qCDebug(lcFolderWatcher) << "Locked files:" << _lockedFiles.values();
// ------- handle ignores:
if (pathIsIgnored(path)) {
changedPaths.insert(path);
}
- qCDebug(lcFolderWatcher) << "Unlocked files:" << unlockedFiles.values();
- qCDebug(lcFolderWatcher) << "Locked files:" << lockedFiles;
+ qCDebug(lcFolderWatcher) << "Unlocked files:" << _unlockedFiles.values();
+ qCDebug(lcFolderWatcher) << "Locked files:" << _lockedFiles;
- if (!unlockedFiles.isEmpty()) {
- emit filesLockReleased(unlockedFiles);
- }
-
- if (!lockedFiles.isEmpty()) {
- emit filesLockImposed(lockedFiles);
- }
-
- if (!lockedFiles.isEmpty()) {
- emit lockedFilesFound(lockedFiles);
+ if (!_lockedFiles.isEmpty() || !_unlockedFiles.isEmpty()) {
+ if (_lockChangeDebouncingTimer.isActive()) {
+ _lockChangeDebouncingTimer.stop();
+ }
+ _lockChangeDebouncingTimer.setSingleShot(true);
+ _lockChangeDebouncingTimer.start();
+ _lockChangeDebouncingTimer.connect(&_lockChangeDebouncingTimer, &QTimer::timeout, this, &FolderWatcher::lockChangeDebouncingTimerTimedOut, Qt::UniqueConnection);
}
if (changedPaths.isEmpty()) {
#include <QScopedPointer>
#include <QSet>
#include <QDir>
-
-class QTimer;
+#include <QTimer>
namespace OCC {
private slots:
void startNotificationTestWhenReady();
+ void lockChangeDebouncingTimerTimedOut();
protected:
QHash<QString, int> _pendingPathes;
/** Path of the expected test notification */
QString _testNotificationPath;
+ QSet<QString> _unlockedFiles;
+ QSet<QString> _lockedFiles;
+
+ QTimer _lockChangeDebouncingTimer;
+
friend class FolderWatcherPrivate;
};
}
return;
}
- shareFolder->accountState()->account()->setLockFileState(fileData.serverRelativePath, shareFolder->journalDb(), lockState);
+ shareFolder->accountState()->account()->setLockFileState(fileData.serverRelativePath,
+ shareFolder->remotePathTrailingSlash(),
+ shareFolder->path(),
+ shareFolder->journalDb(),
+ lockState);
shareFolder->journalDb()->schedulePathForRemoteDiscovery(fileData.serverRelativePath);
shareFolder->scheduleThisFolderSoon();
}
}
+void Account::removeLockStatusChangeInprogress(const QString &serverRelativePath, const SyncFileItem::LockStatus lockStatus)
+{
+ const auto foundLockStatusJobInProgress = _lockStatusChangeInprogress.find(serverRelativePath);
+ if (foundLockStatusJobInProgress != _lockStatusChangeInprogress.end()) {
+ foundLockStatusJobInProgress.value().removeAll(lockStatus);
+ if (foundLockStatusJobInProgress.value().isEmpty()) {
+ _lockStatusChangeInprogress.erase(foundLockStatusJobInProgress);
+ }
+ }
+}
+
PushNotifications *Account::pushNotifications() const
{
return _pushNotifications;
}
void Account::setLockFileState(const QString &serverRelativePath,
+ const QString &remoteSyncPathWithTrailingSlash,
+ const QString &localSyncPath,
SyncJournalDb * const journal,
const SyncFileItem::LockStatus lockStatus)
{
- auto job = std::make_unique<LockFileJob>(sharedFromThis(), journal, serverRelativePath, lockStatus);
- connect(job.get(), &LockFileJob::finishedWithoutError, this, [this]() {
+ auto& lockStatusJobInProgress = _lockStatusChangeInprogress[serverRelativePath];
+ if (lockStatusJobInProgress.contains(lockStatus)) {
+ qCWarning(lcAccount) << "Already running a job with lockStatus:" << lockStatus << " for: " << serverRelativePath;
+ return;
+ }
+ lockStatusJobInProgress.push_back(lockStatus);
+ auto job = std::make_unique<LockFileJob>(sharedFromThis(), journal, serverRelativePath, remoteSyncPathWithTrailingSlash, localSyncPath, lockStatus);
+ connect(job.get(), &LockFileJob::finishedWithoutError, this, [this, serverRelativePath, lockStatus]() {
+ removeLockStatusChangeInprogress(serverRelativePath, lockStatus);
Q_EMIT lockFileSuccess();
});
connect(job.get(), &LockFileJob::finishedWithError, this, [lockStatus, serverRelativePath, this](const int httpErrorCode, const QString &errorString, const QString &lockOwnerName) {
+ removeLockStatusChangeInprogress(serverRelativePath, lockStatus);
auto errorMessage = QString{};
const auto filePath = serverRelativePath.mid(1);
[[nodiscard]] std::shared_ptr<UserStatusConnector> userStatusConnector() const;
void setLockFileState(const QString &serverRelativePath,
+ const QString &remoteSyncPathWithTrailingSlash,
+ const QString &localSyncPath,
SyncJournalDb * const journal,
const SyncFileItem::LockStatus lockStatus);
void slotCredentialsAsked();
void slotDirectEditingRecieved(const QJsonDocument &json);
+private slots:
+ void removeLockStatusChangeInprogress(const QString &serverRelativePath, const SyncFileItem::LockStatus lockStatus);
+
private:
Account(QObject *parent = nullptr);
void setSharedThis(AccountPtr sharedThis);
std::shared_ptr<UserStatusConnector> _userStatusConnector;
+ QHash<QString, QVector<SyncFileItem::LockStatus>> _lockStatusChangeInprogress;
+
/* IMPORTANT - remove later - FIXME MS@2019-12-07 -->
* TODO: For "Log out" & "Remove account": Remove client CA certs and KEY!
*
LockFileJob::LockFileJob(const AccountPtr account,
SyncJournalDb* const journal,
const QString &path,
+ const QString &remoteSyncPathWithTrailingSlash,
+ const QString &localSyncPath,
const SyncFileItem::LockStatus requestedLockState,
QObject *parent)
: AbstractNetworkJob(account, path, parent)
, _journal(journal)
, _requestedLockState(requestedLockState)
+ , _remoteSyncPathWithTrailingSlash(remoteSyncPathWithTrailingSlash)
+ , _localSyncPath(localSyncPath)
{
+ if (!_localSyncPath.endsWith(QLatin1Char('/'))) {
+ _localSyncPath.append(QLatin1Char('/'));
+ }
}
void LockFileJob::start()
}
}
- const auto relativePath = path().mid(1);
- if (_journal->getFileRecord(relativePath, &record) && record.isValid()) {
+ const auto relativePathInDb = path().mid(_remoteSyncPathWithTrailingSlash.size());
+ if (_journal->getFileRecord(relativePathInDb, &record) && record.isValid()) {
setFileRecordLocked(record);
- if (_lockOwnerType != SyncFileItem::LockOwnerType::UserLock ||
- _userId != account()->davUser()) {
- FileSystem::setFileReadOnly(relativePath, true);
+ if ((_lockStatus == SyncFileItem::LockStatus::LockedItem)
+ && (_lockOwnerType != SyncFileItem::LockOwnerType::UserLock || _userId != account()->davUser())) {
+ FileSystem::setFileReadOnly(_localSyncPath + relativePathInDb, true);
}
const auto result = _journal->setFileRecord(record);
if (!result) {
explicit LockFileJob(const AccountPtr account,
SyncJournalDb* const journal,
const QString &path,
+ const QString &remoteSyncPathWithTrailingSlash,
+ const QString &localSyncPath,
const SyncFileItem::LockStatus requestedLockState,
QObject *parent = nullptr);
void start() override;
QString _userId;
qint64 _lockTime = 0;
qint64 _lockTimeout = 0;
+ QString _remoteSyncPathWithTrailingSlash;
+ QString _localSyncPath;
};
}
QVERIFY(fakeFolder.syncOnce());
- fakeFolder.account()->setLockFileState(QStringLiteral("/") + testFileName, &fakeFolder.syncJournal(), OCC::SyncFileItem::LockStatus::LockedItem);
+ fakeFolder.account()->setLockFileState(QStringLiteral("/") + testFileName,
+ QStringLiteral("/"),
+ fakeFolder.localPath(),
+ &fakeFolder.syncJournal(),
+ OCC::SyncFileItem::LockStatus::LockedItem);
QVERIFY(lockFileSuccessSpy.wait());
QCOMPARE(lockFileErrorSpy.count(), 0);
QVERIFY(fakeFolder.syncOnce());
- fakeFolder.account()->setLockFileState(QStringLiteral("/") + testFileName, &fakeFolder.syncJournal(), OCC::SyncFileItem::LockStatus::LockedItem);
+ fakeFolder.account()->setLockFileState(QStringLiteral("/") + testFileName,
+ QStringLiteral("/"),
+ fakeFolder.localPath(),
+ &fakeFolder.syncJournal(),
+ OCC::SyncFileItem::LockStatus::LockedItem);
QVERIFY(lockFileErrorSpy.wait());
QCOMPARE(lockFileSuccessSpy.count(), 0);
QVERIFY(fakeFolder.syncOnce());
- fakeFolder.account()->setLockFileState(QStringLiteral("/") + testFileName, &fakeFolder.syncJournal(), OCC::SyncFileItem::LockStatus::LockedItem);
+ fakeFolder.account()->setLockFileState(QStringLiteral("/") + testFileName,
+ QStringLiteral("/"),
+ fakeFolder.localPath(),
+ &fakeFolder.syncJournal(),
+ OCC::SyncFileItem::LockStatus::LockedItem);
QVERIFY(lockFileSuccessSpy.wait());
QCOMPARE(lockFileErrorSpy.count(), 0);
QVERIFY(fakeFolder.syncOnce());
- fakeFolder.account()->setLockFileState(QStringLiteral("/") + testFileName, &fakeFolder.syncJournal(), OCC::SyncFileItem::LockStatus::LockedItem);
+ fakeFolder.account()->setLockFileState(QStringLiteral("/") + testFileName,
+ QStringLiteral("/"),
+ fakeFolder.localPath(),
+ &fakeFolder.syncJournal(),
+ OCC::SyncFileItem::LockStatus::LockedItem);
QVERIFY(lockFileSuccessSpy.wait());
QCOMPARE(lockFileErrorSpy.count(), 0);
QVERIFY(fakeFolder.syncOnce());
- auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem);
+ auto job = new OCC::LockFileJob(fakeFolder.account(),
+ &fakeFolder.syncJournal(),
+ QStringLiteral("/") + testFileName,
+ QStringLiteral("/"),
+ fakeFolder.localPath(),
+ OCC::SyncFileItem::LockStatus::LockedItem);
QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError);
QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError);
QVERIFY(fakeFolder.syncOnce());
- auto lockFileJob = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem);
+ auto lockFileJob = new OCC::LockFileJob(fakeFolder.account(),
+ &fakeFolder.syncJournal(),
+ QStringLiteral("/") + testFileName,
+ QStringLiteral("/"),
+ fakeFolder.localPath(),
+ OCC::SyncFileItem::LockStatus::LockedItem);
QSignalSpy lockFileJobSuccess(lockFileJob, &OCC::LockFileJob::finishedWithoutError);
QSignalSpy lockFileJobFailure(lockFileJob, &OCC::LockFileJob::finishedWithError);
QVERIFY(fakeFolder.syncOnce());
- auto unlockFileJob = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::UnlockedItem);
+ auto unlockFileJob = new OCC::LockFileJob(fakeFolder.account(),
+ &fakeFolder.syncJournal(),
+ QStringLiteral("/") + testFileName,
+ QStringLiteral("/"),
+ fakeFolder.localPath(),
+ OCC::SyncFileItem::LockStatus::UnlockedItem);
QSignalSpy unlockFileJobSuccess(unlockFileJob, &OCC::LockFileJob::finishedWithoutError);
QSignalSpy unlockFileJobFailure(unlockFileJob, &OCC::LockFileJob::finishedWithError);
QVERIFY(fakeFolder.syncOnce());
- auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem);
+ auto job = new OCC::LockFileJob(fakeFolder.account(),
+ &fakeFolder.syncJournal(),
+ QStringLiteral("/") + testFileName,
+ QStringLiteral("/"),
+ fakeFolder.localPath(),
+ OCC::SyncFileItem::LockStatus::LockedItem);
QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError);
QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError);
QVERIFY(fakeFolder.syncOnce());
- auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem);
+ auto job = new OCC::LockFileJob(fakeFolder.account(),
+ &fakeFolder.syncJournal(),
+ QStringLiteral("/") + testFileName,
+ QStringLiteral("/"),
+ fakeFolder.localPath(),
+ OCC::SyncFileItem::LockStatus::LockedItem);
QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError);
QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError);
QVERIFY(fakeFolder.syncOnce());
- auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::UnlockedItem);
+ auto job = new OCC::LockFileJob(fakeFolder.account(),
+ &fakeFolder.syncJournal(),
+ QStringLiteral("/") + testFileName,
+ QStringLiteral("/"),
+ fakeFolder.localPath(),
+ OCC::SyncFileItem::LockStatus::UnlockedItem);
QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError);
QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError);
QVERIFY(fakeFolder.syncOnce());
- auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::UnlockedItem);
+ auto job = new OCC::LockFileJob(fakeFolder.account(),
+ &fakeFolder.syncJournal(),
+ QStringLiteral("/") + testFileName,
+ QStringLiteral("/"),
+ fakeFolder.localPath(),
+ OCC::SyncFileItem::LockStatus::UnlockedItem);
QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError);
QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError);
QVERIFY(fakeFolder.syncOnce());
- auto lockFileJob = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::LockedItem);
+ auto lockFileJob = new OCC::LockFileJob(fakeFolder.account(),
+ &fakeFolder.syncJournal(),
+ QStringLiteral("/") + testFileName,
+ QStringLiteral("/"),
+ fakeFolder.localPath(),
+ OCC::SyncFileItem::LockStatus::LockedItem);
QSignalSpy lockFileJobSuccess(lockFileJob, &OCC::LockFileJob::finishedWithoutError);
QSignalSpy lockFileJobFailure(lockFileJob, &OCC::LockFileJob::finishedWithError);
QVERIFY(fakeFolder.syncOnce());
- auto unlockFileJob = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::UnlockedItem);
+ auto unlockFileJob = new OCC::LockFileJob(fakeFolder.account(),
+ &fakeFolder.syncJournal(),
+ QStringLiteral("/") + testFileName,
+ QStringLiteral("/"),
+ fakeFolder.localPath(),
+ OCC::SyncFileItem::LockStatus::UnlockedItem);
QSignalSpy unlockFileJobSuccess(unlockFileJob, &OCC::LockFileJob::finishedWithoutError);
QSignalSpy unlockFileJobFailure(unlockFileJob, &OCC::LockFileJob::finishedWithError);
QVERIFY(fakeFolder.syncOnce());
- auto job = new OCC::LockFileJob(fakeFolder.account(), &fakeFolder.syncJournal(), QStringLiteral("/") + testFileName, OCC::SyncFileItem::LockStatus::UnlockedItem);
+ auto job = new OCC::LockFileJob(fakeFolder.account(),
+ &fakeFolder.syncJournal(),
+ QStringLiteral("/") + testFileName,
+ QStringLiteral("/"),
+ fakeFolder.localPath(),
+ OCC::SyncFileItem::LockStatus::UnlockedItem);
QSignalSpy jobSuccess(job, &OCC::LockFileJob::finishedWithoutError);
QSignalSpy jobFailure(job, &OCC::LockFileJob::finishedWithError);