Skip to content

Commit

Permalink
Updated FileSystemWatcher from master to fix Photoshop saves.
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim Baker committed Jun 2, 2021
1 parent 2045135 commit 722249a
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 58 deletions.
166 changes: 126 additions & 40 deletions src/editor/filesystemwatcher.cpp
@@ -1,71 +1,157 @@
/*
* filesystemwatcher.cpp
* Copyright 2011, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
* Copyright 2011-2014, Thorbjørn Lindeijer <bjorn@lindeijer.nl>
*
* This file is part of Tiled.
* This file is part of libtiled.
*
* 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; either version 2 of the License, or (at your option)
* any later version.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 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.
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "filesystemwatcher.h"

#include <QDebug>
#include <QFile>
#include <QFileSystemWatcher>
#include <QStringList>

namespace Tiled {

using namespace Tiled::Internal;
namespace Internal {

FileSystemWatcher::FileSystemWatcher(QObject *parent) :
QObject(parent),
mWatcher(new QFileSystemWatcher(this))
{
connect(mWatcher, SIGNAL(fileChanged(QString)),
SIGNAL(fileChanged(QString)));
connect(mWatcher, SIGNAL(directoryChanged(QString)),
SIGNAL(directoryChanged(QString)));
mChangedPathsTimer.setInterval(500);
mChangedPathsTimer.setSingleShot(true);

connect(mWatcher, &QFileSystemWatcher::fileChanged,
this, &FileSystemWatcher::onFileChanged);
connect(mWatcher, &QFileSystemWatcher::directoryChanged,
this, &FileSystemWatcher::onDirectoryChanged);

connect(&mChangedPathsTimer, &QTimer::timeout,
this, &FileSystemWatcher::pathsChangedTimeout);
}

void FileSystemWatcher::addPath(const QString &path)
void FileSystemWatcher::addPaths(const QStringList &paths)
{
// Just silently ignore the request when the file doesn't exist
if (!QFile::exists(path))
return;

QMap<QString, int>::iterator entry = mWatchCount.find(path);
if (entry == mWatchCount.end()) {
mWatcher->addPath(path);
mWatchCount.insert(path, 1);
} else {
// Path is already being watched, increment watch count
++entry.value();
QStringList pathsToAdd;
pathsToAdd.reserve(paths.size());

for (const QString &path : paths) {
// Just silently ignore the request when the file doesn't exist
if (!QFile::exists(path))
continue;

QMap<QString, int>::iterator entry = mWatchCount.find(path);
if (entry == mWatchCount.end()) {
pathsToAdd.append(path);
mWatchCount.insert(path, 1);
} else {
// Path is already being watched, increment watch count
++entry.value();
}
}

if (!pathsToAdd.isEmpty())
mWatcher->addPaths(pathsToAdd);
}

void FileSystemWatcher::removePath(const QString &path)
void FileSystemWatcher::removePaths(const QStringList &paths)
{
QMap<QString, int>::iterator entry = mWatchCount.find(path);
if (entry == mWatchCount.end()) {
if (QFile::exists(path))
qWarning() << "FileSystemWatcher: Path was never added:" << path;
return;
QStringList pathsToRemove;
pathsToRemove.reserve(paths.size());

for (const QString &path : paths) {
QMap<QString, int>::iterator entry = mWatchCount.find(path);
if (entry == mWatchCount.end()) {
if (QFile::exists(path))
qWarning() << "FileSystemWatcher: Path was never added:" << path;
continue;
}

// Decrement watch count
--entry.value();

if (entry.value() == 0) {
mWatchCount.erase(entry);
pathsToRemove.append(path);
}
}

// Decrement watch count
--entry.value();
if (!pathsToRemove.isEmpty())
mWatcher->removePaths(pathsToRemove);
}

void FileSystemWatcher::clear()
{
const QStringList files = mWatcher->files();
if (!files.isEmpty())
mWatcher->removePaths(files);

const QStringList directories = mWatcher->directories();
if (!directories.isEmpty())
mWatcher->removePaths(directories);

mWatchCount.clear();
}

if (entry.value() == 0) {
mWatchCount.erase(entry);
mWatcher->removePath(path);
void FileSystemWatcher::onFileChanged(const QString &path)
{
mChangedPaths.insert(path);
mChangedPathsTimer.start();

emit fileChanged(path);
}

void FileSystemWatcher::onDirectoryChanged(const QString &path)
{
mChangedPaths.insert(path);
mChangedPathsTimer.start();

emit directoryChanged(path);
}

void FileSystemWatcher::pathsChangedTimeout()
{
const auto changedPaths = mChangedPaths.values();

// If the file was replaced, the watcher is automatically removed and needs
// to be re-added to keep watching it for changes. This happens commonly
// with applications that do atomic saving.
for (const QString &path : changedPaths) {
if (mWatchCount.contains(path) && !mWatcher->files().contains(path)) {
if (QFile::exists(path))
mWatcher->addPath(path);
}
}


emit pathsChanged(changedPaths);

mChangedPaths.clear();
}

} // namespace Internal

} // namespace Tiled
79 changes: 61 additions & 18 deletions src/editor/filesystemwatcher.h
@@ -1,32 +1,44 @@
/*
* filesystemwatcher.h
* Copyright 2011, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
* Copyright 2011-2014, Thorbjørn Lindeijer <bjorn@lindeijer.nl>
*
* This file is part of Tiled.
* This file is part of libtiled.
*
* 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; either version 2 of the License, or (at your option)
* any later version.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 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.
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#ifndef FILESYSTEMWATCHER_H
#define FILESYSTEMWATCHER_H
#pragma once

#include "tiled_global.h"

#include <QMap>
#include <QObject>
#include <QSet>
#include <QTimer>

class QFileSystemWatcher;

namespace Tiled {

namespace Internal {

/**
Expand All @@ -35,27 +47,58 @@ namespace Internal {
* doesn't exist.
*
* It's meant to be used as drop-in replacement for QFileSystemWatcher.
*
* Optionally, the 'pathsChanged' signal can be used, which triggers at a delay
* to avoid problems occurring when trying to reload only partially written
* files, as well as avoiding fast consecutive reloads.
*/
class FileSystemWatcher : public QObject
class /*TILEDSHARED_EXPORT */FileSystemWatcher : public QObject
{
Q_OBJECT

public:
explicit FileSystemWatcher(QObject *parent = 0);
explicit FileSystemWatcher(QObject *parent = nullptr);

void addPath(const QString &path);
void addPaths(const QStringList &paths);
void removePath(const QString &path);
void removePaths(const QStringList &paths);
void clear();

signals:
// Emitted directly
void fileChanged(const QString &path);
void directoryChanged(const QString &path);

/**
* Emitted only after a short delay.
*
* May includes both files and directories.
*/
void pathsChanged(const QStringList &paths);

private:
void onFileChanged(const QString &path);
void onDirectoryChanged(const QString &path);
void pathsChangedTimeout();

QFileSystemWatcher *mWatcher;
QMap<QString, int> mWatchCount;

QSet<QString> mChangedPaths;
QTimer mChangedPathsTimer;
};

inline void FileSystemWatcher::addPath(const QString &path)
{
addPaths(QStringList(path));
}

inline void FileSystemWatcher::removePath(const QString &path)
{
removePaths(QStringList(path));
}

} // namespace Internal
} // namespace Tiled

#endif // FILESYSTEMWATCHER_H
} // namespace Tiled

0 comments on commit 722249a

Please sign in to comment.