diff --git a/src/base/filesystemwatcher.cpp b/src/base/filesystemwatcher.cpp index 33aef074a..78efa4693 100644 --- a/src/base/filesystemwatcher.cpp +++ b/src/base/filesystemwatcher.cpp @@ -1,145 +1,128 @@ -#include -#ifndef Q_OS_WIN -#include -#include -#include +/* + * 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. + */ + +#include "filesystemwatcher.h" + #if defined(Q_OS_MAC) || defined(Q_OS_FREEBSD) -#include +#include #include -#include -#elif !defined Q_OS_HAIKU -#include -#endif +#include #endif -#include "algorithm.h" +#include + +#include "base/algorithm.h" #include "base/bittorrent/magneturi.h" #include "base/bittorrent/torrentinfo.h" +#include "base/global.h" #include "base/preferences.h" -#include "filesystemwatcher.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 +#include "base/utils/fs.h" -const int WATCH_INTERVAL = 10000; // 10 sec -const int MAX_PARTIAL_RETRIES = 5; +namespace +{ + const int WATCH_INTERVAL = 10000; // 10 sec + const int MAX_PARTIAL_RETRIES = 5; +} FileSystemWatcher::FileSystemWatcher(QObject *parent) : QFileSystemWatcher(parent) { - m_filters << "*.torrent" << "*.magnet"; - connect(this, SIGNAL(directoryChanged(QString)), SLOT(scanLocalFolder(QString))); -} + connect(this, &QFileSystemWatcher::directoryChanged, this, &FileSystemWatcher::scanLocalFolder); + + m_partialTorrentTimer.setSingleShot(true); + connect(&m_partialTorrentTimer, &QTimer::timeout, this, &FileSystemWatcher::processPartialTorrents); -FileSystemWatcher::~FileSystemWatcher() -{ #ifndef Q_OS_WIN - if (m_watchTimer) - delete m_watchTimer; + connect(&m_watchTimer, &QTimer::timeout, this, &FileSystemWatcher::scanNetworkFolders); #endif - if (m_partialTorrentTimer) - delete m_partialTorrentTimer; } QStringList FileSystemWatcher::directories() const { - QStringList dirs; + QStringList dirs = QFileSystemWatcher::directories(); #ifndef Q_OS_WIN - if (m_watchTimer) { - foreach (const QDir &dir, m_watchedFolders) - dirs << dir.canonicalPath(); - } + for (const QDir &dir : qAsConst(m_watchedFolders)) + dirs << dir.canonicalPath(); #endif - dirs << QFileSystemWatcher::directories(); return dirs; } void FileSystemWatcher::addPath(const QString &path) { + if (path.isEmpty()) return; + #if !defined Q_OS_WIN && !defined Q_OS_HAIKU - QDir dir(path); + const QDir dir(path); if (!dir.exists()) return; // Check if the path points to a network file system or not - if (isNetworkFileSystem(path)) { + if (Utils::Fs::isNetworkFileSystem(path)) { // Network mode qDebug("Network folder detected: %s", qUtf8Printable(path)); qDebug("Using file polling mode instead of inotify..."); m_watchedFolders << dir; - // Set up the watch timer - if (!m_watchTimer) { - m_watchTimer = new QTimer(this); - 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 + + m_watchTimer.start(WATCH_INTERVAL); + return; } #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) { #ifndef Q_OS_WIN - QDir dir(path); - for (int i = 0; i < m_watchedFolders.count(); ++i) { - if (QDir(m_watchedFolders.at(i)) == dir) { - m_watchedFolders.removeAt(i); - if (m_watchedFolders.isEmpty()) - delete m_watchTimer; - return; - } + if (m_watchedFolders.removeOne(path)) { + if (m_watchedFolders.isEmpty()) + m_watchTimer.stop(); + return; } #endif // Normal mode QFileSystemWatcher::removePath(path); } -void FileSystemWatcher::scanLocalFolder(QString path) +void FileSystemWatcher::scanLocalFolder(const QString &path) { - qDebug("scanLocalFolder(%s) called", qUtf8Printable(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); - } + QTimer::singleShot(2000, this, [this, path]() { processTorrentsInDir(path); }); } +#ifndef Q_OS_WIN void FileSystemWatcher::scanNetworkFolders() { -#ifndef Q_OS_WIN - qDebug("scanNetworkFolders() called"); - 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 + for (const QDir &dir : qAsConst(m_watchedFolders)) + processTorrentsInDir(dir); } +#endif void FileSystemWatcher::processPartialTorrents() { @@ -157,7 +140,7 @@ void FileSystemWatcher::processPartialTorrents() } if (value >= MAX_PARTIAL_RETRIES) { - QFile::rename(torrentPath, torrentPath + ".invalid"); + QFile::rename(torrentPath, torrentPath + ".qbt_rejected"); return true; } @@ -167,13 +150,12 @@ void FileSystemWatcher::processPartialTorrents() // Stop the partial timer if necessary if (m_partialTorrents.empty()) { - m_partialTorrentTimer->stop(); - m_partialTorrentTimer->deleteLater(); + m_partialTorrentTimer.stop(); qDebug("No longer any partial torrent."); } else { 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 @@ -181,99 +163,23 @@ void FileSystemWatcher::processPartialTorrents() emit torrentsAdded(noLongerPartial); } -void FileSystemWatcher::startPartialTorrentTimer() -{ - 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) +void FileSystemWatcher::processTorrentsInDir(const QDir &dir) { - const QStringList files = dir.entryList(m_filters, QDir::Files, QDir::Unsorted); - foreach (const QString &file, files) { + QStringList torrents; + const QStringList files = dir.entryList({"*.torrent", "*.magnet"}, QDir::Files); + for (const QString &file : files) { const QString fileAbsPath = dir.absoluteFilePath(file); - if (fileAbsPath.endsWith(".magnet")) { + if (file.endsWith(".magnet")) torrents << fileAbsPath; - } - else if (BitTorrent::TorrentInfo::loadFromFile(fileAbsPath).isValid()) { + else if (BitTorrent::TorrentInfo::loadFromFile(fileAbsPath).isValid()) torrents << fileAbsPath; - } - else if (!m_partialTorrents.contains(fileAbsPath)) { - qDebug("Partial torrent detected at: %s", qUtf8Printable(fileAbsPath)); - qDebug("Delay the file's processing..."); - m_partialTorrents.insert(fileAbsPath, 0); - } + else if (!m_partialTorrents.contains(fileAbsPath)) + m_partialTorrents[fileAbsPath] = 0; } - if (!m_partialTorrents.empty()) - startPartialTorrentTimer(); -} - -#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(CIFS_MAGIC_NUMBER)) - || (buf.f_type == static_cast(NFS_SUPER_MAGIC)) - || (buf.f_type == static_cast(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; - } + if (!torrents.empty()) + emit torrentsAdded(torrents); - std::cerr << "Errno: " << errno << std::endl; - return false; - } + if (!m_partialTorrents.empty() && !m_partialTorrentTimer.isActive()) + m_partialTorrentTimer.start(WATCH_INTERVAL); } -#endif diff --git a/src/base/filesystemwatcher.h b/src/base/filesystemwatcher.h index b209bdfb7..5079659f6 100644 --- a/src/base/filesystemwatcher.h +++ b/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 #define FILESYSTEMWATCHER_H #include #include #include -#include #include #include @@ -18,7 +45,6 @@ class FileSystemWatcher : public QFileSystemWatcher public: explicit FileSystemWatcher(QObject *parent = nullptr); - ~FileSystemWatcher(); QStringList directories() const; void addPath(const QString &path); @@ -28,25 +54,23 @@ signals: void torrentsAdded(const QStringList &pathList); protected slots: - void scanLocalFolder(QString path); - void scanNetworkFolders(); + void scanLocalFolder(const QString &path); void processPartialTorrents(); +#ifndef Q_OS_WIN + void scanNetworkFolders(); +#endif private: - void startPartialTorrentTimer(); - void addTorrentsFromDir(const QDir &dir, QStringList &torrents); -#if !defined Q_OS_WIN && !defined Q_OS_HAIKU - static bool isNetworkFileSystem(QString path); -#endif + void processTorrentsInDir(const QDir &dir); + + // Partial torrents + QHash m_partialTorrents; + QTimer m_partialTorrentTimer; #ifndef Q_OS_WIN QList m_watchedFolders; - QPointer m_watchTimer; + QTimer m_watchTimer; #endif - QStringList m_filters; - // Partial torrents - QHash m_partialTorrents; - QPointer m_partialTorrentTimer; }; #endif // FILESYSTEMWATCHER_H diff --git a/src/base/utils/fs.cpp b/src/base/utils/fs.cpp index 54349498f..eee398a57 100644 --- a/src/base/utils/fs.cpp +++ b/src/base/utils/fs.cpp @@ -31,8 +31,6 @@ #include "fs.h" #include -#include -#include #include #include @@ -42,6 +40,9 @@ #include #include +#include +#include + #if defined(Q_OS_WIN) #include #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; } + +#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 diff --git a/src/base/utils/fs.h b/src/base/utils/fs.h index 14265fc13..9c70477d3 100644 --- a/src/base/utils/fs.h +++ b/src/base/utils/fs.h @@ -63,6 +63,10 @@ namespace Utils void removeDirRecursive(const QString &path); QString tempPath(); + +#if !defined Q_OS_WIN && !defined Q_OS_HAIKU + bool isNetworkFileSystem(const QString &path); +#endif } }