[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