Browse Source

Merge pull request #8615 from Chocobo1/watcher

Improve FS watcher
adaptive-webui-19844
Mike Tzou 7 years ago committed by GitHub
parent
commit
aa122b062e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 268
      src/base/filesystemwatcher.cpp
  2. 52
      src/base/filesystemwatcher.h
  3. 35
      src/base/utils/fs.cpp
  4. 4
      src/base/utils/fs.h

268
src/base/filesystemwatcher.cpp

@ -1,145 +1,128 @@
#include <QtGlobal> /*
#ifndef Q_OS_WIN * Bittorrent Client using Qt and libtorrent.
#include <QSet> * Copyright (C) 2018
#include <iostream> *
#include <errno.h> * 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.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#include "filesystemwatcher.h"
#if defined(Q_OS_MAC) || defined(Q_OS_FREEBSD) #if defined(Q_OS_MAC) || defined(Q_OS_FREEBSD)
#include <sys/param.h> #include <cstring>
#include <sys/mount.h> #include <sys/mount.h>
#include <string.h> #include <sys/param.h>
#elif !defined Q_OS_HAIKU
#include <sys/vfs.h>
#endif
#endif #endif
#include "algorithm.h" #include <QtGlobal>
#include "base/algorithm.h"
#include "base/bittorrent/magneturi.h" #include "base/bittorrent/magneturi.h"
#include "base/bittorrent/torrentinfo.h" #include "base/bittorrent/torrentinfo.h"
#include "base/global.h"
#include "base/preferences.h" #include "base/preferences.h"
#include "filesystemwatcher.h" #include "base/utils/fs.h"
#ifndef CIFS_MAGIC_NUMBER
#define CIFS_MAGIC_NUMBER 0xFF534D42
#endif
#ifndef NFS_SUPER_MAGIC
#define NFS_SUPER_MAGIC 0x6969
#endif
#ifndef SMB_SUPER_MAGIC
#define SMB_SUPER_MAGIC 0x517B
#endif
const int WATCH_INTERVAL = 10000; // 10 sec namespace
const int MAX_PARTIAL_RETRIES = 5; {
const int WATCH_INTERVAL = 10000; // 10 sec
const int MAX_PARTIAL_RETRIES = 5;
}
FileSystemWatcher::FileSystemWatcher(QObject *parent) FileSystemWatcher::FileSystemWatcher(QObject *parent)
: QFileSystemWatcher(parent) : QFileSystemWatcher(parent)
{ {
m_filters << "*.torrent" << "*.magnet"; connect(this, &QFileSystemWatcher::directoryChanged, this, &FileSystemWatcher::scanLocalFolder);
connect(this, SIGNAL(directoryChanged(QString)), SLOT(scanLocalFolder(QString)));
} m_partialTorrentTimer.setSingleShot(true);
connect(&m_partialTorrentTimer, &QTimer::timeout, this, &FileSystemWatcher::processPartialTorrents);
FileSystemWatcher::~FileSystemWatcher()
{
#ifndef Q_OS_WIN #ifndef Q_OS_WIN
if (m_watchTimer) connect(&m_watchTimer, &QTimer::timeout, this, &FileSystemWatcher::scanNetworkFolders);
delete m_watchTimer;
#endif #endif
if (m_partialTorrentTimer)
delete m_partialTorrentTimer;
} }
QStringList FileSystemWatcher::directories() const QStringList FileSystemWatcher::directories() const
{ {
QStringList dirs; QStringList dirs = QFileSystemWatcher::directories();
#ifndef Q_OS_WIN #ifndef Q_OS_WIN
if (m_watchTimer) { for (const QDir &dir : qAsConst(m_watchedFolders))
foreach (const QDir &dir, m_watchedFolders) dirs << dir.canonicalPath();
dirs << dir.canonicalPath();
}
#endif #endif
dirs << QFileSystemWatcher::directories();
return dirs; return dirs;
} }
void FileSystemWatcher::addPath(const QString &path) void FileSystemWatcher::addPath(const QString &path)
{ {
if (path.isEmpty()) return;
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU #if !defined Q_OS_WIN && !defined Q_OS_HAIKU
QDir dir(path); const QDir dir(path);
if (!dir.exists()) return; if (!dir.exists()) return;
// Check if the path points to a network file system or not // Check if the path points to a network file system or not
if (isNetworkFileSystem(path)) { if (Utils::Fs::isNetworkFileSystem(path)) {
// Network mode // Network mode
qDebug("Network folder detected: %s", qUtf8Printable(path)); qDebug("Network folder detected: %s", qUtf8Printable(path));
qDebug("Using file polling mode instead of inotify..."); qDebug("Using file polling mode instead of inotify...");
m_watchedFolders << dir; m_watchedFolders << dir;
// Set up the watch timer
if (!m_watchTimer) { m_watchTimer.start(WATCH_INTERVAL);
m_watchTimer = new QTimer(this); return;
connect(m_watchTimer, SIGNAL(timeout()), SLOT(scanNetworkFolders()));
m_watchTimer->start(WATCH_INTERVAL); // 5 sec
}
}
else {
#endif
// Normal mode
qDebug("FS Watching is watching %s in normal mode", qUtf8Printable(path));
QFileSystemWatcher::addPath(path);
scanLocalFolder(path);
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
} }
#endif #endif
// Normal mode
qDebug("FS Watcher is watching %s in normal mode", qUtf8Printable(path));
QFileSystemWatcher::addPath(path);
scanLocalFolder(path);
} }
void FileSystemWatcher::removePath(const QString &path) void FileSystemWatcher::removePath(const QString &path)
{ {
#ifndef Q_OS_WIN #ifndef Q_OS_WIN
QDir dir(path); if (m_watchedFolders.removeOne(path)) {
for (int i = 0; i < m_watchedFolders.count(); ++i) { if (m_watchedFolders.isEmpty())
if (QDir(m_watchedFolders.at(i)) == dir) { m_watchTimer.stop();
m_watchedFolders.removeAt(i); return;
if (m_watchedFolders.isEmpty())
delete m_watchTimer;
return;
}
} }
#endif #endif
// Normal mode // Normal mode
QFileSystemWatcher::removePath(path); QFileSystemWatcher::removePath(path);
} }
void FileSystemWatcher::scanLocalFolder(QString path) void FileSystemWatcher::scanLocalFolder(const QString &path)
{ {
qDebug("scanLocalFolder(%s) called", qUtf8Printable(path)); QTimer::singleShot(2000, this, [this, path]() { processTorrentsInDir(path); });
QStringList torrents;
// Local folders scan
addTorrentsFromDir(QDir(path), torrents);
// Report detected torrent files
if (!torrents.empty()) {
qDebug("The following files are being reported: %s", qUtf8Printable(torrents.join("\n")));
emit torrentsAdded(torrents);
}
} }
#ifndef Q_OS_WIN
void FileSystemWatcher::scanNetworkFolders() void FileSystemWatcher::scanNetworkFolders()
{ {
#ifndef Q_OS_WIN for (const QDir &dir : qAsConst(m_watchedFolders))
qDebug("scanNetworkFolders() called"); processTorrentsInDir(dir);
QStringList torrents;
// Network folders scan
foreach (const QDir &dir, m_watchedFolders) {
//qDebug("FSWatcher: Polling manually folder %s", qUtf8Printable(dir.path()));
addTorrentsFromDir(dir, torrents);
}
// Report detected torrent files
if (!torrents.empty()) {
qDebug("The following files are being reported: %s", qUtf8Printable(torrents.join("\n")));
emit torrentsAdded(torrents);
}
#endif
} }
#endif
void FileSystemWatcher::processPartialTorrents() void FileSystemWatcher::processPartialTorrents()
{ {
@ -157,7 +140,7 @@ void FileSystemWatcher::processPartialTorrents()
} }
if (value >= MAX_PARTIAL_RETRIES) { if (value >= MAX_PARTIAL_RETRIES) {
QFile::rename(torrentPath, torrentPath + ".invalid"); QFile::rename(torrentPath, torrentPath + ".qbt_rejected");
return true; return true;
} }
@ -167,13 +150,12 @@ void FileSystemWatcher::processPartialTorrents()
// Stop the partial timer if necessary // Stop the partial timer if necessary
if (m_partialTorrents.empty()) { if (m_partialTorrents.empty()) {
m_partialTorrentTimer->stop(); m_partialTorrentTimer.stop();
m_partialTorrentTimer->deleteLater();
qDebug("No longer any partial torrent."); qDebug("No longer any partial torrent.");
} }
else { else {
qDebug("Still %d partial torrents after delayed processing.", m_partialTorrents.count()); qDebug("Still %d partial torrents after delayed processing.", m_partialTorrents.count());
m_partialTorrentTimer->start(WATCH_INTERVAL); m_partialTorrentTimer.start(WATCH_INTERVAL);
} }
// Notify of new torrents // Notify of new torrents
@ -181,99 +163,23 @@ void FileSystemWatcher::processPartialTorrents()
emit torrentsAdded(noLongerPartial); emit torrentsAdded(noLongerPartial);
} }
void FileSystemWatcher::startPartialTorrentTimer() void FileSystemWatcher::processTorrentsInDir(const QDir &dir)
{
Q_ASSERT(!m_partialTorrents.isEmpty());
if (!m_partialTorrentTimer) {
m_partialTorrentTimer = new QTimer();
connect(m_partialTorrentTimer, SIGNAL(timeout()), SLOT(processPartialTorrents()));
m_partialTorrentTimer->setSingleShot(true);
m_partialTorrentTimer->start(WATCH_INTERVAL);
}
}
void FileSystemWatcher::addTorrentsFromDir(const QDir &dir, QStringList &torrents)
{ {
const QStringList files = dir.entryList(m_filters, QDir::Files, QDir::Unsorted); QStringList torrents;
foreach (const QString &file, files) { const QStringList files = dir.entryList({"*.torrent", "*.magnet"}, QDir::Files);
for (const QString &file : files) {
const QString fileAbsPath = dir.absoluteFilePath(file); const QString fileAbsPath = dir.absoluteFilePath(file);
if (fileAbsPath.endsWith(".magnet")) { if (file.endsWith(".magnet"))
torrents << fileAbsPath; torrents << fileAbsPath;
} else if (BitTorrent::TorrentInfo::loadFromFile(fileAbsPath).isValid())
else if (BitTorrent::TorrentInfo::loadFromFile(fileAbsPath).isValid()) {
torrents << fileAbsPath; torrents << fileAbsPath;
} else if (!m_partialTorrents.contains(fileAbsPath))
else if (!m_partialTorrents.contains(fileAbsPath)) { m_partialTorrents[fileAbsPath] = 0;
qDebug("Partial torrent detected at: %s", qUtf8Printable(fileAbsPath));
qDebug("Delay the file's processing...");
m_partialTorrents.insert(fileAbsPath, 0);
}
} }
if (!m_partialTorrents.empty()) if (!torrents.empty())
startPartialTorrentTimer(); emit torrentsAdded(torrents);
}
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
bool FileSystemWatcher::isNetworkFileSystem(QString path)
{
QString file = path;
if (!file.endsWith("/"))
file += "/";
file += ".";
struct statfs buf;
if (!statfs(file.toLocal8Bit().constData(), &buf)) {
#ifdef Q_OS_MAC
// XXX: should we make sure HAVE_STRUCT_FSSTAT_F_FSTYPENAME is defined?
return ((strcmp(buf.f_fstypename, "nfs") == 0) || (strcmp(buf.f_fstypename, "cifs") == 0) || (strcmp(buf.f_fstypename, "smbfs") == 0));
#else
return ((buf.f_type == static_cast<long>(CIFS_MAGIC_NUMBER))
|| (buf.f_type == static_cast<long>(NFS_SUPER_MAGIC))
|| (buf.f_type == static_cast<long>(SMB_SUPER_MAGIC)));
#endif
}
else {
std::cerr << "Error: statfs() call failed for " << qPrintable(file) << ". Supposing it is a local folder..." << std::endl;
switch(errno) {
case EACCES:
std::cerr << "Search permission is denied for a component of the path prefix of the path" << std::endl;
break;
case EFAULT:
std::cerr << "Buf or path points to an invalid address" << std::endl;
break;
case EINTR:
std::cerr << "This call was interrupted by a signal" << std::endl;
break;
case EIO:
std::cerr << "I/O Error" << std::endl;
break;
case ELOOP:
std::cerr << "Too many symlinks" << std::endl;
break;
case ENAMETOOLONG:
std::cerr << "path is too long" << std::endl;
break;
case ENOENT:
std::cerr << "The file referred by path does not exist" << std::endl;
break;
case ENOMEM:
std::cerr << "Insufficient kernel memory" << std::endl;
break;
case ENOSYS:
std::cerr << "The file system does not detect this call" << std::endl;
break;
case ENOTDIR:
std::cerr << "A component of the path is not a directory" << std::endl;
break;
case EOVERFLOW:
std::cerr << "Some values were too large to be represented in the struct" << std::endl;
break;
default:
std::cerr << "Unknown error" << std::endl;
}
std::cerr << "Errno: " << errno << std::endl; if (!m_partialTorrents.empty() && !m_partialTorrentTimer.isActive())
return false; m_partialTorrentTimer.start(WATCH_INTERVAL);
}
} }
#endif

52
src/base/filesystemwatcher.h

@ -1,10 +1,37 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018
*
* 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.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#ifndef FILESYSTEMWATCHER_H #ifndef FILESYSTEMWATCHER_H
#define FILESYSTEMWATCHER_H #define FILESYSTEMWATCHER_H
#include <QDir> #include <QDir>
#include <QFileSystemWatcher> #include <QFileSystemWatcher>
#include <QHash> #include <QHash>
#include <QPointer>
#include <QStringList> #include <QStringList>
#include <QTimer> #include <QTimer>
@ -18,7 +45,6 @@ class FileSystemWatcher : public QFileSystemWatcher
public: public:
explicit FileSystemWatcher(QObject *parent = nullptr); explicit FileSystemWatcher(QObject *parent = nullptr);
~FileSystemWatcher();
QStringList directories() const; QStringList directories() const;
void addPath(const QString &path); void addPath(const QString &path);
@ -28,25 +54,23 @@ signals:
void torrentsAdded(const QStringList &pathList); void torrentsAdded(const QStringList &pathList);
protected slots: protected slots:
void scanLocalFolder(QString path); void scanLocalFolder(const QString &path);
void scanNetworkFolders();
void processPartialTorrents(); void processPartialTorrents();
#ifndef Q_OS_WIN
void scanNetworkFolders();
#endif
private: private:
void startPartialTorrentTimer(); void processTorrentsInDir(const QDir &dir);
void addTorrentsFromDir(const QDir &dir, QStringList &torrents);
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU // Partial torrents
static bool isNetworkFileSystem(QString path); QHash<QString, int> m_partialTorrents;
#endif QTimer m_partialTorrentTimer;
#ifndef Q_OS_WIN #ifndef Q_OS_WIN
QList<QDir> m_watchedFolders; QList<QDir> m_watchedFolders;
QPointer<QTimer> m_watchTimer; QTimer m_watchTimer;
#endif #endif
QStringList m_filters;
// Partial torrents
QHash<QString, int> m_partialTorrents;
QPointer<QTimer> m_partialTorrentTimer;
}; };
#endif // FILESYSTEMWATCHER_H #endif // FILESYSTEMWATCHER_H

35
src/base/utils/fs.cpp

@ -31,8 +31,6 @@
#include "fs.h" #include "fs.h"
#include <cstring> #include <cstring>
#include <sys/stat.h>
#include <sys/types.h>
#include <QDebug> #include <QDebug>
#include <QDir> #include <QDir>
@ -42,6 +40,9 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QStorageInfo> #include <QStorageInfo>
#include <sys/stat.h>
#include <sys/types.h>
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
#include <Windows.h> #include <Windows.h>
#elif defined(Q_OS_MAC) || defined(Q_OS_FREEBSD) #elif defined(Q_OS_MAC) || defined(Q_OS_FREEBSD)
@ -301,3 +302,33 @@ bool Utils::Fs::isRegularFile(const QString &path)
return (st.st_mode & S_IFMT) == S_IFREG; return (st.st_mode & S_IFMT) == S_IFREG;
} }
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
bool Utils::Fs::isNetworkFileSystem(const QString &path)
{
QString file = path;
if (!file.endsWith('/'))
file += '/';
file += '.';
struct statfs buf {};
if (statfs(file.toLocal8Bit().constData(), &buf) != 0)
return false;
#ifdef Q_OS_MAC
// XXX: should we make sure HAVE_STRUCT_FSSTAT_F_FSTYPENAME is defined?
return ((strncmp(buf.f_fstypename, "nfs", sizeof(buf.f_fstypename)) == 0)
|| (strncmp(buf.f_fstypename, "cifs", sizeof(buf.f_fstypename)) == 0)
|| (strncmp(buf.f_fstypename, "smbfs", sizeof(buf.f_fstypename)) == 0));
#else
// usually defined in /usr/include/linux/magic.h
const unsigned long CIFS_MAGIC_NUMBER = 0xFF534D42;
const unsigned long NFS_SUPER_MAGIC = 0x6969;
const unsigned long SMB_SUPER_MAGIC = 0x517B;
return ((buf.f_type == CIFS_MAGIC_NUMBER)
|| (buf.f_type == NFS_SUPER_MAGIC)
|| (buf.f_type == SMB_SUPER_MAGIC));
#endif
}
#endif

4
src/base/utils/fs.h

@ -63,6 +63,10 @@ namespace Utils
void removeDirRecursive(const QString &path); void removeDirRecursive(const QString &path);
QString tempPath(); QString tempPath();
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
bool isNetworkFileSystem(const QString &path);
#endif
} }
} }

Loading…
Cancel
Save