FEATURE: Support for multiple scan folders

* Patch by Christian Kandeler (Thanks!)
This commit is contained in:
Christophe Dumez 2010-02-28 15:15:00 +00:00
parent 4d5001d18d
commit 7710c88797
13 changed files with 601 additions and 270 deletions

View file

@ -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 <chris@qbittorrent.org> - v2.1.0

View file

@ -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);

View file

@ -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<downloadThread> downloader;
// File System
QPointer<FileSystemWatcher> 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);

View file

@ -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();

View file

@ -34,12 +34,14 @@
class FileSystemWatcher: public QFileSystemWatcher {
Q_OBJECT
#ifndef Q_WS_WIN
private:
QDir watched_folder;
#ifndef Q_WS_WIN
QList<QDir> watched_folders;
QPointer<QTimer> 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

View file

@ -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);

View file

@ -52,6 +52,7 @@ private:
QStringList locales;
QAbstractButton *applyButton;
AdvancedSettings *advancedSettings;
QList<QString> 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);

View file

@ -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() {

View file

@ -432,7 +432,7 @@ void RSSImp::refreshNewsList(QTreeWidgetItem* item) {
QList<RssItem*> 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();

191
src/scannedfoldersmodel.cpp Normal file
View file

@ -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 <QDir>
#include <QFileInfo>
#include <QSettings>
#include <QString>
#include <QTemporaryFile>
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> &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<PathData>(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> &pathData = m_pathList.at(i);
if (pathData->path == path)
return i;
}
return -1;
}
void ScanFoldersModel::makePersistent(QSettings &settings) {
QStringList paths;
QList<QVariant> downloadInFolderInfo;
foreach (const QSharedPointer<PathData> &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;

82
src/scannedfoldersmodel.h Normal file
View file

@ -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 <QAbstractTableModel>
#include <QList>
#include <QSharedPointer>
#include <QStringList>
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<QSharedPointer<PathData> > m_pathList;
FileSystemWatcher *m_fsWatcher;
};
#endif // SCANNEDFOLDERSMODEL_H

View file

@ -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 = .

View file

@ -565,10 +565,10 @@ QGroupBox {
<widget class="QWidget" name="scrollAreaWidgetContents_2">
<property name="geometry">
<rect>
<x>0</x>
<y>-76</y>
<width>644</width>
<height>548</height>
<x>-30</x>
<y>0</y>
<width>632</width>
<height>684</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_17">
@ -583,7 +583,7 @@ QGroupBox {
<property name="title">
<string>File system</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_12">
<layout class="QVBoxLayout" name="verticalLayout_25">
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="styleSheet">
@ -696,57 +696,120 @@ QGroupBox {
</layout>
</item>
<item>
<widget class="QCheckBox" name="checkScanDir">
<property name="text">
<string>Automatically load .torrent files from:</string>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Check Folders for .torrent Files:</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_38">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_16">
<item>
<widget class="QTableView" name="scanFoldersView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>250</width>
<height>150</height>
</size>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>80</number>
</attribute>
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>80</number>
</attribute>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_37">
<item>
<widget class="QPushButton" name="addScanFolderButton">
<property name="text">
<string>Add folder ...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeScanFolderButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Remove folder</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>30</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="textScanDir">
<property name="enabled">
<bool>false</bool>
</property>
<property name="styleSheet">
<string>QLineEdit {
margin-left: 23px;
}</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="browseScanDirButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>27</height>
</size>
</property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/Icons/oxygen/browse.png</normaloff>:/Icons/oxygen/browse.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="checkExportDir">
<property name="text">
@ -1060,8 +1123,8 @@ QGroupBox {
<rect>
<x>0</x>
<y>0</y>
<width>620</width>
<height>482</height>
<width>447</width>
<height>288</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_28">
@ -1305,8 +1368,8 @@ QGroupBox {
<rect>
<x>0</x>
<y>0</y>
<width>620</width>
<height>490</height>
<width>364</width>
<height>332</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_33">
@ -1710,8 +1773,8 @@ QGroupBox {
<rect>
<x>0</x>
<y>0</y>
<width>620</width>
<height>490</height>
<width>459</width>
<height>415</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_20">
@ -2127,8 +2190,8 @@ QGroupBox {
<rect>
<x>0</x>
<y>0</y>
<width>620</width>
<height>490</height>
<width>475</width>
<height>312</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_16">
@ -2561,8 +2624,8 @@ QGroupBox {
<rect>
<x>0</x>
<y>0</y>
<width>620</width>
<height>490</height>
<width>287</width>
<height>124</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_21">
@ -2658,8 +2721,8 @@ QGroupBox {
<rect>
<x>0</x>
<y>0</y>
<width>620</width>
<height>490</height>
<width>213</width>
<height>221</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_23">
@ -2825,8 +2888,8 @@ QGroupBox {
<rect>
<x>0</x>
<y>0</y>
<width>620</width>
<height>490</height>
<width>445</width>
<height>192</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_24">
@ -3000,8 +3063,8 @@ QGroupBox {
<rect>
<x>0</x>
<y>0</y>
<width>620</width>
<height>490</height>
<width>96</width>
<height>26</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_36"/>
@ -3062,7 +3125,6 @@ QGroupBox {
<tabstop>checkPreallocateAll</tabstop>
<tabstop>checkAdditionDialog</tabstop>
<tabstop>checkStartPaused</tabstop>
<tabstop>browseScanDirButton</tabstop>
<tabstop>spinPort</tabstop>
<tabstop>checkUPnP</tabstop>
<tabstop>checkNATPMP</tabstop>