folderwizard.cpp
generalsettings.cpp
ignorelisteditor.cpp
+ lockwatcher.cpp
logbrowser.cpp
networksettings.cpp
ocsjob.cpp
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()
#include "accountstate.h"
#include "accountmanager.h"
#include "filesystem.h"
+#include "lockwatcher.h"
#include <syncengine.h>
#ifdef Q_OS_MAC
QObject(parent),
_currentSyncFolder(0),
_syncEnabled( true ),
+ _lockWatcher(new LockWatcher),
_appRestartRequired(false)
{
Q_ASSERT(!_instance);
connect(AccountManager::instance(), SIGNAL(accountRemoved(AccountState*)),
SLOT(slotRemoveFoldersForAccount(AccountState*)));
+
+ connect(_lockWatcher.data(), SIGNAL(fileUnlocked(QString)),
+ SLOT(slotScheduleFolderOwningFile(QString)));
}
FolderMan *FolderMan::instance()
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.
}
}
+void FolderMan::slotScheduleFolderOwningFile(const QString& path)
+{
+ if (Folder* f = folderForPath(path)) {
+ slotScheduleSync(f);
+ }
+}
+
void FolderMan::slotFolderSyncStarted( )
{
qDebug() << ">===================================== sync started for " << _currentSyncFolder->remoteUrl().toString();
class Application;
class SyncResult;
class SocketApi;
+class LockWatcher;
/**
* @brief The FolderMan class
*/
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();
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.
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. */
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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;
+};
+
+}
#include "csync.h"
#include "vio/csync_vio_local.h"
#include "std/c_path.h"
+#include "std/c_string.h"
}
namespace OCC {
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
*/
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);
+
}
/** @} */
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;
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;
}
_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;
}
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);
// 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);
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})