2014-01-23 16:16:08 +04:00
|
|
|
/*
|
|
|
|
* This software is in the public domain, furnished "as is", without technical
|
|
|
|
* support, and with no warranty, express or implied, as to its usefulness for
|
|
|
|
* any purpose.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <QtTest>
|
|
|
|
|
2014-11-19 16:45:25 +03:00
|
|
|
#include "folderwatcher.h"
|
2017-08-16 09:36:52 +03:00
|
|
|
#include "common/utility.h"
|
2024-04-30 11:04:32 +03:00
|
|
|
#include "logger.h"
|
2014-01-23 16:16:08 +04:00
|
|
|
|
2017-02-07 23:30:55 +03:00
|
|
|
void touch(const QString &file)
|
|
|
|
{
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
OCC::Utility::writeRandomFile(file);
|
|
|
|
#else
|
|
|
|
QString cmd;
|
|
|
|
cmd = QString("touch %1").arg(file);
|
|
|
|
qDebug() << "Command: " << cmd;
|
|
|
|
system(cmd.toLocal8Bit());
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void mkdir(const QString &file)
|
|
|
|
{
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
QDir dir;
|
|
|
|
dir.mkdir(file);
|
|
|
|
#else
|
|
|
|
QString cmd = QString("mkdir %1").arg(file);
|
|
|
|
qDebug() << "Command: " << cmd;
|
|
|
|
system(cmd.toLocal8Bit());
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void rmdir(const QString &file)
|
|
|
|
{
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
QDir dir;
|
|
|
|
dir.rmdir(file);
|
|
|
|
#else
|
|
|
|
QString cmd = QString("rmdir %1").arg(file);
|
|
|
|
qDebug() << "Command: " << cmd;
|
|
|
|
system(cmd.toLocal8Bit());
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void rm(const QString &file)
|
|
|
|
{
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
QFile::remove(file);
|
|
|
|
#else
|
|
|
|
QString cmd = QString("rm %1").arg(file);
|
|
|
|
qDebug() << "Command: " << cmd;
|
|
|
|
system(cmd.toLocal8Bit());
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void mv(const QString &file1, const QString &file2)
|
|
|
|
{
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
QFile::rename(file1, file2);
|
|
|
|
#else
|
|
|
|
QString cmd = QString("mv %1 %2").arg(file1, file2);
|
|
|
|
qDebug() << "Command: " << cmd;
|
|
|
|
system(cmd.toLocal8Bit());
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2014-11-10 00:36:49 +03:00
|
|
|
using namespace OCC;
|
2014-01-23 16:16:08 +04:00
|
|
|
|
|
|
|
class TestFolderWatcher : public QObject
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
|
2017-02-07 23:30:55 +03:00
|
|
|
QTemporaryDir _root;
|
|
|
|
QString _rootPath;
|
|
|
|
QScopedPointer<FolderWatcher> _watcher;
|
|
|
|
QScopedPointer<QSignalSpy> _pathChangedSpy;
|
2014-11-06 02:36:04 +03:00
|
|
|
|
2017-02-07 23:30:55 +03:00
|
|
|
bool waitForPathChanged(const QString &path)
|
2014-11-06 02:36:04 +03:00
|
|
|
{
|
2017-02-07 23:30:55 +03:00
|
|
|
QElapsedTimer t;
|
|
|
|
t.start();
|
|
|
|
while (t.elapsed() < 5000) {
|
|
|
|
// Check if it was already reported as changed by the watcher
|
|
|
|
for (int i = 0; i < _pathChangedSpy->size(); ++i) {
|
|
|
|
const auto &args = _pathChangedSpy->at(i);
|
|
|
|
if (args.first().toString() == path)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// Wait a bit and test again (don't bother checking if we timed out or not)
|
|
|
|
_pathChangedSpy->wait(200);
|
|
|
|
}
|
|
|
|
return false;
|
2014-11-06 02:36:04 +03:00
|
|
|
}
|
2014-01-23 16:16:08 +04:00
|
|
|
|
2019-03-05 15:20:09 +03:00
|
|
|
#ifdef Q_OS_LINUX
|
|
|
|
#define CHECK_WATCH_COUNT(n) QCOMPARE(_watcher->testLinuxWatchCount(), (n))
|
|
|
|
#else
|
|
|
|
#define CHECK_WATCH_COUNT(n) do {} while (false)
|
|
|
|
#endif
|
|
|
|
|
2017-02-07 23:30:55 +03:00
|
|
|
public:
|
2021-09-03 22:37:46 +03:00
|
|
|
TestFolderWatcher()
|
|
|
|
{
|
2017-02-07 23:30:55 +03:00
|
|
|
QDir rootDir(_root.path());
|
|
|
|
_rootPath = rootDir.canonicalPath();
|
|
|
|
qDebug() << "creating test directory tree in " << _rootPath;
|
|
|
|
|
|
|
|
rootDir.mkpath("a1/b1/c1");
|
|
|
|
rootDir.mkpath("a1/b1/c2");
|
|
|
|
rootDir.mkpath("a1/b2/c1");
|
|
|
|
rootDir.mkpath("a1/b3/c3");
|
|
|
|
rootDir.mkpath("a2/b3/c3");
|
|
|
|
Utility::writeRandomFile( _rootPath+"/a1/random.bin");
|
|
|
|
Utility::writeRandomFile( _rootPath+"/a1/b2/todelete.bin");
|
|
|
|
Utility::writeRandomFile( _rootPath+"/a2/renamefile");
|
|
|
|
Utility::writeRandomFile( _rootPath+"/a1/movefile");
|
|
|
|
|
2018-04-24 10:52:15 +03:00
|
|
|
_watcher.reset(new FolderWatcher);
|
|
|
|
_watcher->init(_rootPath);
|
2023-02-17 19:06:35 +03:00
|
|
|
_pathChangedSpy.reset(new QSignalSpy(_watcher.data(), &FolderWatcher::pathChanged));
|
2014-11-06 02:36:04 +03:00
|
|
|
}
|
|
|
|
|
2019-03-05 15:20:09 +03:00
|
|
|
int countFolders(const QString &path)
|
|
|
|
{
|
|
|
|
int n = 0;
|
2022-10-24 18:53:40 +03:00
|
|
|
const auto entryList = QDir(path).entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
|
|
|
for (const auto &sub : entryList) {
|
2019-03-05 15:20:09 +03:00
|
|
|
n += 1 + countFolders(path + '/' + sub);
|
2022-10-24 18:53:40 +03:00
|
|
|
}
|
2019-03-05 15:20:09 +03:00
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
2017-02-07 23:30:55 +03:00
|
|
|
private slots:
|
2024-04-30 11:04:32 +03:00
|
|
|
void initTestCase()
|
|
|
|
{
|
|
|
|
OCC::Logger::instance()->setLogFlush(true);
|
|
|
|
OCC::Logger::instance()->setLogDebug(true);
|
|
|
|
|
|
|
|
QStandardPaths::setTestModeEnabled(true);
|
|
|
|
}
|
|
|
|
|
2014-11-06 02:36:04 +03:00
|
|
|
void init()
|
|
|
|
{
|
2017-02-07 23:30:55 +03:00
|
|
|
_pathChangedSpy->clear();
|
2019-03-05 15:20:09 +03:00
|
|
|
CHECK_WATCH_COUNT(countFolders(_rootPath) + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void cleanup()
|
|
|
|
{
|
|
|
|
CHECK_WATCH_COUNT(countFolders(_rootPath) + 1);
|
2014-01-23 16:16:08 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void testACreate() { // create a new file
|
2017-02-07 23:30:55 +03:00
|
|
|
QString file(_rootPath + "/foo.txt");
|
2014-01-23 16:16:08 +04:00
|
|
|
QString cmd;
|
2022-11-09 18:36:38 +03:00
|
|
|
cmd = QString("echo \"xyz\" > \"%1\"").arg(file);
|
2014-01-23 16:16:08 +04:00
|
|
|
qDebug() << "Command: " << cmd;
|
|
|
|
system(cmd.toLocal8Bit());
|
|
|
|
|
2017-02-07 23:30:55 +03:00
|
|
|
QVERIFY(waitForPathChanged(file));
|
2014-01-23 16:16:08 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void testATouch() { // touch an existing file.
|
2017-02-07 23:30:55 +03:00
|
|
|
QString file(_rootPath + "/a1/random.bin");
|
|
|
|
touch(file);
|
|
|
|
QVERIFY(waitForPathChanged(file));
|
2014-01-23 16:16:08 +04:00
|
|
|
}
|
|
|
|
|
2019-05-09 11:05:49 +03:00
|
|
|
void testMove3LevelDirWithFile() {
|
|
|
|
QString file(_rootPath + "/a0/b/c/empty.txt");
|
|
|
|
mkdir(_rootPath + "/a0");
|
|
|
|
mkdir(_rootPath + "/a0/b");
|
|
|
|
mkdir(_rootPath + "/a0/b/c");
|
|
|
|
touch(file);
|
2019-10-08 09:31:31 +03:00
|
|
|
mv(_rootPath + "/a0", _rootPath + "/a");
|
2019-05-09 11:05:49 +03:00
|
|
|
QVERIFY(waitForPathChanged(_rootPath + "/a/b/c/empty.txt"));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-01-23 16:16:08 +04:00
|
|
|
void testCreateADir() {
|
2017-02-07 23:30:55 +03:00
|
|
|
QString file(_rootPath+"/a1/b1/new_dir");
|
|
|
|
mkdir(file);
|
|
|
|
QVERIFY(waitForPathChanged(file));
|
2019-03-05 15:20:09 +03:00
|
|
|
|
|
|
|
// Notifications from that new folder arrive too
|
|
|
|
QString file2(_rootPath + "/a1/b1/new_dir/contained");
|
|
|
|
touch(file2);
|
|
|
|
QVERIFY(waitForPathChanged(file2));
|
2014-01-23 16:16:08 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void testRemoveADir() {
|
2017-02-07 23:30:55 +03:00
|
|
|
QString file(_rootPath+"/a1/b3/c3");
|
|
|
|
rmdir(file);
|
|
|
|
QVERIFY(waitForPathChanged(file));
|
2014-01-23 16:16:08 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void testRemoveAFile() {
|
2017-02-07 23:30:55 +03:00
|
|
|
QString file(_rootPath+"/a1/b2/todelete.bin");
|
2014-11-07 12:53:41 +03:00
|
|
|
QVERIFY(QFile::exists(file));
|
2017-02-07 23:30:55 +03:00
|
|
|
rm(file);
|
2014-11-07 12:53:41 +03:00
|
|
|
QVERIFY(!QFile::exists(file));
|
2014-11-06 02:36:04 +03:00
|
|
|
|
2017-02-07 23:30:55 +03:00
|
|
|
QVERIFY(waitForPathChanged(file));
|
2014-11-06 02:36:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void testRenameAFile() {
|
2017-02-07 23:30:55 +03:00
|
|
|
QString file1(_rootPath+"/a2/renamefile");
|
|
|
|
QString file2(_rootPath+"/a2/renamefile.renamed");
|
2014-11-07 12:53:41 +03:00
|
|
|
QVERIFY(QFile::exists(file1));
|
2017-02-07 23:30:55 +03:00
|
|
|
mv(file1, file2);
|
2014-11-07 12:53:41 +03:00
|
|
|
QVERIFY(QFile::exists(file2));
|
2014-11-06 02:36:04 +03:00
|
|
|
|
2017-02-07 23:30:55 +03:00
|
|
|
QVERIFY(waitForPathChanged(file1));
|
|
|
|
QVERIFY(waitForPathChanged(file2));
|
2014-01-23 16:16:08 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void testMoveAFile() {
|
2017-02-07 23:30:55 +03:00
|
|
|
QString old_file(_rootPath+"/a1/movefile");
|
|
|
|
QString new_file(_rootPath+"/a2/movefile.renamed");
|
2014-11-07 12:53:41 +03:00
|
|
|
QVERIFY(QFile::exists(old_file));
|
2017-02-07 23:30:55 +03:00
|
|
|
mv(old_file, new_file);
|
2014-11-07 12:53:41 +03:00
|
|
|
QVERIFY(QFile::exists(new_file));
|
2014-11-06 02:36:04 +03:00
|
|
|
|
2017-02-07 23:30:55 +03:00
|
|
|
QVERIFY(waitForPathChanged(old_file));
|
|
|
|
QVERIFY(waitForPathChanged(new_file));
|
2014-01-23 16:16:08 +04:00
|
|
|
}
|
2018-10-12 12:03:10 +03:00
|
|
|
|
|
|
|
void testRenameDirectorySameBase() {
|
|
|
|
QString old_file(_rootPath+"/a1/b1");
|
|
|
|
QString new_file(_rootPath+"/a1/brename");
|
|
|
|
QVERIFY(QFile::exists(old_file));
|
|
|
|
mv(old_file, new_file);
|
|
|
|
QVERIFY(QFile::exists(new_file));
|
|
|
|
|
|
|
|
QVERIFY(waitForPathChanged(old_file));
|
|
|
|
QVERIFY(waitForPathChanged(new_file));
|
|
|
|
|
|
|
|
// Verify that further notifications end up with the correct paths
|
|
|
|
|
|
|
|
QString file(_rootPath+"/a1/brename/c1/random.bin");
|
|
|
|
touch(file);
|
|
|
|
QVERIFY(waitForPathChanged(file));
|
|
|
|
|
|
|
|
QString dir(_rootPath+"/a1/brename/newfolder");
|
|
|
|
mkdir(dir);
|
|
|
|
QVERIFY(waitForPathChanged(dir));
|
|
|
|
}
|
|
|
|
|
|
|
|
void testRenameDirectoryDifferentBase() {
|
|
|
|
QString old_file(_rootPath+"/a1/brename");
|
|
|
|
QString new_file(_rootPath+"/bren");
|
|
|
|
QVERIFY(QFile::exists(old_file));
|
|
|
|
mv(old_file, new_file);
|
|
|
|
QVERIFY(QFile::exists(new_file));
|
|
|
|
|
|
|
|
QVERIFY(waitForPathChanged(old_file));
|
|
|
|
QVERIFY(waitForPathChanged(new_file));
|
|
|
|
|
|
|
|
// Verify that further notifications end up with the correct paths
|
|
|
|
|
|
|
|
QString file(_rootPath+"/bren/c1/random.bin");
|
|
|
|
touch(file);
|
|
|
|
QVERIFY(waitForPathChanged(file));
|
|
|
|
|
|
|
|
QString dir(_rootPath+"/bren/newfolder2");
|
|
|
|
mkdir(dir);
|
|
|
|
QVERIFY(waitForPathChanged(dir));
|
|
|
|
}
|
2024-04-03 19:10:49 +03:00
|
|
|
|
|
|
|
void testDetectLockFiles()
|
|
|
|
{
|
|
|
|
QStringList listOfOfficeFiles = {QString(_rootPath + "/document.docx"), QString(_rootPath + "/document.odt")};
|
|
|
|
std::sort(std::begin(listOfOfficeFiles), std::end(listOfOfficeFiles));
|
|
|
|
|
|
|
|
const QStringList listOfOfficeLockFiles = {QString(_rootPath + "/.~lock.document.docx#"), QString(_rootPath + "/.~lock.document.odt#")};
|
|
|
|
|
|
|
|
_watcher->setShouldWatchForFileUnlocking(true);
|
|
|
|
|
|
|
|
// verify that office files for locking got detected by the watcher
|
|
|
|
QScopedPointer<QSignalSpy> locksImposedSpy(new QSignalSpy(_watcher.data(), &FolderWatcher::filesLockImposed));
|
|
|
|
|
|
|
|
for (const auto &officeFile : listOfOfficeFiles) {
|
|
|
|
touch(officeFile);
|
|
|
|
QVERIFY(waitForPathChanged(officeFile));
|
|
|
|
}
|
|
|
|
for (const auto &officeLockFile : listOfOfficeLockFiles) {
|
|
|
|
touch(officeLockFile);
|
|
|
|
QVERIFY(waitForPathChanged(officeLockFile));
|
|
|
|
}
|
|
|
|
|
|
|
|
locksImposedSpy->wait(_watcher->lockChangeDebouncingTimout() + 100);
|
|
|
|
QCOMPARE(locksImposedSpy->count(), 1);
|
|
|
|
auto lockedOfficeFilesByWatcher = locksImposedSpy->takeFirst().at(0).value<QSet<QString>>().values();
|
|
|
|
std::sort(std::begin(lockedOfficeFilesByWatcher), std::end(lockedOfficeFilesByWatcher));
|
|
|
|
QCOMPARE(listOfOfficeFiles.size(), lockedOfficeFilesByWatcher.size());
|
|
|
|
|
|
|
|
for (int i = 0; i < listOfOfficeFiles.size(); ++i) {
|
|
|
|
QVERIFY(listOfOfficeFiles.at(i) == lockedOfficeFilesByWatcher.at(i));
|
|
|
|
}
|
|
|
|
|
|
|
|
// verify that office files for unlocking got detected by the watcher
|
|
|
|
QScopedPointer<QSignalSpy> locksReleasedSpy(new QSignalSpy(_watcher.data(), &FolderWatcher::filesLockReleased));
|
|
|
|
|
|
|
|
for (const auto &officeLockFile : listOfOfficeLockFiles) {
|
|
|
|
rm(officeLockFile);
|
|
|
|
QVERIFY(waitForPathChanged(officeLockFile));
|
|
|
|
}
|
|
|
|
|
|
|
|
locksReleasedSpy->wait(_watcher->lockChangeDebouncingTimout() + 100);
|
|
|
|
QCOMPARE(locksReleasedSpy->count(), 1);
|
|
|
|
auto unLockedOfficeFilesByWatcher = locksReleasedSpy->takeFirst().at(0).value<QSet<QString>>().values();
|
|
|
|
std::sort(std::begin(unLockedOfficeFilesByWatcher), std::end(unLockedOfficeFilesByWatcher));
|
|
|
|
QCOMPARE(listOfOfficeFiles.size(), unLockedOfficeFilesByWatcher.size());
|
|
|
|
|
|
|
|
for (int i = 0; i < listOfOfficeFiles.size(); ++i) {
|
|
|
|
QVERIFY(listOfOfficeFiles.at(i) == unLockedOfficeFilesByWatcher.at(i));
|
|
|
|
}
|
|
|
|
|
|
|
|
// cleanup
|
|
|
|
for (const auto &officeLockFile : listOfOfficeLockFiles) {
|
|
|
|
rm(officeLockFile);
|
|
|
|
}
|
|
|
|
for (const auto &officeFile : listOfOfficeFiles) {
|
|
|
|
rm(officeFile);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void testDetectLockFilesExternally()
|
|
|
|
{
|
|
|
|
QStringList listOfOfficeFiles = {QString(_rootPath + "/document.docx"), QString(_rootPath + "/document.odt")};
|
|
|
|
std::sort(std::begin(listOfOfficeFiles), std::end(listOfOfficeFiles));
|
|
|
|
|
|
|
|
const QStringList listOfOfficeLockFiles = {QString(_rootPath + "/.~lock.document.docx#"), QString(_rootPath + "/.~lock.document.odt#")};
|
|
|
|
|
|
|
|
for (const auto &officeFile : listOfOfficeFiles) {
|
|
|
|
touch(officeFile);
|
|
|
|
}
|
|
|
|
for (const auto &officeLockFile : listOfOfficeLockFiles) {
|
|
|
|
touch(officeLockFile);
|
|
|
|
}
|
|
|
|
|
|
|
|
_watcher.reset(new FolderWatcher);
|
|
|
|
_watcher->init(_rootPath);
|
|
|
|
_watcher->setShouldWatchForFileUnlocking(true);
|
|
|
|
_pathChangedSpy.reset(new QSignalSpy(_watcher.data(), &FolderWatcher::pathChanged));
|
|
|
|
QScopedPointer<QSignalSpy> locksImposedSpy(new QSignalSpy(_watcher.data(), &FolderWatcher::filesLockImposed));
|
|
|
|
QScopedPointer<QSignalSpy> locksReleasedSpy(new QSignalSpy(_watcher.data(), &FolderWatcher::filesLockReleased));
|
|
|
|
|
|
|
|
for (const auto &officeLockFile : listOfOfficeLockFiles) {
|
|
|
|
_watcher->slotLockFileDetectedExternally(officeLockFile);
|
|
|
|
}
|
|
|
|
|
|
|
|
// locked files detected
|
|
|
|
locksImposedSpy->wait(_watcher->lockChangeDebouncingTimout() + 100);
|
|
|
|
QCOMPARE(locksImposedSpy->count(), 1);
|
|
|
|
auto lockedOfficeFilesByWatcher = locksImposedSpy->takeFirst().at(0).value<QSet<QString>>().values();
|
|
|
|
std::sort(std::begin(lockedOfficeFilesByWatcher), std::end(lockedOfficeFilesByWatcher));
|
|
|
|
QCOMPARE(listOfOfficeFiles.size(), lockedOfficeFilesByWatcher.size());
|
|
|
|
for (int i = 0; i < listOfOfficeFiles.size(); ++i) {
|
|
|
|
QVERIFY(listOfOfficeFiles.at(i) == lockedOfficeFilesByWatcher.at(i));
|
|
|
|
}
|
|
|
|
|
|
|
|
// unlocked files detected
|
|
|
|
for (const auto &officeLockFile : listOfOfficeLockFiles) {
|
|
|
|
rm(officeLockFile);
|
|
|
|
}
|
|
|
|
locksReleasedSpy->wait(_watcher->lockChangeDebouncingTimout() + 100);
|
|
|
|
QCOMPARE(locksReleasedSpy->count(), 1);
|
|
|
|
auto unLockedOfficeFilesByWatcher = locksReleasedSpy->takeFirst().at(0).value<QSet<QString>>().values();
|
|
|
|
std::sort(std::begin(unLockedOfficeFilesByWatcher), std::end(unLockedOfficeFilesByWatcher));
|
|
|
|
QCOMPARE(listOfOfficeFiles.size(), unLockedOfficeFilesByWatcher.size());
|
|
|
|
|
|
|
|
for (int i = 0; i < listOfOfficeFiles.size(); ++i) {
|
|
|
|
QVERIFY(listOfOfficeFiles.at(i) == unLockedOfficeFilesByWatcher.at(i));
|
|
|
|
}
|
|
|
|
|
|
|
|
// cleanup
|
|
|
|
for (const auto &officeFile : listOfOfficeFiles) {
|
|
|
|
rm(officeFile);
|
|
|
|
}
|
|
|
|
for (const auto &officeLockFile : listOfOfficeLockFiles) {
|
|
|
|
rm(officeLockFile);
|
|
|
|
}
|
|
|
|
}
|
2014-01-23 16:16:08 +04:00
|
|
|
};
|
|
|
|
|
2017-02-10 17:12:03 +03:00
|
|
|
#ifdef Q_OS_MAC
|
2017-02-07 23:30:55 +03:00
|
|
|
QTEST_MAIN(TestFolderWatcher)
|
2016-05-11 02:17:16 +03:00
|
|
|
#else
|
|
|
|
QTEST_GUILESS_MAIN(TestFolderWatcher)
|
|
|
|
#endif
|
|
|
|
|
2016-03-30 18:58:15 +03:00
|
|
|
#include "testfolderwatcher.moc"
|