From 7710c887978bb6cf844287d62858c7244d612172 Mon Sep 17 00:00:00 2001 From: Christophe Dumez Date: Sun, 28 Feb 2010 15:15:00 +0000 Subject: [PATCH] FEATURE: Support for multiple scan folders * Patch by Christian Kandeler (Thanks!) --- Changelog | 1 + src/bittorrent.cpp | 90 ++++++---------- src/bittorrent.h | 8 +- src/eventmanager.cpp | 10 +- src/filesystemwatcher.h | 97 +++++++++++------ src/options_imp.cpp | 92 ++++++++-------- src/options_imp.h | 8 +- src/preferences.h | 21 ++-- src/rss_imp.cpp | 2 +- src/scannedfoldersmodel.cpp | 191 ++++++++++++++++++++++++++++++++++ src/scannedfoldersmodel.h | 82 +++++++++++++++ src/src.pro | 67 +++++------- src/ui/options.ui | 202 +++++++++++++++++++++++------------- 13 files changed, 601 insertions(+), 270 deletions(-) create mode 100644 src/scannedfoldersmodel.cpp create mode 100644 src/scannedfoldersmodel.h diff --git a/Changelog b/Changelog index abada3fe5..76b8e0596 100644 --- a/Changelog +++ b/Changelog @@ -12,6 +12,7 @@ - FEATURE: Trackers can be added from Web UI - FEATURE: Global transfer information are displayed in the new Web UI status bar - FEATURE: Allow to change the priority of several files at once + - FEATURE: Support for multiple scan folders (Patch by Christian Kandeler) - COSMETIC: Improved style management * Mon Jan 18 2010 - Christophe Dumez - v2.1.0 diff --git a/src/bittorrent.cpp b/src/bittorrent.cpp index 206c46dfe..47c9b549e 100644 --- a/src/bittorrent.cpp +++ b/src/bittorrent.cpp @@ -41,6 +41,7 @@ #include "downloadthread.h" #include "filterparserthread.h" #include "preferences.h" +#include "scannedfoldersmodel.h" #ifndef DISABLE_GUI #include "geoip.h" #endif @@ -68,7 +69,13 @@ enum ProxyType {HTTP=1, SOCKS5=2, HTTP_PW=3, SOCKS5_PW=4, SOCKS4=5}; enum VersionType { NORMAL,ALPHA,BETA,RELEASE_CANDIDATE,DEVEL }; // Main constructor -Bittorrent::Bittorrent() : preAllocateAll(false), addInPause(false), ratio_limit(-1), UPnPEnabled(false), NATPMPEnabled(false), LSDEnabled(false), DHTEnabled(false), current_dht_port(0), queueingEnabled(false), torrentExport(false), exiting(false) { +Bittorrent::Bittorrent() + : m_scanFolders(ScanFoldersModel::instance(this)), + preAllocateAll(false), addInPause(false), ratio_limit(-1), + UPnPEnabled(false), NATPMPEnabled(false), LSDEnabled(false), + DHTEnabled(false), current_dht_port(0), queueingEnabled(false), + torrentExport(false), exiting(false) +{ #ifndef DISABLE_GUI geoipDBLoaded = false; resolve_countries = false; @@ -136,6 +143,7 @@ Bittorrent::Bittorrent() : preAllocateAll(false), addInPause(false), ratio_limit #endif // Apply user settings to Bittorrent session configureSession(); + connect(m_scanFolders, SIGNAL(torrentsAdded(QStringList&)), this, SLOT(addTorrentsFromScanFolder(QStringList&))); qDebug("* BTSession constructed"); } @@ -165,8 +173,6 @@ Bittorrent::~Bittorrent() { session_proxy sp = s->abort(); delete s; } - // Disable directory scanning - disableDirectoryScanning(); // Delete our objects delete timerAlerts; if(BigRatioTimer) @@ -174,8 +180,6 @@ Bittorrent::~Bittorrent() { if(filterParser) delete filterParser; delete downloader; - if(FSWatcher) - delete FSWatcher; if(bd_scheduler) delete bd_scheduler; // HTTP Server @@ -272,13 +276,14 @@ void Bittorrent::configureSession() { #endif preAllocateAllFiles(Preferences::preAllocateAllFiles()); startTorrentsInPause(Preferences::addTorrentsInPause()); - // * Scan dir - QString scan_dir = Preferences::getScanDir(); - if(scan_dir.isEmpty()) { - disableDirectoryScanning(); - }else{ - //Interval first - enableDirectoryScanning(scan_dir); + // * Scan dirs + const QStringList &scan_dirs = Preferences::getScanDirs(); + foreach (const QString &dir, scan_dirs) { + m_scanFolders->addPath(dir); + } + const QVariantList &downloadInDirList = Preferences::getDownloadInScanDirs(); + for (int i = 0; i < downloadInDirList.count(); ++i) { + m_scanFolders->setDownloadAtPath(i, downloadInDirList.at(i).toBool()); } // * Export Dir bool newTorrentExport = Preferences::isTorrentExportEnabled(); @@ -1039,7 +1044,7 @@ QTorrentHandle Bittorrent::addTorrent(QString path, bool fromScanDir, QString fr // Enforcing the save path defined before URL download (from RSS for example) savePath = savepath_fromurl.take(QUrl::fromEncoded(from_url.toLocal8Bit())); } else { - savePath = getSavePath(hash); + savePath = getSavePath(hash, fromScanDir, path); } if(!defaultTempPath.isEmpty() && resumed && !TorrentPersistentData::isSeed(hash)) { qDebug("addTorrent::Temp folder is enabled."); @@ -1495,14 +1500,13 @@ void Bittorrent::addConsoleMessage(QString msg, QString) { } void Bittorrent::addTorrentsFromScanFolder(QStringList &pathList) { - QString dir_path = FSWatcher->directories().first(); foreach(const QString &file, pathList) { - QString fullPath = dir_path+QDir::separator()+file; + qDebug("File %s added", qPrintable(file)); try { - torrent_info t(fullPath.toLocal8Bit().data()); - addTorrent(fullPath, true); + torrent_info t(file.toLocal8Bit().data()); + addTorrent(file, true); } catch(std::exception&) { - qDebug("Ignoring incomplete torrent file: %s", fullPath.toLocal8Bit().data()); + qDebug("Ignoring incomplete torrent file: %s", file.toLocal8Bit().data()); } } } @@ -1647,39 +1651,6 @@ void Bittorrent::addConsoleMessage(QString msg, QString) { } #endif - // Enable directory scanning - void Bittorrent::enableDirectoryScanning(QString scan_dir) { - if(!scan_dir.isEmpty()) { - QDir newDir(scan_dir); - if(!newDir.exists()) { - qDebug("Scan dir %s does not exist, create it", scan_dir.toUtf8().data()); - newDir.mkpath(scan_dir); - } - if(FSWatcher == 0) { - // Set up folder watching - FSWatcher = new FileSystemWatcher(this); - connect(FSWatcher, SIGNAL(torrentsAdded(QStringList&)), this, SLOT(addTorrentsFromScanFolder(QStringList&))); - FSWatcher->addPath(scan_dir); - } else { - QString old_scan_dir = ""; - if(!FSWatcher->directories().empty()) - old_scan_dir = FSWatcher->directories().first(); - if(QDir(old_scan_dir) != QDir(scan_dir)) { - if(!old_scan_dir.isEmpty()) - FSWatcher->removePath(old_scan_dir); - FSWatcher->addPath(scan_dir); - } - } - } - } - - // Disable directory scanning - void Bittorrent::disableDirectoryScanning() { - if(FSWatcher) { - delete FSWatcher; - } - } - // Set the ports range in which is chosen the port the Bittorrent // session will listen to void Bittorrent::setListeningPort(int port) { @@ -2133,12 +2104,13 @@ void Bittorrent::addConsoleMessage(QString msg, QString) { return s->status(); } - QString Bittorrent::getSavePath(QString hash) { + QString Bittorrent::getSavePath(QString hash, bool fromScanDir, QString filePath) { QString savePath; if(TorrentTempData::hasTempData(hash)) { savePath = TorrentTempData::getSavePath(hash); - if(savePath.isEmpty()) - savePath = defaultSavePath; + if(savePath.isEmpty()) { + savePath = defaultSavePath; + } if(appendLabelToSavePath) { qDebug("appendLabelToSavePath is true"); QString label = TorrentTempData::getLabel(hash); @@ -2152,9 +2124,13 @@ void Bittorrent::addConsoleMessage(QString msg, QString) { qDebug("getSavePath, got save_path from temp data: %s", savePath.toLocal8Bit().data()); } else { savePath = TorrentPersistentData::getSavePath(hash); - if(savePath.isEmpty()) - savePath = defaultSavePath; - if(appendLabelToSavePath) { + if(savePath.isEmpty()) { + if(fromScanDir && m_scanFolders->downloadInTorrentFolder(filePath)) + savePath = QFileInfo(filePath).dir().path(); + else + savePath = defaultSavePath; + } + if(!fromScanDir && appendLabelToSavePath) { QString label = TorrentPersistentData::getLabel(hash); if(!label.isEmpty()) { QDir save_dir(savePath); diff --git a/src/bittorrent.h b/src/bittorrent.h index 822d03209..6a66648ab 100644 --- a/src/bittorrent.h +++ b/src/bittorrent.h @@ -52,10 +52,10 @@ using namespace libtorrent; class downloadThread; class QTimer; -class FileSystemWatcher; class FilterParserThread; class HttpServer; class BandwidthScheduler; +class ScanFoldersModel; class TrackerInfos { public: @@ -102,7 +102,7 @@ private: // HTTP QPointer downloader; // File System - QPointer FSWatcher; + ScanFoldersModel *m_scanFolders; // Console / Log QStringList consoleMessages; QStringList peerBanMessages; @@ -142,7 +142,7 @@ private: bool exiting; protected: - QString getSavePath(QString hash); + QString getSavePath(QString hash, bool fromScanDir = false, QString filePath = QString()); bool initWebUi(QString username, QString password, int port); public: @@ -195,8 +195,6 @@ public slots: void saveDHTEntry(); void preAllocateAllFiles(bool b); void saveFastResumeData(); - void enableDirectoryScanning(QString scan_dir); - void disableDirectoryScanning(); void enableIPFilter(QString filter); void disableIPFilter(); void setQueueingEnabled(bool enable); diff --git a/src/eventmanager.cpp b/src/eventmanager.cpp index 093a15c52..814a64b61 100644 --- a/src/eventmanager.cpp +++ b/src/eventmanager.cpp @@ -129,8 +129,10 @@ void EventManager::setGlobalPreferences(QVariantMap m) const { Preferences::setTempPathEnabled(m["temp_path_enabled"].toBool()); if(m.contains("temp_path")) Preferences::setTempPath(m["temp_path"].toString()); - if(m.contains("scan_dir")) - Preferences::setScanDir(m["scan_dir"].toString()); + if(m.contains("scan_dirs")) + Preferences::setScanDirs(m["scan_dirs"].toStringList()); + if(m.contains("download_in_scan_dirs")) + Preferences::setDownloadInScanDirs(m["download_in_scan_dirs"].toList()); if(m.contains("export_dir")) Preferences::setExportDir(m["export_dir"].toString()); if(m.contains("preallocate_all")) @@ -229,8 +231,8 @@ QVariantMap EventManager::getGlobalPreferences() const { data["save_path"] = Preferences::getSavePath(); data["temp_path_enabled"] = Preferences::isTempPathEnabled(); data["temp_path"] = Preferences::getTempPath(); - data["scan_dir_enabled"] = Preferences::isDirScanEnabled(); - data["scan_dir"] = Preferences::getScanDir(); + data["scan_dirs"] = Preferences::getScanDirs(); + data["download_in_scan_dirs"] = Preferences::getDownloadInScanDirs(); data["export_dir_enabled"] = Preferences::isTorrentExportEnabled(); data["export_dir"] = Preferences::getExportDir(); data["preallocate_all"] = Preferences::preAllocateAllFiles(); diff --git a/src/filesystemwatcher.h b/src/filesystemwatcher.h index 00ded5249..8257b4aca 100644 --- a/src/filesystemwatcher.h +++ b/src/filesystemwatcher.h @@ -34,12 +34,14 @@ class FileSystemWatcher: public QFileSystemWatcher { Q_OBJECT -#ifndef Q_WS_WIN private: - QDir watched_folder; +#ifndef Q_WS_WIN + QList watched_folders; QPointer watch_timer; +#endif QStringList filters; +#ifndef Q_WS_WIN protected: bool isNetworkFileSystem(QString path) { QString file = path; @@ -98,12 +100,12 @@ protected: public: FileSystemWatcher(QObject *parent): QFileSystemWatcher(parent) { filters << "*.torrent"; - connect(this, SIGNAL(directoryChanged(QString)), this, SLOT(scanFolder())); + connect(this, SIGNAL(directoryChanged(QString)), this, SLOT(scanLocalFolder(QString))); } FileSystemWatcher(QString path, QObject *parent): QFileSystemWatcher(parent) { filters << "*.torrent"; - connect(this, SIGNAL(directoryChanged(QString)), this, SLOT(scanFolder())); + connect(this, SIGNAL(directoryChanged(QString)), this, SLOT(scanLocalFolder(QString))); addPath(path); } @@ -115,33 +117,40 @@ public: } QStringList directories() const { + QStringList dirs; #ifndef Q_WS_WIN - if(watch_timer) - return QStringList(watched_folder.path()); + if(watch_timer) { + foreach (const QDir &dir, watched_folders) + dirs << dir.canonicalPath(); + } #endif - return QFileSystemWatcher::directories(); + dirs << QFileSystemWatcher::directories(); + return dirs; } void addPath(const QString & path) { #ifndef Q_WS_WIN - watched_folder = QDir(path); - if(!watched_folder.exists()) return; + QDir dir(path); + if (!dir.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("Network folder detected: %s", qPrintable(path)); qDebug("Using file polling mode instead of inotify..."); + watched_folders << dir; // Set up the watch timer - watch_timer = new QTimer(this); - connect(watch_timer, SIGNAL(timeout()), this, SLOT(scanFolder())); - watch_timer->start(5000); // 5 sec + if (!watch_timer) { + watch_timer = new QTimer(this); + connect(watch_timer, SIGNAL(timeout()), this, SLOT(scanNetworkFolders())); + watch_timer->start(5000); // 5 sec + } } else { #endif // Normal mode qDebug("FS Watching is watching %s in normal mode", path.toLocal8Bit().data()); QFileSystemWatcher::addPath(path); - scanFolder(); + scanLocalFolder(path); #ifndef Q_WS_WIN } #endif @@ -149,38 +158,58 @@ public: void removePath(const QString & path) { #ifndef Q_WS_WIN - if(watch_timer) { - // Network mode - if(QDir(path) == watched_folder) { - delete watch_timer; + QDir dir(path); + for (int i = 0; i < watched_folders.count(); ++i) { + if (QDir(watched_folders.at(i)) == dir) { + watched_folders.removeAt(i); + if (watched_folders.isEmpty()) + delete watch_timer; + return; } - } else { -#endif - // Normal mode - QFileSystemWatcher::removePath(path); -#ifndef Q_WS_WIN } #endif + // Normal mode + QFileSystemWatcher::removePath(path); } protected slots: - // XXX: Does not detect file size changes to improve performance. - void scanFolder() { - qDebug("Scan folder was called"); + void scanLocalFolder(QString path) { + qDebug("scanLocalFolder(%s) called", qPrintable(path)); 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); - qDebug("FSWatcher: Polling manually folder %s", QFileSystemWatcher::directories().first().toLocal8Bit().data()); - } - if(!torrents.empty()) + // Local folders scan + addTorrentsFromDir(QDir(path), torrents); + // Report detected torrent files + if(!torrents.empty()) { + qDebug("The following files are being reported: %s", qPrintable(torrents.join("\n"))); emit torrentsAdded(torrents); + } + } + + void scanNetworkFolders() { + qDebug("scanNetworkFolders() called"); + QStringList torrents; + // Network folders scan + foreach (const QDir &dir, watched_folders) { + qDebug("FSWatcher: Polling manually folder %s", qPrintable(dir.path())); + addTorrentsFromDir(dir, torrents); + } + // Report detected torrent files + if(!torrents.empty()) { + qDebug("The following files are being reported: %s", qPrintable(torrents.join("\n"))); + emit torrentsAdded(torrents); + } } signals: void torrentsAdded(QStringList &pathList); +private: + void addTorrentsFromDir(const QDir &dir, QStringList &torrents) { + const QStringList &files = dir.entryList(filters, QDir::Files, QDir::Unsorted); + foreach(const QString &file, files) + torrents << dir.canonicalPath() + '/' + file; + } + }; #endif // FILESYSTEMWATCHER_H diff --git a/src/options_imp.cpp b/src/options_imp.cpp index a0a4750f8..d550c21a7 100644 --- a/src/options_imp.cpp +++ b/src/options_imp.cpp @@ -47,6 +47,7 @@ #include "preferences.h" #include "misc.h" #include "advancedsettings.h" +#include "scannedfoldersmodel.h" // Constructor options_imp::options_imp(QWidget *parent):QDialog(parent){ @@ -62,6 +63,12 @@ options_imp::options_imp(QWidget *parent):QDialog(parent){ break; } } + + scanFoldersView->horizontalHeader()->setResizeMode(QHeaderView::ResizeToContents); + scanFoldersView->setModel(ScanFoldersModel::instance()); + connect(ScanFoldersModel::instance(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(enableApplyButton())); + connect(scanFoldersView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(handleScanFolderViewSelectionChanged())); + connect(buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(applySettings(QAbstractButton*))); comboStyle->addItems(QStyleFactory::keys()); // Languages supported @@ -139,7 +146,6 @@ options_imp::options_imp(QWidget *parent):QDialog(parent){ connect(checkNoSystray, SIGNAL(toggled(bool)), this, SLOT(setSystrayOptionsState(bool))); // Downloads tab connect(checkTempFolder, SIGNAL(toggled(bool)), this, SLOT(enableTempPathInput(bool))); - connect(checkScanDir, SIGNAL(toggled(bool)), this, SLOT(enableDirScan(bool))); connect(checkExportDir, SIGNAL(toggled(bool)), this, SLOT(enableTorrentExport(bool))); // Connection tab connect(checkUploadLimit, SIGNAL(toggled(bool)), this, SLOT(enableUploadLimit(bool))); @@ -187,8 +193,6 @@ options_imp::options_imp(QWidget *parent):QDialog(parent){ connect(checkPreallocateAll, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); connect(checkAdditionDialog, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); connect(checkStartPaused, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); - connect(checkScanDir, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); - connect(textScanDir, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton())); connect(checkExportDir, SIGNAL(toggled(bool)), this, SLOT(enableApplyButton())); connect(textExportDir, SIGNAL(textChanged(QString)), this, SLOT(enableApplyButton())); connect(actionTorrentDlOnDblClBox, SIGNAL(currentIndexChanged(int)), this, SLOT(enableApplyButton())); @@ -280,6 +284,8 @@ options_imp::options_imp(QWidget *parent):QDialog(parent){ // Main destructor options_imp::~options_imp(){ qDebug("-> destructing Options"); + foreach (const QString &path, addedScanDirs) + ScanFoldersModel::instance()->removePath(path); delete scrollArea_advanced->layout(); delete advancedSettings; } @@ -366,7 +372,8 @@ void options_imp::saveOptions(){ settings.setValue(QString::fromUtf8("PreAllocation"), preAllocateAllFiles()); settings.setValue(QString::fromUtf8("AdditionDialog"), useAdditionDialog()); settings.setValue(QString::fromUtf8("StartInPause"), addTorrentsInPause()); - settings.setValue(QString::fromUtf8("ScanDir"), getScanDir()); + ScanFoldersModel::instance()->makePersistent(settings); + addedScanDirs.clear(); Preferences::setExportDir(getExportDir()); settings.setValue(QString::fromUtf8("DblClOnTorDl"), getActionOnDblClOnTorrentDl()); settings.setValue(QString::fromUtf8("DblClOnTorFn"), getActionOnDblClOnTorrentFn()); @@ -589,17 +596,6 @@ void options_imp::loadOptions(){ checkPreallocateAll->setChecked(Preferences::preAllocateAllFiles()); checkAdditionDialog->setChecked(Preferences::useAdditionDialog()); checkStartPaused->setChecked(Preferences::addTorrentsInPause()); - strValue = Preferences::getScanDir(); - if(strValue.isEmpty()) { - // Disable - checkScanDir->setChecked(false); - enableDirScan(checkScanDir->isChecked()); - } else { - // enable - checkScanDir->setChecked(true); - textScanDir->setText(strValue); - enableDirScan(checkScanDir->isChecked()); - } strValue = Preferences::getExportDir(); if(strValue.isEmpty()) { @@ -923,10 +919,6 @@ bool options_imp::confirmOnExit() const{ return checkConfirmExit->isChecked(); } -bool options_imp::isDirScanEnabled() const { - return checkScanDir->isChecked(); -} - bool options_imp::isQueueingSystemEnabled() const { return checkEnableQueueing->isChecked(); } @@ -1241,11 +1233,6 @@ void options_imp::enableHTTPProxyAuth(bool checked){ textProxyPassword_http->setEnabled(checked); } -void options_imp::enableDirScan(bool checked){ - textScanDir->setEnabled(checked); - browseScanDirButton->setEnabled(checked); -} - void options_imp::enableTorrentExport(bool checked) { textExportDir->setEnabled(checked); browseExportDirButton->setEnabled(checked); @@ -1340,15 +1327,6 @@ void options_imp::setLocale(QString locale){ } } -// Return scan dir set in options -QString options_imp::getScanDir() const { - if(checkScanDir->isChecked()){ - return misc::expandPath(textScanDir->text()); - }else{ - return QString::null; - } -} - QString options_imp::getExportDir() const { if(checkExportDir->isChecked()){ return misc::expandPath(textExportDir->text()); @@ -1371,21 +1349,45 @@ int options_imp::getActionOnDblClOnTorrentFn() const { return actionTorrentFnOnDblClBox->currentIndex(); } -// Display dialog to choose scan dir -void options_imp::on_browseScanDirButton_clicked() { - QString scan_path = misc::expandPath(textScanDir->text()); - QDir scanDir(scan_path); - QString dir; - if(!scan_path.isEmpty() && scanDir.exists()) { - dir = QFileDialog::getExistingDirectory(this, tr("Choose scan directory"), scanDir.absolutePath()); - } else { - dir = QFileDialog::getExistingDirectory(this, tr("Choose scan directory"), QDir::homePath()); - } - if(!dir.isNull()){ - textScanDir->setText(dir); +void options_imp::on_addScanFolderButton_clicked() { + const QString dir = QFileDialog::getExistingDirectory(this, tr("Add directory to scan")); + if (!dir.isEmpty()) { + const ScanFoldersModel::PathStatus status = ScanFoldersModel::instance()->addPath(dir); + QString error; + switch (status) { + case ScanFoldersModel::AlreadyInList: + error = tr("Folder is already being watched.").arg(dir); + break; + case ScanFoldersModel::DoesNotExist: + error = tr("Folder does not exist."); + break; + case ScanFoldersModel::CannotRead: + error = tr("Folder is not readable."); + break; + default: + addedScanDirs << dir; + enableApplyButton(); + } + + if (!error.isEmpty()) { + QMessageBox::warning(this, tr("Failure"), tr("Failed to add Scan Folder '%1': %2").arg(dir).arg(error)); + } } } +void options_imp::on_removeScanFolderButton_clicked() { + const QModelIndexList &selected + = scanFoldersView->selectionModel()->selectedIndexes(); + if (selected.isEmpty()) + return; + Q_ASSERT(selected.count() == ScanFoldersModel::instance()->columnCount()); + ScanFoldersModel::instance()->removePath(selected.first().row()); +} + +void options_imp::handleScanFolderViewSelectionChanged() { + removeScanFolderButton->setEnabled(!scanFoldersView->selectionModel()->selectedIndexes().isEmpty()); +} + void options_imp::on_browseExportDirButton_clicked() { QString export_path = misc::expandPath(textExportDir->text()); QDir exportDir(export_path); diff --git a/src/options_imp.h b/src/options_imp.h index 0a72a9e0b..cb8a218df 100644 --- a/src/options_imp.h +++ b/src/options_imp.h @@ -52,6 +52,7 @@ private: QStringList locales; QAbstractButton *applyButton; AdvancedSettings *advancedSettings; + QList addedScanDirs; public: // Contructor / Destructor @@ -82,8 +83,6 @@ protected: bool preAllocateAllFiles() const; bool useAdditionDialog() const; bool addTorrentsInPause() const; - bool isDirScanEnabled() const; - QString getScanDir() const; QString getExportDir() const; int getActionOnDblClOnTorrentDl() const; int getActionOnDblClOnTorrentFn() const; @@ -136,7 +135,6 @@ protected slots: void enableUploadLimit(bool checked); void enableDownloadLimit(bool checked); void enableTempPathInput(bool checked); - void enableDirScan(bool checked); void enableTorrentExport(bool checked); void enablePeerProxy(int comboIndex); void enablePeerProxyAuth(bool checked); @@ -159,7 +157,6 @@ protected slots: void closeEvent(QCloseEvent *e); void on_buttonBox_rejected(); void applySettings(QAbstractButton* button); - void on_browseScanDirButton_clicked(); void on_browseExportDirButton_clicked(); void on_browseFilterButton_clicked(); void on_browseSaveDirButton_clicked(); @@ -173,6 +170,9 @@ protected slots: void loadWindowState(); void saveWindowState() const; void on_randomButton_clicked(); + void on_addScanFolderButton_clicked(); + void on_removeScanFolderButton_clicked(); + void handleScanFolderViewSelectionChanged(); public slots: void setLocale(QString locale); diff --git a/src/preferences.h b/src/preferences.h index 588355389..ceff41231 100644 --- a/src/preferences.h +++ b/src/preferences.h @@ -191,22 +191,25 @@ public: return settings.value(QString::fromUtf8("Preferences/Downloads/StartInPause"), false).toBool(); } - static bool isDirScanEnabled() { + static QStringList getScanDirs() { QSettings settings("qBittorrent", "qBittorrent"); - return !settings.value(QString::fromUtf8("Preferences/Downloads/ScanDir"), QString()).toString().isEmpty(); + return settings.value(QString::fromUtf8("Preferences/Downloads/ScanDirs"), QStringList()).toStringList(); } - static QString getScanDir() { + // This must be called somewhere with data from the model + static void setScanDirs(const QStringList &dirs) { QSettings settings("qBittorrent", "qBittorrent"); - return settings.value(QString::fromUtf8("Preferences/Downloads/ScanDir"), QString()).toString(); + settings.setValue(QString::fromUtf8("Preferences/Downloads/ScanDirs"), dirs); } - static void setScanDir(QString path) { - path = path.trimmed(); - if(path.isEmpty()) - path = QString(); + static QVariantList getDownloadInScanDirs() { QSettings settings("qBittorrent", "qBittorrent"); - settings.setValue(QString::fromUtf8("Preferences/Downloads/ScanDir"), path); + return settings.value(QString::fromUtf8("Preferences/Downloads/DownloadInScanDirs"), QVariantList()).toList(); + } + + static void setDownloadInScanDirs(const QVariantList &list) { + QSettings settings("qBittorrent", "qBittorrent"); + settings.setValue(QString::fromUtf8("Preferences/Downloads/DownloadInScanDirs"), list); } static bool isTorrentExportEnabled() { diff --git a/src/rss_imp.cpp b/src/rss_imp.cpp index 72a890295..fe830654a 100644 --- a/src/rss_imp.cpp +++ b/src/rss_imp.cpp @@ -432,7 +432,7 @@ void RSSImp::refreshNewsList(QTreeWidgetItem* item) { QList news; if(rss_item == rssmanager) news = RssManager::sortNewsList(rss_item->getUnreadNewsList()); - else + else if(rss_item) news = RssManager::sortNewsList(rss_item->getNewsList()); // Clear the list first textBrowser->clear(); diff --git a/src/scannedfoldersmodel.cpp b/src/scannedfoldersmodel.cpp new file mode 100644 index 000000000..d706ff321 --- /dev/null +++ b/src/scannedfoldersmodel.cpp @@ -0,0 +1,191 @@ +/* + * Bittorrent Client using Qt4 and libtorrent. + * Copyright (C) 2010 Christian Kandeler, Christophe Dumez + * + * 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. + * + * Contact : chris@qbittorrent.org + */ + +#include "scannedfoldersmodel.h" + +#include "filesystemwatcher.h" + +#include +#include +#include +#include +#include + +namespace { + const int PathColumn = 0; + const int DownloadAtTorrentColumn = 1; +} + +class ScanFoldersModel::PathData { +public: + PathData(const QString &path) : path(path), downloadAtPath(false) {} + const QString path; + bool downloadAtPath; +}; + +ScanFoldersModel *ScanFoldersModel::instance(QObject *parent) { + Q_ASSERT(!parent != !m_instance); + if (!m_instance) + m_instance = new ScanFoldersModel(parent); + return m_instance; +} + +ScanFoldersModel::ScanFoldersModel(QObject *parent) : + QAbstractTableModel(parent), m_fsWatcher(0) +{ } + +ScanFoldersModel::~ScanFoldersModel() { } + +int ScanFoldersModel::rowCount(const QModelIndex &parent) const { + return parent.isValid() ? 0 : m_pathList.count(); +} + +int ScanFoldersModel::columnCount(const QModelIndex &parent) const { + Q_UNUSED(parent); + return 2; +} + +QVariant ScanFoldersModel::data(const QModelIndex &index, int role) const { + if (!index.isValid() || index.row() >= rowCount()) + return QVariant(); + + const QSharedPointer &pathData = m_pathList.at(index.row()); + if (index.column() == PathColumn && role == Qt::DisplayRole) + return pathData->path; + if (index.column() == DownloadAtTorrentColumn && role == Qt::CheckStateRole) + return pathData->downloadAtPath ? Qt::Checked : Qt::Unchecked; + return QVariant(); +} + +QVariant ScanFoldersModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (orientation != Qt::Horizontal || role != Qt::DisplayRole || section < 0 || section >= columnCount()) + return QVariant(); + + if (section == PathColumn) + return tr("Watched Folder"); + return tr("Download here"); +} + +Qt::ItemFlags ScanFoldersModel::flags(const QModelIndex &index) const { + if (!index.isValid() || index.row() >= rowCount() || index.column() != DownloadAtTorrentColumn) + return QAbstractTableModel::flags(index); + return QAbstractTableModel::flags(index) | Qt::ItemIsUserCheckable; +} + +bool ScanFoldersModel::setData(const QModelIndex &index, const QVariant &value, int role) { + if (!index.isValid() || index.row() >= rowCount() || index.column() > DownloadAtTorrentColumn || role != Qt::CheckStateRole) + return false; + Q_ASSERT(index.column() == DownloadAtTorrentColumn); + m_pathList[index.row()]->downloadAtPath = (value.toInt() == Qt::Checked); + emit dataChanged(index, index); + return true; +} + +ScanFoldersModel::PathStatus ScanFoldersModel::addPath(const QString &path) { + QDir dir(path); + if (!dir.exists()) + return DoesNotExist; + if (!dir.isReadable()) + return CannotRead; + const QString &canonicalPath = dir.canonicalPath(); + if (findPathData(canonicalPath) != -1) + return AlreadyInList; + if (!m_fsWatcher) { + m_fsWatcher = new FileSystemWatcher(this); + connect(m_fsWatcher, SIGNAL(torrentsAdded(QStringList&)), this, SIGNAL(torrentsAdded(QStringList&))); + } + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + m_pathList << QSharedPointer(new PathData(canonicalPath)); + endInsertRows(); + m_fsWatcher->addPath(canonicalPath); + return Ok; +} + +void ScanFoldersModel::removePath(int row) { + Q_ASSERT(row >= 0 && row < rowCount()); + beginRemoveRows(QModelIndex(), row, row); + m_fsWatcher->removePath(m_pathList.at(row)->path); + m_pathList.removeAt(row); + endRemoveRows(); +} + +bool ScanFoldersModel::removePath(const QString &path) { + const int row = findPathData(path); + if (row == -1) + return false; + removePath(row); + return true; +} + +ScanFoldersModel::PathStatus ScanFoldersModel::setDownloadAtPath(int row, bool downloadAtPath) { + Q_ASSERT(row >= 0 && row < rowCount()); + + bool &oldValue = m_pathList[row]->downloadAtPath; + if (oldValue != downloadAtPath) { + if (downloadAtPath) { + QTemporaryFile testFile(m_pathList[row]->path + "/tmpFile"); + if (!testFile.open()) + return CannotWrite; + } + oldValue = downloadAtPath; + const QModelIndex &changedIndex = index(row, DownloadAtTorrentColumn); + emit dataChanged(changedIndex, changedIndex); + } + return Ok; +} + +bool ScanFoldersModel::downloadInTorrentFolder(const QString &filePath) const { + const int row = findPathData(QFileInfo(filePath).dir().path()); + Q_ASSERT(row != -1); + return m_pathList.at(row)->downloadAtPath; +} + +int ScanFoldersModel::findPathData(const QString &path) const { + for (int i = 0; i < m_pathList.count(); ++i) { + const QSharedPointer &pathData = m_pathList.at(i); + if (pathData->path == path) + return i; + } + + return -1; +} + +void ScanFoldersModel::makePersistent(QSettings &settings) { + QStringList paths; + QList downloadInFolderInfo; + foreach (const QSharedPointer &pathData, m_pathList) { + paths << pathData->path; + downloadInFolderInfo << pathData->downloadAtPath; + } + settings.setValue(QString::fromUtf8("ScanDirs"), paths); + settings.setValue(QString::fromUtf8("DownloadInScanDirs"), downloadInFolderInfo); +} + +ScanFoldersModel *ScanFoldersModel::m_instance = 0; diff --git a/src/scannedfoldersmodel.h b/src/scannedfoldersmodel.h new file mode 100644 index 000000000..5234b6a01 --- /dev/null +++ b/src/scannedfoldersmodel.h @@ -0,0 +1,82 @@ +/* + * Bittorrent Client using Qt4 and libtorrent. + * Copyright (C) 2010 Christian Kandeler, Christophe Dumez + * + * 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. + * + * Contact : chris@qbittorrent.org + */ + +#ifndef SCANNEDFOLDERSMODEL_H +#define SCANNEDFOLDERSMODEL_H + +#include +#include +#include +#include + +class FileSystemWatcher; +class QSettings; + +class ScanFoldersModel : public QAbstractTableModel { + Q_OBJECT + Q_DISABLE_COPY(ScanFoldersModel) + +public: + enum PathStatus { Ok, DoesNotExist, CannotRead, CannotWrite, AlreadyInList }; + static ScanFoldersModel *instance(QObject *parent = 0); + ~ScanFoldersModel(); + + virtual int rowCount(const QModelIndex & parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + + // TODO: removePaths(); singular version becomes private helper functions; + // also: remove functions should take modelindexes + PathStatus addPath(const QString &path); + void removePath(int row); + bool removePath(const QString &path); + PathStatus setDownloadAtPath(int row, bool downloadAtPath); + + bool downloadInTorrentFolder(const QString &filePath) const; + void makePersistent(QSettings &settings); + +signals: + // The absolute paths of new torrent files in the scanned directories. + void torrentsAdded(QStringList &pathList); + +private: + explicit ScanFoldersModel(QObject *parent); + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + static ScanFoldersModel *m_instance; + class PathData; + int findPathData(const QString &path) const; + + QList > m_pathList; + FileSystemWatcher *m_fsWatcher; +}; + +#endif // SCANNEDFOLDERSMODEL_H diff --git a/src/src.pro b/src/src.pro index fe1d637d6..3f8d59f45 100644 --- a/src/src.pro +++ b/src/src.pro @@ -15,6 +15,7 @@ DEFINES += VERSION=\\\"v2.2.0beta4\\\" DEFINES += VERSION_MAJOR=2 DEFINES += VERSION_MINOR=2 DEFINES += VERSION_BUGFIX=0 + # NORMAL,ALPHA,BETA,RELEASE_CANDIDATE,DEVEL DEFINES += VERSION_TYPE=BETA @@ -38,17 +39,14 @@ contains(DEBUG_MODE, 0) { include(../conf.pri) # Target - #target.path = $$BINDIR + # target.path = $$BINDIR target.path = $$PREFIX/bin/ INSTALLS += target } # Man page -contains(DEFINES, DISABLE_GUI) { - man.files = ../doc/qbittorrent-nox.1 -} else { - man.files = ../doc/qbittorrent.1 -} + contains(DEFINES, DISABLE_GUI):man.files = ../doc/qbittorrent-nox.1 + else:man.files = ../doc/qbittorrent.1 man.path = $$PREFIX/share/man/man1/ INSTALLS += man @@ -94,11 +92,10 @@ contains(DEFINES, DISABLE_GUI) { } contains(DEFINES, DISABLE_GUI) { - QT=core + QT = core TARGET = qbittorrent-nox -} else { - TARGET = qbittorrent } +else:TARGET = qbittorrent # QMAKE_CXXFLAGS_RELEASE += -fwrapv # QMAKE_CXXFLAGS_DEBUG += -fwrapv @@ -107,9 +104,8 @@ CONFIG += link_pkgconfig PKGCONFIG += "libtorrent-rasterbar" QT += network -!contains(DEFINES, DISABLE_GUI) { - QT += xml -} +!contains(DEFINES, DISABLE_GUI):QT += xml + DEFINES += QT_NO_CAST_TO_ASCII # Windows @@ -127,31 +123,25 @@ win32:LIBS += -lssl32 \ DEFINES += WITH_GEOIP_EMBEDDED message("On Windows, GeoIP database must be embedded.") } - macx { DEFINES += WITH_GEOIP_EMBEDDED message("On Mac OS X, GeoIP database must be embedded.") } - - unix:!macx { - contains(DEFINES, WITH_GEOIP_EMBEDDED) { - message("You chose to embed GeoIP database in qBittorrent executable.") - } - } + unix:!macx:contains(DEFINES, WITH_GEOIP_EMBEDDED):message("You chose to embed GeoIP database in qBittorrent executable.") # Add GeoIP resource file if the GeoIP database # should be embedded in qBittorrent executable contains(DEFINES, WITH_GEOIP_EMBEDDED) { - exists("geoip/GeoIP.dat") { - message("GeoIP.dat was found in src/geoip/.") - RESOURCES += geoip.qrc - } else { - DEFINES -= WITH_GEOIP_EMBEDDED - error("GeoIP.dat was not found in src/geoip/ folder, please follow instructions in src/geoip/README.") - } - } else { - message("GeoIP database will not be embedded in qBittorrent executable.") + exists("geoip/GeoIP.dat") { + message("GeoIP.dat was found in src/geoip/.") + RESOURCES += geoip.qrc + } + else { + DEFINES -= WITH_GEOIP_EMBEDDED + error("GeoIP.dat was not found in src/geoip/ folder, please follow instructions in src/geoip/README.") + } } +else:message("GeoIP database will not be embedded in qBittorrent executable.") } # Resource files @@ -206,12 +196,11 @@ HEADERS += misc.h \ torrentpersistentdata.h \ filesystemwatcher.h \ preferences.h \ - bandwidthscheduler.h + bandwidthscheduler.h \ + scannedfoldersmodel.h -contains(DEFINES, DISABLE_GUI) { - HEADERS += headlessloader.h -} else { - HEADERS += GUI.h \ +contains(DEFINES, DISABLE_GUI):HEADERS += headlessloader.h +else:HEADERS += GUI.h \ feedList.h \ supportedengines.h \ transferlistwidget.h \ @@ -252,10 +241,8 @@ contains(DEFINES, DISABLE_GUI) { trackerlogin.h \ pieceavailabilitybar.h \ advancedsettings.h -} -!contains(DEFINES, DISABLE_GUI) { - FORMS += ui/mainwindow.ui \ +!contains(DEFINES, DISABLE_GUI):FORMS += ui/mainwindow.ui \ ui/options.ui \ ui/about.ui \ ui/createtorrent.ui \ @@ -274,7 +261,6 @@ contains(DEFINES, DISABLE_GUI) { ui/propertieswidget.ui \ ui/peer.ui \ ui/confirmdeletiondlg.ui -} SOURCES += main.cpp \ bittorrent.cpp \ @@ -284,10 +270,10 @@ SOURCES += main.cpp \ httpconnection.cpp \ httprequestparser.cpp \ httpresponsegenerator.cpp \ - eventmanager.cpp + eventmanager.cpp \ + scannedfoldersmodel.cpp -!contains(DEFINES, DISABLE_GUI) { - SOURCES += GUI.cpp \ +!contains(DEFINES, DISABLE_GUI):SOURCES += GUI.cpp \ options_imp.cpp \ createtorrent_imp.cpp \ searchengine.cpp \ @@ -299,6 +285,5 @@ SOURCES += main.cpp \ transferlistwidget.cpp \ propertieswidget.cpp \ peerlistwidget.cpp -} DESTDIR = . diff --git a/src/ui/options.ui b/src/ui/options.ui index acce3e4be..a8a0ddc19 100644 --- a/src/ui/options.ui +++ b/src/ui/options.ui @@ -565,10 +565,10 @@ QGroupBox { - 0 - -76 - 644 - 548 + -30 + 0 + 632 + 684 @@ -583,7 +583,7 @@ QGroupBox { File system - + @@ -696,57 +696,120 @@ QGroupBox { - - - Automatically load .torrent files from: + + + Check Folders for .torrent Files: + + + + + + + + 0 + 1 + + + + + 250 + 150 + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + false + + + true + + + 80 + + + false + + + true + + + false + + + false + + + false + + + true + + + 80 + + + + + + + + + Add folder ... + + + + + + + false + + + Remove folder + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 30 + 20 + + + + + + + - - - - 6 - - - 0 - - - - - false - - - QLineEdit { - margin-left: 23px; -} - - - - - - - false - - - - 22 - 22 - - - - - 25 - 27 - - - - - :/Icons/oxygen/browse.png:/Icons/oxygen/browse.png - - - - - @@ -1060,8 +1123,8 @@ QGroupBox { 0 0 - 620 - 482 + 447 + 288 @@ -1305,8 +1368,8 @@ QGroupBox { 0 0 - 620 - 490 + 364 + 332 @@ -1710,8 +1773,8 @@ QGroupBox { 0 0 - 620 - 490 + 459 + 415 @@ -2127,8 +2190,8 @@ QGroupBox { 0 0 - 620 - 490 + 475 + 312 @@ -2561,8 +2624,8 @@ QGroupBox { 0 0 - 620 - 490 + 287 + 124 @@ -2658,8 +2721,8 @@ QGroupBox { 0 0 - 620 - 490 + 213 + 221 @@ -2825,8 +2888,8 @@ QGroupBox { 0 0 - 620 - 490 + 445 + 192 @@ -3000,8 +3063,8 @@ QGroupBox { 0 0 - 620 - 490 + 96 + 26 @@ -3062,7 +3125,6 @@ QGroupBox { checkPreallocateAll checkAdditionDialog checkStartPaused - browseScanDirButton spinPort checkUPnP checkNATPMP