]> dgit.raspbian.org Git - nextcloud-desktop.git/commitdiff
LockWatcher: Keep an eye on Windows file locks (#4758)
authorckamm <mail@ckamm.de>
Fri, 29 Apr 2016 14:14:18 +0000 (16:14 +0200)
committerKlaas Freitag <freitag@owncloud.com>
Fri, 29 Apr 2016 14:14:18 +0000 (16:14 +0200)
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

13 files changed:
src/gui/CMakeLists.txt
src/gui/folder.cpp
src/gui/folderman.cpp
src/gui/folderman.h
src/gui/lockwatcher.cpp [new file with mode: 0644]
src/gui/lockwatcher.h [new file with mode: 0644]
src/libsync/filesystem.cpp
src/libsync/filesystem.h
src/libsync/owncloudpropagator.h
src/libsync/propagatedownload.cpp
src/libsync/syncengine.cpp
src/libsync/syncengine.h
test/CMakeLists.txt

index 93ebd20831a8ce807f1be47629f9fadf0508a64a..1bb50b86451f649e7758a2cca44c656923c336fb 100644 (file)
@@ -54,6 +54,7 @@ set(client_SRCS
     folderwizard.cpp
     generalsettings.cpp
     ignorelisteditor.cpp
+    lockwatcher.cpp
     logbrowser.cpp
     networksettings.cpp
     ocsjob.cpp
index 6f4407d3f30b9aaddad53a9c4162bb057a18a495..4984ca872567d418a249a54b1e6e9902556a0dff 100644 (file)
@@ -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()
index 3bda867568d12ec5436191abccafd534a9f23748..fc4268053d979ffc518ee8a7d1a32c061ffa29e7 100644 (file)
@@ -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();
index e597e52d4437a1acd37debfe5224cf10fc2893e8..62205d05074e1178d20004ad59553881d3579751 100644 (file)
@@ -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 (file)
index 0000000..7b12479
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) by Christian Kamm <mail@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 (file)
index 0000000..d5a7a05
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) by Christian Kamm <mail@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;
+};
+
+}
index 09fd1682680be23f9fb7b00eea3678ddd19d45ae..f5f5e7da4d4cc66c03e09c8ed9710c49a3e322ee 100644 (file)
@@ -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
index addab46c5aa8bdaca14de2c243b94b919ffde3a9..ebee6b94994f2c8d243723a6b11df53e426547d7 100644 (file)
@@ -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);
+
 }
 
 /** @} */
index 3718efff3458bc66c93e56904dac973c0fd156f1..db8a86b41cb803ddd17e961e06a16cb6036c5578 100644 (file)
@@ -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;
index 779fe5c36be55bfd66332cb1604266cfe3b9a5a1..17b2421db5b439c72ff7154ab73a252e16c59108 100644 (file)
@@ -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;
     }
index a826d7840207306f32791439b3e0f6e5fb4cd170..fdb9de48393222c9499090bbf923dbffb6e0378f 100644 (file)
@@ -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);
index 934e0728c749f0f566a70d53b13b5e350ecac711..084ad23871207ac9bb61545ee80a38fd928014ed 100644 (file)
@@ -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);
index cf3f0a78c51c1dcf24521a8b1c8302943da9a2f3..f701700aa5c5a9871c480bab1a17cd5c0aadb660 100644 (file)
@@ -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})