mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-22 04:55:48 +03:00
Unlock Office files when they are closed.
Signed-off-by: alex-z <blackslayer4@gmail.com>
This commit is contained in:
parent
4cc85d6dac
commit
da6a7d3dac
4 changed files with 171 additions and 1 deletions
|
@ -123,6 +123,8 @@ Folder::Folder(const FolderDefinition &definition,
|
|||
connect(_engine.data(), &SyncEngine::itemCompleted,
|
||||
_localDiscoveryTracker.data(), &LocalDiscoveryTracker::slotItemCompleted);
|
||||
|
||||
connect(_accountState->account().data(), &Account::capabilitiesChanged, this, &Folder::slotCapabilitiesChanged);
|
||||
|
||||
// Potentially upgrade suffix vfs to windows vfs
|
||||
ENFORCE(_vfs);
|
||||
if (_definition.virtualFilesMode == Vfs::WithSuffix
|
||||
|
@ -615,6 +617,39 @@ void Folder::slotWatchedPathChanged(const QString &path, ChangeReason reason)
|
|||
scheduleThisFolderSoon();
|
||||
}
|
||||
|
||||
void Folder::slotFilesLockReleased(const QSet<QString> &files)
|
||||
{
|
||||
qCDebug(lcFolder) << "Going to unlock office files" << files;
|
||||
|
||||
for (const auto &file : files) {
|
||||
const auto fileRecordPath = fileFromLocalPath(file);
|
||||
SyncJournalFileRecord rec;
|
||||
const auto canUnlockFile = journalDb()->getFileRecord(fileRecordPath, &rec)
|
||||
&& rec.isValid()
|
||||
&& rec._lockstate._locked
|
||||
&& rec._lockstate._lockOwnerType == static_cast<qint64>(SyncFileItem::LockOwnerType::UserLock)
|
||||
&& rec._lockstate._lockOwnerId == _accountState->account()->davUser();
|
||||
|
||||
if (!canUnlockFile) {
|
||||
qCDebug(lcFolder) << "Skipping file" << file << "with rec.isValid():" << rec.isValid()
|
||||
<< "and rec._lockstate._lockOwnerId:" << rec._lockstate._lockOwnerId << "and davUser:" << _accountState->account()->davUser();
|
||||
continue;
|
||||
}
|
||||
const QString remoteFilePath = remotePathTrailingSlash() + rec.path();
|
||||
qCDebug(lcFolder) << "Unlocking an office file" << remoteFilePath;
|
||||
_officeFileLockReleaseUnlockSuccess = connect(_accountState->account().data(), &Account::lockFileSuccess, this, [this, remoteFilePath]() {
|
||||
disconnect(_officeFileLockReleaseUnlockSuccess);
|
||||
qCDebug(lcFolder) << "Unlocking an office file succeeded" << remoteFilePath;
|
||||
startSync();
|
||||
});
|
||||
_officeFileLockReleaseUnlockFailure = connect(_accountState->account().data(), &Account::lockFileError, this, [this, remoteFilePath](const QString &message) {
|
||||
disconnect(_officeFileLockReleaseUnlockFailure);
|
||||
qCWarning(lcFolder) << "Failed to unlock a file:" << remoteFilePath << message;
|
||||
});
|
||||
_accountState->account()->setLockFileState(remoteFilePath, journalDb(), SyncFileItem::LockStatus::UnlockedItem);
|
||||
}
|
||||
}
|
||||
|
||||
void Folder::implicitlyHydrateFile(const QString &relativepath)
|
||||
{
|
||||
qCInfo(lcFolder) << "Implicitly hydrate virtual file:" << relativepath;
|
||||
|
@ -1257,6 +1292,13 @@ void Folder::slotHydrationDone()
|
|||
emit syncStateChange();
|
||||
}
|
||||
|
||||
void Folder::slotCapabilitiesChanged()
|
||||
{
|
||||
if (_accountState->account()->capabilities().filesLockAvailable()) {
|
||||
connect(_folderWatcher.data(), &FolderWatcher::filesLockReleased, this, &Folder::slotFilesLockReleased, Qt::UniqueConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void Folder::scheduleThisFolderSoon()
|
||||
{
|
||||
if (!_scheduleSelfTimer.isActive()) {
|
||||
|
@ -1297,6 +1339,9 @@ void Folder::registerFolderWatcher()
|
|||
this, &Folder::slotNextSyncFullLocalDiscovery);
|
||||
connect(_folderWatcher.data(), &FolderWatcher::becameUnreliable,
|
||||
this, &Folder::slotWatcherUnreliable);
|
||||
if (_accountState->account()->capabilities().filesLockAvailable()) {
|
||||
connect(_folderWatcher.data(), &FolderWatcher::filesLockReleased, this, &Folder::slotFilesLockReleased);
|
||||
}
|
||||
_folderWatcher->init(path());
|
||||
_folderWatcher->startNotificatonTest(path() + QLatin1String(".nextcloudsync.log"));
|
||||
}
|
||||
|
|
|
@ -342,6 +342,11 @@ public slots:
|
|||
*/
|
||||
void slotWatchedPathChanged(const QString &path, OCC::Folder::ChangeReason reason);
|
||||
|
||||
/*
|
||||
* Triggered when lock files were removed
|
||||
*/
|
||||
void slotFilesLockReleased(const QSet<QString> &files);
|
||||
|
||||
/**
|
||||
* Mark a virtual file as being requested for download, and start a sync.
|
||||
*
|
||||
|
@ -428,6 +433,8 @@ private slots:
|
|||
/** Unblocks normal sync operation */
|
||||
void slotHydrationDone();
|
||||
|
||||
void slotCapabilitiesChanged();
|
||||
|
||||
private:
|
||||
void connectSyncRoot();
|
||||
|
||||
|
@ -533,6 +540,9 @@ private:
|
|||
* The vfs mode instance (created by plugin) to use. Never null.
|
||||
*/
|
||||
QSharedPointer<Vfs> _vfs;
|
||||
|
||||
QMetaObject::Connection _officeFileLockReleaseUnlockSuccess;
|
||||
QMetaObject::Connection _officeFileLockReleaseUnlockFailure;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
// event masks
|
||||
#include "folderwatcher.h"
|
||||
|
||||
#include "accountstate.h"
|
||||
#include "account.h"
|
||||
#include "capabilities.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <QFileInfo>
|
||||
|
@ -35,6 +39,11 @@
|
|||
#include "folder.h"
|
||||
#include "filesystem.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
const char *lockFilePatterns[] = {".~lock.", "~$"};
|
||||
}
|
||||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcFolderWatcher, "nextcloud.gui.folderwatcher", QtInfoMsg)
|
||||
|
@ -43,6 +52,10 @@ FolderWatcher::FolderWatcher(Folder *folder)
|
|||
: QObject(folder)
|
||||
, _folder(folder)
|
||||
{
|
||||
if (_folder && _folder->accountState() && _folder->accountState()->account()) {
|
||||
connect(_folder->accountState()->account().data(), &Account::capabilitiesChanged, this, &FolderWatcher::folderAccountCapabilitiesChanged);
|
||||
folderAccountCapabilitiesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
FolderWatcher::~FolderWatcher() = default;
|
||||
|
@ -166,20 +179,36 @@ void FolderWatcher::changeDetected(const QStringList &paths)
|
|||
_timer.restart();
|
||||
|
||||
QSet<QString> changedPaths;
|
||||
QSet<QString> unlockedFiles;
|
||||
|
||||
// ------- handle ignores:
|
||||
for (int i = 0; i < paths.size(); ++i) {
|
||||
QString path = paths[i];
|
||||
if (!_testNotificationPath.isEmpty()
|
||||
&& Utility::fileNamesEqual(path, _testNotificationPath)) {
|
||||
_testNotificationPath.clear();
|
||||
}
|
||||
|
||||
if (_shouldWatchForFileUnlocking) {
|
||||
const auto unlockedFilePath = possiblyAddUnlockedFilePath(path);
|
||||
if (!unlockedFilePath.isEmpty()) {
|
||||
unlockedFiles.insert(unlockedFilePath);
|
||||
}
|
||||
|
||||
qCDebug(lcFolderWatcher) << "Unlocked files:" << unlockedFiles.values();
|
||||
}
|
||||
|
||||
// ------- handle ignores:
|
||||
if (pathIsIgnored(path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
changedPaths.insert(path);
|
||||
}
|
||||
|
||||
if (!unlockedFiles.isEmpty()) {
|
||||
emit filesLockReleased(unlockedFiles);
|
||||
}
|
||||
|
||||
if (changedPaths.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
@ -190,4 +219,79 @@ void FolderWatcher::changeDetected(const QStringList &paths)
|
|||
}
|
||||
}
|
||||
|
||||
void FolderWatcher::folderAccountCapabilitiesChanged()
|
||||
{
|
||||
_shouldWatchForFileUnlocking = _folder->accountState()->account()->capabilities().filesLockAvailable();
|
||||
}
|
||||
|
||||
QString FolderWatcher::possiblyAddUnlockedFilePath(const QString &path)
|
||||
{
|
||||
qCDebug(lcFolderWatcher) << "Checking if it is a lock file:" << path;
|
||||
const auto pathSplit = path.split(QLatin1Char('/'), Qt::SkipEmptyParts);
|
||||
if (pathSplit.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
QString lockFilePatternFound;
|
||||
for (const auto &lockFilePattern : lockFilePatterns) {
|
||||
if (pathSplit.last().startsWith(lockFilePattern)) {
|
||||
lockFilePatternFound = lockFilePattern;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lockFilePatternFound.isEmpty() || QFileInfo::exists(path)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
qCDebug(lcFolderWatcher) << "Found a lock file with prefix:" << lockFilePatternFound << "in path:" << path;
|
||||
|
||||
const auto lockFilePathWitoutPrefix = QString(path).replace(lockFilePatternFound, QStringLiteral(""));
|
||||
auto lockFilePathWithoutPrefixSplit = lockFilePathWitoutPrefix.split(QLatin1Char('.'));
|
||||
|
||||
if (lockFilePathWithoutPrefixSplit.size() < 2) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto extensionSanitized = lockFilePathWithoutPrefixSplit.takeLast().toStdString();
|
||||
// remove possible non-alphabetical characters at the end of the extension
|
||||
extensionSanitized.erase(
|
||||
std::remove_if(extensionSanitized.begin(), extensionSanitized.end(), [](const auto &ch) {
|
||||
return !std::isalnum(ch);
|
||||
}),
|
||||
extensionSanitized.end()
|
||||
);
|
||||
|
||||
lockFilePathWithoutPrefixSplit.push_back(QString::fromStdString(extensionSanitized));
|
||||
auto unlockedFilePath = lockFilePathWithoutPrefixSplit.join(QLatin1Char('.'));
|
||||
|
||||
if (!QFile::exists(unlockedFilePath)) {
|
||||
unlockedFilePath.clear();
|
||||
qCDebug(lcFolderWatcher) << "Assumed unlocked file path" << unlockedFilePath << "does not exist. Going to try to find matching file";
|
||||
auto splitFilePath = unlockedFilePath.split(QLatin1Char('/'));
|
||||
if (splitFilePath.size() > 1) {
|
||||
const auto lockFileName = splitFilePath.takeLast();
|
||||
// some software will modify lock file name such that it does not correspond to original file (removing some symbols from the name, so we will search for a matching file
|
||||
unlockedFilePath = findMatchingUnlockedFileInDir(splitFilePath.join(QLatin1Char('/')), lockFileName);
|
||||
}
|
||||
}
|
||||
|
||||
if (unlockedFilePath.isEmpty() || !QFile::exists(unlockedFilePath)) {
|
||||
return {};
|
||||
}
|
||||
return unlockedFilePath;
|
||||
}
|
||||
|
||||
QString FolderWatcher::findMatchingUnlockedFileInDir(const QString &dirPath, const QString &lockFileName)
|
||||
{
|
||||
QString foundFilePath;
|
||||
const QDir dir(dirPath);
|
||||
for (const auto &candidateUnlockedFileInfo : dir.entryInfoList(QDir::Files)) {
|
||||
if (candidateUnlockedFileInfo.fileName().contains(lockFileName)) {
|
||||
foundFilePath = candidateUnlockedFileInfo.absoluteFilePath();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return foundFilePath;
|
||||
}
|
||||
|
||||
} // namespace OCC
|
||||
|
|
|
@ -88,6 +88,11 @@ signals:
|
|||
* of the contained files is changed. */
|
||||
void pathChanged(const QString &path);
|
||||
|
||||
/*
|
||||
* Emitted when lock files were removed
|
||||
*/
|
||||
void filesLockReleased(const QSet<QString> &files);
|
||||
|
||||
/**
|
||||
* Emitted if some notifications were lost.
|
||||
*
|
||||
|
@ -108,6 +113,7 @@ protected slots:
|
|||
// called from the implementations to indicate a change in path
|
||||
void changeDetected(const QString &path);
|
||||
void changeDetected(const QStringList &paths);
|
||||
void folderAccountCapabilitiesChanged();
|
||||
|
||||
private slots:
|
||||
void startNotificationTestWhenReady();
|
||||
|
@ -122,8 +128,13 @@ private:
|
|||
Folder *_folder;
|
||||
bool _isReliable = true;
|
||||
|
||||
bool _shouldWatchForFileUnlocking = false;
|
||||
|
||||
void appendSubPaths(QDir dir, QStringList& subPaths);
|
||||
|
||||
QString possiblyAddUnlockedFilePath(const QString &path);
|
||||
QString findMatchingUnlockedFileInDir(const QString &dirPath, const QString &lockFileName);
|
||||
|
||||
/** Path of the expected test notification */
|
||||
QString _testNotificationPath;
|
||||
|
||||
|
|
Loading…
Reference in a new issue