[Pkg-owncloud-commits] [owncloud-client] 430/470: LockWatcher: Keep an eye on Windows file locks (#4758)
Sandro Knauß
hefee-guest at moszumanska.debian.org
Thu May 12 16:25:38 UTC 2016
This is an automated email from the git hooks/post-receive script.
hefee-guest pushed a commit to branch master
in repository owncloud-client.
commit e6b937f508a78d6eae3e9d187d70271535d23d86
Author: ckamm <mail at ckamm.de>
Date: Fri Apr 29 16:14:18 2016 +0200
LockWatcher: Keep an eye on Windows file locks (#4758)
When a conflict-rename or a temporary-rename fails, notify the
LockWatcher. It'll regularly check whether the file has become
accesible again. When it has, another sync is triggered.
owncloud/enterprise#1288
---
src/gui/CMakeLists.txt | 1 +
src/gui/folder.cpp | 1 +
src/gui/folderman.cpp | 17 ++++++++++
src/gui/folderman.h | 15 +++++++++
src/gui/lockwatcher.cpp | 51 ++++++++++++++++++++++++++++++
src/gui/lockwatcher.h | 66 +++++++++++++++++++++++++++++++++++++++
src/libsync/filesystem.cpp | 35 +++++++++++++++++++++
src/libsync/filesystem.h | 5 +++
src/libsync/owncloudpropagator.h | 3 ++
src/libsync/propagatedownload.cpp | 18 +++++++++--
src/libsync/syncengine.cpp | 1 +
src/libsync/syncengine.h | 6 ++++
test/CMakeLists.txt | 1 +
13 files changed, 218 insertions(+), 2 deletions(-)
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 93ebd20..1bb50b8 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -54,6 +54,7 @@ set(client_SRCS
folderwizard.cpp
generalsettings.cpp
ignorelisteditor.cpp
+ lockwatcher.cpp
logbrowser.cpp
networksettings.cpp
ocsjob.cpp
diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp
index 6f4407d..4984ca8 100644
--- a/src/gui/folder.cpp
+++ b/src/gui/folder.cpp
@@ -115,6 +115,7 @@ Folder::Folder(const FolderDefinition& definition,
connect(_engine.data(), SIGNAL(itemCompleted(const SyncFileItem &, const PropagatorJob &)),
this, SLOT(slotItemCompleted(const SyncFileItem &, const PropagatorJob &)));
connect(_engine.data(), SIGNAL(newBigFolder(QString)), this, SLOT(slotNewBigFolderDiscovered(QString)));
+ connect(_engine.data(), SIGNAL(seenLockedFile(QString)), FolderMan::instance(), SLOT(slotSyncOnceFileUnlocks(QString)));
}
Folder::~Folder()
diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp
index 3bda867..fc42680 100644
--- a/src/gui/folderman.cpp
+++ b/src/gui/folderman.cpp
@@ -22,6 +22,7 @@
#include "accountstate.h"
#include "accountmanager.h"
#include "filesystem.h"
+#include "lockwatcher.h"
#include <syncengine.h>
#ifdef Q_OS_MAC
@@ -44,6 +45,7 @@ FolderMan::FolderMan(QObject *parent) :
QObject(parent),
_currentSyncFolder(0),
_syncEnabled( true ),
+ _lockWatcher(new LockWatcher),
_appRestartRequired(false)
{
Q_ASSERT(!_instance);
@@ -64,6 +66,9 @@ FolderMan::FolderMan(QObject *parent) :
connect(AccountManager::instance(), SIGNAL(accountRemoved(AccountState*)),
SLOT(slotRemoveFoldersForAccount(AccountState*)));
+
+ connect(_lockWatcher.data(), SIGNAL(fileUnlocked(QString)),
+ SLOT(slotScheduleFolderOwningFile(QString)));
}
FolderMan *FolderMan::instance()
@@ -460,6 +465,11 @@ void FolderMan::slotScheduleAppRestart()
qDebug() << "## Application restart requested!";
}
+void FolderMan::slotSyncOnceFileUnlocks(const QString& path)
+{
+ _lockWatcher->addFile(path);
+}
+
/*
* if a folder wants to be synced, it calls this slot and is added
* to the queue. The slot to actually start a sync is called afterwards.
@@ -744,6 +754,13 @@ void FolderMan::slotServerVersionChanged(Account *account)
}
}
+void FolderMan::slotScheduleFolderOwningFile(const QString& path)
+{
+ if (Folder* f = folderForPath(path)) {
+ slotScheduleSync(f);
+ }
+}
+
void FolderMan::slotFolderSyncStarted( )
{
qDebug() << ">===================================== sync started for " << _currentSyncFolder->remoteUrl().toString();
diff --git a/src/gui/folderman.h b/src/gui/folderman.h
index e597e52..62205d0 100644
--- a/src/gui/folderman.h
+++ b/src/gui/folderman.h
@@ -32,6 +32,7 @@ namespace OCC {
class Application;
class SyncResult;
class SocketApi;
+class LockWatcher;
/**
* @brief The FolderMan class
@@ -184,6 +185,13 @@ public slots:
*/
void slotScheduleAppRestart();
+ /**
+ * Triggers a sync run once the lock on the given file is removed.
+ *
+ * Automatically detemines the folder that's responsible for the file.
+ */
+ void slotSyncOnceFileUnlocks(const QString& path);
+
private slots:
// slot to take the next folder from queue and start syncing.
void slotStartScheduledFolderSync();
@@ -197,6 +205,12 @@ private slots:
void slotServerVersionChanged(Account* account);
+ /**
+ * Schedules the folder for synchronization that contains
+ * the file with the given path.
+ */
+ void slotScheduleFolderOwningFile(const QString& path);
+
private:
/** Adds a new folder, does not add it to the account settings and
* does not set an account on the new folder.
@@ -227,6 +241,7 @@ private:
QPointer<RequestEtagJob> _currentEtagJob; // alias of Folder running the current RequestEtagJob
QMap<QString, FolderWatcher*> _folderWatchers;
+ QScopedPointer<LockWatcher> _lockWatcher;
QScopedPointer<SocketApi> _socketApi;
/** The aliases of folders that shall be synced. */
diff --git a/src/gui/lockwatcher.cpp b/src/gui/lockwatcher.cpp
new file mode 100644
index 0000000..7b12479
--- /dev/null
+++ b/src/gui/lockwatcher.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) by Christian Kamm <mail at ckamm.de>
+ *
+ * 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; version 2 of the License.
+ *
+ * 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.
+ */
+
+#include "lockwatcher.h"
+#include "filesystem.h"
+
+#include <QTimer>
+
+using namespace OCC;
+
+static const int check_frequency = 20 * 1000; // ms
+
+LockWatcher::LockWatcher(QObject* parent)
+ : QObject(parent)
+{
+ connect(&_timer, SIGNAL(timeout()),
+ SLOT(checkFiles()));
+ _timer.start(check_frequency);
+}
+
+void LockWatcher::addFile(const QString& path)
+{
+ _watchedPaths.insert(path);
+}
+
+void LockWatcher::checkFiles()
+{
+ QSet<QString> unlocked;
+
+ foreach (const QString& path, _watchedPaths) {
+ if (!FileSystem::isFileLocked(path)) {
+ emit fileUnlocked(path);
+ unlocked.insert(path);
+ }
+ }
+
+ // Doing it this way instead of with a QMutableSetIterator
+ // ensures that calling back into addFile from connected
+ // slots isn't a problem.
+ _watchedPaths.subtract(unlocked);
+}
diff --git a/src/gui/lockwatcher.h b/src/gui/lockwatcher.h
new file mode 100644
index 0000000..d5a7a05
--- /dev/null
+++ b/src/gui/lockwatcher.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) by Christian Kamm <mail at ckamm.de>
+ *
+ * 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; version 2 of the License.
+ *
+ * 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 "config.h"
+
+#include <QList>
+#include <QObject>
+#include <QString>
+#include <QSet>
+#include <QTimer>
+
+namespace OCC {
+
+/**
+ * @brief Monitors files that are locked, signaling when they become unlocked
+ *
+ * Only relevant on Windows. Some high-profile applications like Microsoft
+ * Word lock the document that is currently being edited. The synchronization
+ * client will be unable to update them while they are locked.
+ *
+ * In this situation we do want to start a sync run as soon as the file
+ * becomes available again. To do that, we need to regularly check whether
+ * the file is still being locked.
+ *
+ * @ingroup gui
+ */
+
+class LockWatcher : public QObject
+{
+ Q_OBJECT
+public:
+ explicit LockWatcher(QObject* parent = 0);
+
+ /** Start watching a file.
+ *
+ * If the file is not locked later on, the fileUnlocked signal will be
+ * emitted once.
+ */
+ void addFile(const QString& path);
+
+signals:
+ /** Emitted when one of the watched files is no longer
+ * being locked. */
+ void fileUnlocked(const QString& path);
+
+private slots:
+ void checkFiles();
+
+private:
+ QSet<QString> _watchedPaths;
+ QTimer _timer;
+};
+
+}
diff --git a/src/libsync/filesystem.cpp b/src/libsync/filesystem.cpp
index 09fd168..f5f5e7d 100644
--- a/src/libsync/filesystem.cpp
+++ b/src/libsync/filesystem.cpp
@@ -43,6 +43,7 @@ extern "C" {
#include "csync.h"
#include "vio/csync_vio_local.h"
#include "std/c_path.h"
+#include "std/c_string.h"
}
namespace OCC {
@@ -589,4 +590,38 @@ bool FileSystem::remove(const QString &fileName, QString *errorString)
return true;
}
+bool FileSystem::isFileLocked(const QString& fileName)
+{
+#ifdef Q_OS_WIN
+ mbchar_t *wuri = c_utf8_path_to_locale(fileName.toUtf8());
+
+ // Check if file exists
+ DWORD attr = GetFileAttributesW(wuri);
+ if (attr != INVALID_FILE_ATTRIBUTES) {
+ // Try to open the file with as much access as possible..
+ HANDLE win_h = CreateFileW(
+ wuri,
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
+ NULL);
+
+ c_free_locale_string(wuri);
+ if (win_h == INVALID_HANDLE_VALUE) {
+ /* could not be opened, so locked? */
+ /* 32 == ERROR_SHARING_VIOLATION */
+ return true;
+ } else {
+ CloseHandle(win_h);
+ }
+ } else {
+ c_free_locale_string(wuri);
+ }
+#else
+ Q_UNUSED(fileName);
+#endif
+ return false;
+}
+
} // namespace OCC
diff --git a/src/libsync/filesystem.h b/src/libsync/filesystem.h
index addab46..ebee6b9 100644
--- a/src/libsync/filesystem.h
+++ b/src/libsync/filesystem.h
@@ -182,6 +182,11 @@ QByteArray OWNCLOUDSYNC_EXPORT calcAdler32( const QString& fileName );
*/
QString OWNCLOUDSYNC_EXPORT makeConflictFileName(const QString &fn, const QDateTime &dt);
+/**
+ * Returns true when a file is locked. (Windows only)
+ */
+bool OWNCLOUDSYNC_EXPORT isFileLocked(const QString& fileName);
+
}
/** @} */
diff --git a/src/libsync/owncloudpropagator.h b/src/libsync/owncloudpropagator.h
index 3718eff..db8a86b 100644
--- a/src/libsync/owncloudpropagator.h
+++ b/src/libsync/owncloudpropagator.h
@@ -369,6 +369,9 @@ signals:
void progress(const SyncFileItem&, quint64 bytes);
void finished();
+ /** Emitted when propagation has problems with a locked file. */
+ void seenLockedFile(const QString &fileName);
+
private:
AccountPtr _account;
diff --git a/src/libsync/propagatedownload.cpp b/src/libsync/propagatedownload.cpp
index 779fe5c..17b2421 100644
--- a/src/libsync/propagatedownload.cpp
+++ b/src/libsync/propagatedownload.cpp
@@ -704,7 +704,14 @@ void PropagateDownloadFileQNAM::downloadFinished()
QString renameError;
QString conflictFileName = FileSystem::makeConflictFileName(fn, Utility::qDateTimeFromTime_t(_item->_modtime));
if (!FileSystem::rename(fn, conflictFileName, &renameError)) {
- //If the rename fails, don't replace it.
+ // If the rename fails, don't replace it.
+
+ // If the file is locked, we want to retry this sync when it
+ // becomes available again.
+ if (FileSystem::isFileLocked(fn)) {
+ emit _propagator->seenLockedFile(fn);
+ }
+
done(SyncFileItem::SoftError, renameError);
return;
}
@@ -764,7 +771,14 @@ void PropagateDownloadFileQNAM::downloadFinished()
_propagator->_journal->commit("download finished");
}
- _propagator->_anotherSyncNeeded = true;
+ // If the file is locked, we want to retry this sync when it
+ // becomes available again, otherwise try again directly
+ if (FileSystem::isFileLocked(fn)) {
+ emit _propagator->seenLockedFile(fn);
+ } else {
+ _propagator->_anotherSyncNeeded = true;
+ }
+
done(SyncFileItem::SoftError, error);
return;
}
diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp
index a826d78..fdb9de4 100644
--- a/src/libsync/syncengine.cpp
+++ b/src/libsync/syncengine.cpp
@@ -933,6 +933,7 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
connect(_propagator.data(), SIGNAL(progress(const SyncFileItem &,quint64)),
this, SLOT(slotProgress(const SyncFileItem &,quint64)));
connect(_propagator.data(), SIGNAL(finished()), this, SLOT(slotFinished()), Qt::QueuedConnection);
+ connect(_propagator.data(), SIGNAL(seenLockedFile(QString)), SIGNAL(seenLockedFile(QString)));
// apply the network limits to the propagator
setNetworkLimits(_uploadLimit, _downloadLimit);
diff --git a/src/libsync/syncengine.h b/src/libsync/syncengine.h
index 934e072..084ad23 100644
--- a/src/libsync/syncengine.h
+++ b/src/libsync/syncengine.h
@@ -143,6 +143,12 @@ signals:
// A new folder was discovered and was not synced because of the confirmation feature
void newBigFolder(const QString &folder);
+ /** Emitted when propagation has problems with a locked file.
+ *
+ * Forwarded from OwncloudPropagator::seenLockedFile.
+ */
+ void seenLockedFile(const QString &fileName);
+
private slots:
void slotRootEtagReceived(const QString &);
void slotItemCompleted(const SyncFileItem& item, const PropagatorJob & job);
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index cf3f0a7..f701700 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -48,6 +48,7 @@ list(APPEND FolderMan_SRC ../src/gui/folder.cpp )
list(APPEND FolderMan_SRC ../src/gui/socketapi.cpp )
list(APPEND FolderMan_SRC ../src/gui/accountstate.cpp )
list(APPEND FolderMan_SRC ../src/gui/syncrunfilelog.cpp )
+list(APPEND FolderMan_SRC ../src/gui/lockwatcher.cpp )
list(APPEND FolderMan_SRC ${FolderWatcher_SRC})
list(APPEND FolderMan_SRC stub.cpp )
#include_directories(${QTKEYCHAIN_INCLUDE_DIR})
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-owncloud/owncloud-client.git
More information about the Pkg-owncloud-commits
mailing list