|
|
|
@ -1,145 +1,128 @@
@@ -1,145 +1,128 @@
|
|
|
|
|
#include <QtGlobal> |
|
|
|
|
#ifndef Q_OS_WIN |
|
|
|
|
#include <QSet> |
|
|
|
|
#include <iostream> |
|
|
|
|
#include <errno.h> |
|
|
|
|
/*
|
|
|
|
|
* 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 <sys/param.h> |
|
|
|
|
#include <cstring> |
|
|
|
|
#include <sys/mount.h> |
|
|
|
|
#include <string.h> |
|
|
|
|
#elif !defined Q_OS_HAIKU |
|
|
|
|
#include <sys/vfs.h> |
|
|
|
|
#endif |
|
|
|
|
#include <sys/param.h> |
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
#include "algorithm.h" |
|
|
|
|
#include <QtGlobal> |
|
|
|
|
|
|
|
|
|
#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" |
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
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
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
m_watchTimer.start(WATCH_INTERVAL); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
// Normal mode
|
|
|
|
|
qDebug("FS Watching is watching %s in normal mode", qUtf8Printable(path)); |
|
|
|
|
qDebug("FS Watcher is watching %s in normal mode", qUtf8Printable(path)); |
|
|
|
|
QFileSystemWatcher::addPath(path); |
|
|
|
|
scanLocalFolder(path); |
|
|
|
|
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU |
|
|
|
|
} |
|
|
|
|
#endif |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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.removeOne(path)) { |
|
|
|
|
if (m_watchedFolders.isEmpty()) |
|
|
|
|
delete m_watchTimer; |
|
|
|
|
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); |
|
|
|
|
for (const QDir &dir : qAsConst(m_watchedFolders)) |
|
|
|
|
processTorrentsInDir(dir); |
|
|
|
|
} |
|
|
|
|
#endif |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void FileSystemWatcher::processPartialTorrents() |
|
|
|
|
{ |
|
|
|
@ -157,7 +140,7 @@ 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()
@@ -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()
@@ -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); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!m_partialTorrents.empty()) |
|
|
|
|
startPartialTorrentTimer(); |
|
|
|
|
else if (!m_partialTorrents.contains(fileAbsPath)) |
|
|
|
|
m_partialTorrents[fileAbsPath] = 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#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; |
|
|
|
|
} |
|
|
|
|
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 |
|
|
|
|