diff --git a/Changelog b/Changelog index 44152ae9e..48bda454b 100644 --- a/Changelog +++ b/Changelog @@ -16,6 +16,7 @@ - FEATURE: Dropped Qt 4.3 support (Qt >= 4.4 is required) - FEATURE: Display more information regarding the torrent in its properties - FEATURE: Various optimizations to save CPU and memory + - FEATURE: Folder scanning now works with CIFS and NFS mounted folders - COSMETIC: Merged download / upload lists - COSMETIC: Torrents can be filtered based on their status - COSMETIC: Torrent properties are now displayed in main window diff --git a/src/bittorrent.cpp b/src/bittorrent.cpp index c241b0ebf..c99acc4e4 100644 --- a/src/bittorrent.cpp +++ b/src/bittorrent.cpp @@ -32,10 +32,9 @@ #include #include #include -#include #include -#include +#include "filesystemwatcher.h" #include "bittorrent.h" #include "misc.h" #include "downloadThread.h" @@ -89,9 +88,6 @@ bittorrent::bittorrent() : DHTEnabled(false), preAllocateAll(false), addInPause( downloader = new downloadThread(this); connect(downloader, SIGNAL(downloadFinished(QString, QString)), this, SLOT(processDownloadedFile(QString, QString))); connect(downloader, SIGNAL(downloadFailure(QString, QString)), this, SLOT(handleDownloadFailure(QString, QString))); - BigRatioTimer = 0; - filterParser = 0; - FSWatcher = 0; qDebug("* BTSession constructed"); } @@ -114,7 +110,6 @@ bittorrent::~bittorrent() { delete downloader; if(FSWatcher) { delete FSWatcher; - delete FSMutex; } // Delete BT session qDebug("Deleting session"); @@ -969,25 +964,10 @@ bool bittorrent::isFilePreviewPossible(QString hash) const{ return false; } -// Scan the first level of the directory for torrent files -// and add them to download list -void bittorrent::scanDirectory(QString scan_dir) { - FSMutex->lock(); - qDebug("Scanning directory: %s", scan_dir.toLocal8Bit().data()); - QDir dir(scan_dir); - QDir torrentBackup(misc::qBittorrentPath() + "BT_backup"); - // Check that scan dir is not BT_backup (silly but who knows...) - if(dir == torrentBackup) { - std::cerr << "Scan directory cannot be qBittorrent backup folder!" << std::endl; - return; - } - QStringList filters; - filters << "*.torrent"; - QStringList files = dir.entryList(filters, QDir::Files, QDir::Unsorted); - foreach(const QString &file, files) { - QString fullPath = dir.path()+QDir::separator()+file; - QFile torrent(fullPath); - qDebug("Adding for scan_dir: %s", fullPath.toLocal8Bit().data()); +void bittorrent::addTorrentsFromScanFolder(QStringList &pathList) { + QString dir_path = FSWatcher->directories().first(); + foreach(const QString &file, pathList) { + QString fullPath = dir_path+QDir::separator()+file; try { torrent_info t(fullPath.toLocal8Bit().data()); addTorrent(fullPath, true); @@ -995,7 +975,6 @@ void bittorrent::scanDirectory(QString scan_dir) { qDebug("Ignoring incomplete torrent file: %s", fullPath.toLocal8Bit().data()); } } - FSMutex->unlock(); } void bittorrent::setDefaultSavePath(QString savepath) { @@ -1050,21 +1029,17 @@ void bittorrent::enableDirectoryScanning(QString scan_dir) { newDir.mkpath(scan_dir); } if(FSWatcher == 0) { - FSMutex = new QMutex(); - FSWatcher = new QFileSystemWatcher(QStringList(scan_dir), this); - connect(FSWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(scanDirectory(QString))); - // Initial scan - scanDirectory(scan_dir); + // Set up folder watching + FSWatcher = new FileSystemWatcher(scan_dir, this); + connect(FSWatcher, SIGNAL(torrentsAdded(QStringList&)), this, SLOT(addTorrentsFromScanFolder(QStringList&))); } else { QString old_scan_dir = ""; if(!FSWatcher->directories().empty()) old_scan_dir = FSWatcher->directories().first(); - if(old_scan_dir != scan_dir) { + if(QDir(old_scan_dir) != QDir(scan_dir)) { if(!old_scan_dir.isEmpty()) FSWatcher->removePath(old_scan_dir); FSWatcher->addPath(scan_dir); - // Initial scan - scanDirectory(scan_dir); } } } @@ -1074,7 +1049,6 @@ void bittorrent::enableDirectoryScanning(QString scan_dir) { void bittorrent::disableDirectoryScanning() { if(FSWatcher) { delete FSWatcher; - delete FSMutex; } } diff --git a/src/bittorrent.h b/src/bittorrent.h index 6aaab3254..42a483874 100644 --- a/src/bittorrent.h +++ b/src/bittorrent.h @@ -44,8 +44,7 @@ using namespace libtorrent; class downloadThread; class QTimer; -class QFileSystemWatcher; -class QMutex; +class FileSystemWatcher; class FilterParserThread; class bittorrent : public QObject { @@ -53,8 +52,7 @@ class bittorrent : public QObject { private: session *s; - QPointer FSWatcher; - QMutex* FSMutex; + QPointer FSWatcher; QPointer timerAlerts; QPointer BigRatioTimer; bool DHTEnabled; @@ -172,7 +170,7 @@ class bittorrent : public QObject { void downloadFromURLList(const QStringList& urls); protected slots: - void scanDirectory(QString); + void addTorrentsFromScanFolder(QStringList&); void readAlerts(); void loadTrackerFile(QString hash); void deleteBigRatios(); diff --git a/src/filesystemwatcher.h b/src/filesystemwatcher.h new file mode 100644 index 000000000..2ec609bda --- /dev/null +++ b/src/filesystemwatcher.h @@ -0,0 +1,182 @@ +#ifndef FILESYSTEMWATCHER_H +#define FILESYSTEMWATCHER_H + +#include + +#ifndef Q_WS_WIN +#include +#include +#include +#include +#include +#include +#include +#ifdef Q_WS_MAC +#include +#include +#else +#include +#endif +#endif + +#ifndef CIFS_MAGIC_NUMBER +#define CIFS_MAGIC_NUMBER 0xFF534D42 +#endif + +#ifndef NFS_SUPER_MAGIC +#define NFS_SUPER_MAGIC 0x6969 +#endif + +/* + * Subclassing QFileSystemWatcher in order to support Network File + * System watching (NFS, CIFS) on Linux and Mac OS. + */ +class FileSystemWatcher: public QFileSystemWatcher { + Q_OBJECT + +#ifndef Q_WS_WIN +private: + QDir watched_folder; + QPointer watch_timer; + QStringList filters; + +protected: + bool isNetworkFileSystem(QString path) { + QString file = path; + if(!file.endsWith(QDir::separator())) + file += QDir::separator(); + file += "."; + struct statfs buf; + if(!statfs(file.toLocal8Bit().data(), &buf)) { + return (buf.f_type == (long)CIFS_MAGIC_NUMBER || buf.f_type == (long)NFS_SUPER_MAGIC); + } else { + std::cerr << "Error: statfs() call failed for " << file.toLocal8Bit().data() << ". 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; + return false; + } + + } +#endif + +public: + FileSystemWatcher(QObject *parent): QFileSystemWatcher(parent) { + filters << "*.torrent"; + connect(this, SIGNAL(directoryChanged(QString)), this, SLOT(scanFolder())); + } + + FileSystemWatcher(QString path, QObject *parent): QFileSystemWatcher(parent) { + filters << "*.torrent"; + connect(this, SIGNAL(directoryChanged(QString)), this, SLOT(scanFolder())); + addPath(path); + } + + ~FileSystemWatcher() { +#ifndef Q_WS_WIN + if(watch_timer) + delete watch_timer; +#endif + } + + QStringList directories() const { +#ifndef Q_WS_WIN + if(watch_timer) + return QStringList(watched_folder.path()); +#endif + return QFileSystemWatcher::directories(); + } + + void addPath(const QString & path) { +#ifndef Q_WS_WIN + watched_folder = QDir(path); + if(!watched_folder.exists()) return; + // Check if the path points to a network file system or not + if(isNetworkFileSystem(path)) { + // Network mode + Q_ASSERT(!watch_timer); + qDebug("Network folder detected: %s", path.toLocal8Bit().data()); + qDebug("Using file polling mode instead of inotify..."); + // Set up the watch timer + watch_timer = new QTimer(this); + connect(watch_timer, SIGNAL(timeout()), this, SLOT(scanFolder())); + watch_timer->start(5000); // 5 sec + } else { +#endif + // Normal mode + QFileSystemWatcher::addPath(path); + scanFolder(); +#ifndef Q_WS_WIN + } +#endif + } + + void removePath(const QString & path) { +#ifndef Q_WS_WIN + if(watch_timer) { + // Network mode + if(QDir(path) == watched_folder) { + delete watch_timer; + } + } else { +#endif + // Normal mode + QFileSystemWatcher::removePath(path); +#ifndef Q_WS_WIN + } +#endif + } + +protected slots: + // XXX: Does not detect file size changes to improve performance. + void scanFolder() { + QStringList torrents; + if(watch_timer) + torrents = watched_folder.entryList(filters, QDir::Files, QDir::Unsorted); + else + torrents = QDir(QFileSystemWatcher::directories().first()).entryList(filters, QDir::Files, QDir::Unsorted); + if(!torrents.empty()) + emit torrentsAdded(torrents); + } + +signals: + void torrentsAdded(QStringList &pathList); + +}; + +#endif // FILESYSTEMWATCHER_H diff --git a/src/src.pro b/src/src.pro index e2ef4066d..efb0a8227 100644 --- a/src/src.pro +++ b/src/src.pro @@ -184,7 +184,8 @@ HEADERS += GUI.h \ TransferListDelegate.h \ TransferListFiltersWidget.h \ propertieswidget.h \ - TorrentFilesModel.h + TorrentFilesModel.h \ + filesystemwatcher.h FORMS += MainWindow.ui \ options.ui \ about.ui \