From 90dd33706946acf0badba43c072c5c3a42af1893 Mon Sep 17 00:00:00 2001 From: Christophe Dumez Date: Sat, 23 Oct 2010 16:21:56 +0000 Subject: [PATCH] FEATURE: Added a torrent import assistant to seed or keep downloading outside torrents --- Changelog | 1 + src/GUI.cpp | 6 + src/GUI.h | 1 + src/misc.cpp | 1 + src/misc.h | 8 + src/previewselect.h | 2 +- src/properties/propertieswidget.cpp | 2 +- src/qtlibtorrent/qbtsession.cpp | 73 ++++---- src/qtlibtorrent/qtorrenthandle.cpp | 2 +- src/qtlibtorrent/qtorrenthandle.h | 2 +- src/src.pro | 9 +- src/torrentimportdlg.cpp | 259 ++++++++++++++++++++++++++++ src/torrentimportdlg.h | 81 +++++++++ src/torrentpersistentdata.h | 9 +- src/ui/mainwindow.ui | 18 +- src/ui/torrentimportdlg.ui | 133 ++++++++++++++ 16 files changed, 562 insertions(+), 45 deletions(-) create mode 100644 src/torrentimportdlg.cpp create mode 100644 src/torrentimportdlg.h create mode 100644 src/ui/torrentimportdlg.ui diff --git a/Changelog b/Changelog index c744135dc..33cd44d76 100644 --- a/Changelog +++ b/Changelog @@ -1,6 +1,7 @@ * Unreleased - Christophe Dumez - v2.5.0 - FEATURE: qBittorrent can now act as a tracker - FEATURE: Added feature to shutdown qbittorrent on torrents completion + - FEATURE: Added a torrent import assistant to seed or keep downloading outside torrents - FEATURE: Added a transfer list column to display the current tracker - COSMETIC: Replaced message box by on-screen notification for download errors diff --git a/src/GUI.cpp b/src/GUI.cpp index 73ab280ca..5ff2e36c5 100644 --- a/src/GUI.cpp +++ b/src/GUI.cpp @@ -67,6 +67,7 @@ #include "statusbar.h" #include "hidabletabwidget.h" #include "qinisettings.h" +#include "torrentimportdlg.h" #ifdef Q_WS_MAC #include "qmacapplication.h" void qt_mac_set_dock_menu(QMenu *menu); @@ -1195,6 +1196,11 @@ void GUI::on_actionSearch_engine_triggered() { displaySearchTab(actionSearch_engine->isChecked()); } +void GUI::on_action_Import_Torrent_triggered() +{ + TorrentImportDlg::importTorrent(BTSession); +} + /***************************************************** * * * HTTP Downloader * diff --git a/src/GUI.h b/src/GUI.h index ded9a5d80..290fb860b 100644 --- a/src/GUI.h +++ b/src/GUI.h @@ -184,6 +184,7 @@ private slots: void on_actionTop_tool_bar_triggered(); void on_actionShutdown_when_downloads_complete_triggered(); void on_actionShutdown_qBittorrent_when_downloads_complete_triggered(); + void on_action_Import_Torrent_triggered(); }; #endif diff --git a/src/misc.cpp b/src/misc.cpp index da10f5e26..df86d9d60 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -512,6 +512,7 @@ QString misc::friendlyUnit(double val) { } bool misc::isPreviewable(QString extension){ + if(extension.isEmpty()) return false; extension = extension.toUpper(); if(extension == "AVI") return true; if(extension == "MP3") return true; diff --git a/src/misc.h b/src/misc.h index d38aec9c8..c309dc313 100644 --- a/src/misc.h +++ b/src/misc.h @@ -98,6 +98,14 @@ public: return x; } + static inline QString file_extension(const QString &filename) { + QString extension; + if(filename.contains(".")) { + extension = filename.mid(filename.lastIndexOf(".")+1); + } + return extension; + } + static void shutdownComputer(); static bool safeRemove(QString file_path) { diff --git a/src/previewselect.h b/src/previewselect.h index 8f6905626..3ca29237b 100644 --- a/src/previewselect.h +++ b/src/previewselect.h @@ -109,7 +109,7 @@ public: h.file_progress(fp); unsigned int nbFiles = h.num_files(); for(unsigned int i=0; irowCount(); diff --git a/src/properties/propertieswidget.cpp b/src/properties/propertieswidget.cpp index c07778a10..cbf98a639 100644 --- a/src/properties/propertieswidget.cpp +++ b/src/properties/propertieswidget.cpp @@ -730,7 +730,7 @@ void PropertiesWidget::renameSelectedFile() { #if defined(Q_WS_WIN) || defined(Q_OS_OS2) if(h.file_at(0).compare(new_file_name, Qt::CaseInsensitive) != 0) { #else - if(h.file_at(0).compare(new_file_name, Qt::CaseSensitive) != 0) { + if(h.filename_at(0).compare(new_file_name, Qt::CaseSensitive) != 0) { #endif qDebug("Renaming single file to %s", qPrintable(new_file_name)); h.rename_file(0, new_file_name); diff --git a/src/qtlibtorrent/qbtsession.cpp b/src/qtlibtorrent/qbtsession.cpp index 070b9bf4d..0b033fa01 100644 --- a/src/qtlibtorrent/qbtsession.cpp +++ b/src/qtlibtorrent/qbtsession.cpp @@ -1107,6 +1107,7 @@ QTorrentHandle QBtSession::addTorrent(QString path, bool fromScanDir, QString fr loadTorrentSettings(h); if(!resumed) { + qDebug("This is a NEW torrent (first time)..."); loadTorrentTempData(h, savePath, false); #if LIBTORRENT_VERSION_MINOR > 14 @@ -1191,45 +1192,48 @@ add_torrent_params QBtSession::initializeAddTorrentParams(QString hash) { } void QBtSession::loadTorrentTempData(QTorrentHandle h, QString savePath, bool magnet) { + qDebug("loadTorrentTempdata() - ENTER"); const QString hash = h.hash(); // Sequential download - if(!TorrentTempData::hasTempData(hash)) return; - // sequential download - h.set_sequential_download(TorrentTempData::isSequential(hash)); - - // The following is useless for newly added magnet - if(!magnet) { - // Files priorities - vector fp; - TorrentTempData::getFilesPriority(hash, fp); - h.prioritize_files(fp); - - // Update file names - const QStringList files_path = TorrentTempData::getFilesPath(hash); - bool force_recheck = false; - if(files_path.size() == h.num_files()) { - for(int i=0; i fp; + TorrentTempData::getFilesPriority(hash, fp); + h.prioritize_files(fp); + + // Update file names + const QStringList files_path = TorrentTempData::getFilesPath(hash); + bool force_recheck = false; + if(files_path.size() == h.num_files()) { + for(int i=0; ipath.c_str()); + const QString new_save_path = misc::toQStringU(p->path.c_str()); qDebug("Torrent moved from %s to %s", qPrintable(old_save_path), qPrintable(new_save_path)); QDir old_save_dir(old_save_path); if(old_save_dir != QDir(defaultSavePath) && old_save_dir != QDir(defaultTempPath)) { @@ -2173,6 +2177,7 @@ void QBtSession::readAlerts() { misc::removeEmptyTree(old_save_path); } if(defaultTempPath.isEmpty() || !new_save_path.startsWith(defaultTempPath)) { + qDebug("Storage has been moved, updating save path to %s", qPrintable(new_save_path)); TorrentPersistentData::saveSavePath(h.hash(), new_save_path); } emit savePathChanged(h); diff --git a/src/qtlibtorrent/qtorrenthandle.cpp b/src/qtlibtorrent/qtorrenthandle.cpp index 12e0d00b6..e27588e00 100644 --- a/src/qtlibtorrent/qtorrenthandle.cpp +++ b/src/qtlibtorrent/qtorrenthandle.cpp @@ -232,7 +232,7 @@ int QTorrentHandle::num_files() const { return torrent_handle::get_torrent_info().num_files(); } -QString QTorrentHandle::file_at(unsigned int index) const { +QString QTorrentHandle::filename_at(unsigned int index) const { Q_ASSERT(torrent_handle::is_valid()); Q_ASSERT(index < (unsigned int)torrent_handle::get_torrent_info().num_files()); return misc::toQStringU(torrent_handle::get_torrent_info().file_at(index).path.leaf()); diff --git a/src/qtlibtorrent/qtorrenthandle.h b/src/qtlibtorrent/qtorrenthandle.h index 7b78dc366..0d1dbec22 100644 --- a/src/qtlibtorrent/qtorrenthandle.h +++ b/src/qtlibtorrent/qtorrenthandle.h @@ -81,7 +81,7 @@ class QTorrentHandle : public torrent_handle { int num_files() const; int queue_position() const; bool is_queued() const; - QString file_at(unsigned int index) const; + QString filename_at(unsigned int index) const; size_type filesize_at(unsigned int index) const; torrent_status::state_t state() const; QString creator() const; diff --git a/src/src.pro b/src/src.pro index 753b420d8..abfb23f1f 100644 --- a/src/src.pro +++ b/src/src.pro @@ -309,7 +309,8 @@ contains(DEFINES, DISABLE_GUI) { cookiesdlg.h \ hidabletabwidget.h \ sessionapplication.h \ - torrentcreatordlg.h + torrentcreatordlg.h \ + torrentimportdlg.h macx { HEADERS += qmacapplication.h @@ -360,7 +361,8 @@ include(tracker/tracker.pri) ui/console.ui \ ui/peer.ui \ ui/confirmdeletiondlg.ui \ - ui/cookiesdlg.ui + ui/cookiesdlg.ui \ + ui/torrentimportdlg.ui } SOURCES += main.cpp \ @@ -377,7 +379,8 @@ SOURCES += main.cpp \ cookiesdlg.cpp \ torrentadditiondlg.cpp \ sessionapplication.cpp \ - torrentcreatordlg.cpp + torrentcreatordlg.cpp \ + torrentimportdlg.cpp macx { SOURCES += qmacapplication.cpp diff --git a/src/torrentimportdlg.cpp b/src/torrentimportdlg.cpp new file mode 100644 index 000000000..b93195872 --- /dev/null +++ b/src/torrentimportdlg.cpp @@ -0,0 +1,259 @@ +/* + * Bittorrent Client using Qt4 and libtorrent. + * Copyright (C) 2010 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 +#include + +#include "torrentimportdlg.h" +#include "ui_torrentimportdlg.h" +#include "qinisettings.h" +#include "qbtsession.h" +#include "torrentpersistentdata.h" +#include "misc.h" + +using namespace libtorrent; + +TorrentImportDlg::TorrentImportDlg(QWidget *parent) : + QDialog(parent), + ui(new Ui::TorrentImportDlg) +{ + ui->setupUi(this); + // Libtorrent < 0.15 does not support skipping file checking +#if LIBTORRENT_VERSION_MINOR < 15 + ui->checkSkipCheck->setVisible(false); +#endif +} + +TorrentImportDlg::~TorrentImportDlg() +{ + delete ui; +} + +void TorrentImportDlg::on_browseTorrentBtn_clicked() +{ + QIniSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent")); + const QString default_dir = settings.value(QString::fromUtf8("MainWindowLastDir"), QDir::homePath()).toString(); + // Ask for a torrent file + m_torrentPath = QFileDialog::getOpenFileName(this, tr("Torrent file to import"), default_dir, tr("Torrent files (*.torrent)")); + if(!m_torrentPath.isEmpty()) { + loadTorrent(m_torrentPath); + } else { + ui->lineTorrent->clear(); + } +} + +void TorrentImportDlg::on_browseContentBtn_clicked() +{ + QIniSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent")); + const QString default_dir = settings.value(QString::fromUtf8("TorrentImport/LastContentDir"), QDir::homePath()).toString(); + if(t->num_files() == 1) { + // Single file torrent + const QString file_name = misc::toQStringU(t->file_at(0).path.leaf()); + qDebug("Torrent has only one file: %s", qPrintable(file_name)); + QString extension = misc::file_extension(file_name); + qDebug("File extension is : %s", qPrintable(extension)); + QString filter; + if(!extension.isEmpty()) { + extension = extension.toUpper(); + filter = tr("%1 Files", "%1 is a file extension (e.g. PDF)").arg(extension)+" (*."+extension+")"; + } + m_contentPath = QFileDialog::getOpenFileName(this, tr("Please provide the location of %1", "%1 is a file name").arg(file_name), default_dir, filter); + if(m_contentPath.isEmpty() || !QFile(m_contentPath).exists()) { + m_contentPath = QString::null; + ui->importBtn->setEnabled(false); + ui->checkSkipCheck->setEnabled(false); + return; + } + // Update display +#if defined(Q_WS_WIN) || defined(Q_OS_OS2) + ui->lineContent->setText(m_contentPath.replace("/", "\\"); + #else + ui->lineContent->setText(m_contentPath); +#endif +#if LIBTORRENT_VERSION_MINOR >= 15 + // Check file size + const qint64 file_size = QFile(m_contentPath).size(); + if(t->file_at(0).size == file_size) { + qDebug("The file size matches, allowing fast seeding..."); + ui->checkSkipCheck->setEnabled(true); + } else { + qDebug("The file size does not match, forbidding fast seeding..."); + ui->checkSkipCheck->setChecked(false); + ui->checkSkipCheck->setEnabled(false); + } +#endif + // Handle file renaming + QStringList parts = m_contentPath.replace("\\", "/").split("/"); + QString new_file_name = parts.takeLast(); + if(new_file_name != file_name) { + qDebug("The file has been renamed"); + QStringList new_parts = m_filesPath.first().split("/"); + new_parts.replace(new_parts.count()-1, new_file_name); + m_filesPath.replace(0, new_parts.join("/")); + } + m_contentPath = parts.join("/"); + } else { + // multiple files torrent + m_contentPath = QFileDialog::getExistingDirectory(this, tr("Please point to the location of the torrent: %1").arg(misc::toQStringU(t->name())), + default_dir); + if(m_contentPath.isEmpty() || !QDir(m_contentPath).exists()) { + m_contentPath = QString::null; + ui->importBtn->setEnabled(false); + ui->checkSkipCheck->setEnabled(false); + return; + } + // Update the display +#if defined(Q_WS_WIN) || defined(Q_OS_OS2) + ui->lineContent->setText(m_contentPath.replace("/", "\\"); + #else + ui->lineContent->setText(m_contentPath); +#endif +#if LIBTORRENT_VERSION_MINOR >= 15 + bool size_mismatch = false; + QDir content_dir(m_contentPath); + // Check file sizes + torrent_info::file_iterator it; t->begin_files(); + for(it = t->begin_files(); it != t->end_files(); it++) { + if(QFile(QDir::cleanPath(content_dir.absoluteFilePath(misc::toQStringU(it->path.string())))).size() != it->size) { + qDebug("%s is %lld", + qPrintable(QDir::cleanPath(content_dir.absoluteFilePath(misc::toQStringU(it->path.string())))), (long long int) QFile(QDir::cleanPath(content_dir.absoluteFilePath(misc::toQStringU(it->path.string())))).size()); + qDebug("%s is %lld", + it->path.string().c_str(), (long long int)it->size); + size_mismatch = true; + break; + } + } + if(size_mismatch) { + qDebug("The file size does not match, forbidding fast seeding..."); + ui->checkSkipCheck->setChecked(false); + ui->checkSkipCheck->setEnabled(false); + } else { + qDebug("The file size matches, allowing fast seeding..."); + ui->checkSkipCheck->setEnabled(true); + } +#endif + } + // Enable the import button + ui->importBtn->setEnabled(true); +} + +void TorrentImportDlg::on_importBtn_clicked() +{ + accept(); +} + +QString TorrentImportDlg::getTorrentPath() const +{ + return m_torrentPath; +} + +QString TorrentImportDlg::getContentPath() const +{ + return m_contentPath; +} + +void TorrentImportDlg::importTorrent(QBtSession *BTSession) +{ + TorrentImportDlg dlg; + if(dlg.exec()) { + boost::intrusive_ptr t = dlg.torrent(); + if(!t->is_valid()) + return; + QString torrent_path = dlg.getTorrentPath(); + QString content_path = dlg.getContentPath(); + if(torrent_path.isEmpty() || content_path.isEmpty() || !QFile(torrent_path).exists()) return; + const QString hash = misc::toQString(t->info_hash()); + TorrentTempData::setSavePath(hash, content_path); +#if LIBTORRENT_VERSION_MINOR >= 15 + TorrentTempData::setSeedingMode(hash, dlg.skipFileChecking()); +#endif + qDebug("Adding the torrent to the session..."); + BTSession->addTorrent(torrent_path); + // Remember the last opened folder + QIniSettings settings(QString::fromUtf8("qBittorrent"), QString::fromUtf8("qBittorrent")); + settings.setValue(QString::fromUtf8("MainWindowLastDir"), torrent_path); + settings.setValue("TorrentImport/LastContentDir", content_path); + return; + } + return; +} + +void TorrentImportDlg::loadTorrent(const QString &torrent_path) +{ + // Load the torrent file + try { + t = new torrent_info(torrent_path.toUtf8().constData()); + if(!t->is_valid() || t->num_files() == 0) + throw std::exception(); + } catch(std::exception&) { + ui->browseContentBtn->setEnabled(false); + ui->lineTorrent->clear(); + QMessageBox::warning(this, tr("Invalid torrent file"), tr("This is not a valid torrent file.")); + return; + } + // The torrent file is valid + misc::truncateRootFolder(t); + // Update display +#if defined(Q_WS_WIN) || defined(Q_OS_OS2) + ui->lineTorrent->setText(torrent_path.replace("/", "\\")); +#else + ui->lineTorrent->setText(torrent_path); +#endif + ui->browseContentBtn->setEnabled(true); + // Load the file names + initializeFilesPath(); +} + +void TorrentImportDlg::initializeFilesPath() +{ + m_filesPath.clear(); + // Loads files path in the torrent + for(int i=0; inum_files(); ++i) { + m_filesPath << misc::toQStringU(t->file_at(i).path.string()).replace("\\", "/"); + } +} + +bool TorrentImportDlg::fileRenamed() const +{ + return m_fileRenamed; +} + + +boost::intrusive_ptr TorrentImportDlg::torrent() const +{ + return t; +} + +#if LIBTORRENT_VERSION_MINOR >= 15 +bool TorrentImportDlg::skipFileChecking() const +{ + return ui->checkSkipCheck->isChecked(); +} +#endif diff --git a/src/torrentimportdlg.h b/src/torrentimportdlg.h new file mode 100644 index 000000000..e33d58f92 --- /dev/null +++ b/src/torrentimportdlg.h @@ -0,0 +1,81 @@ +/* + * Bittorrent Client using Qt4 and libtorrent. + * Copyright (C) 2010 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 TORRENTIMPORTDLG_H +#define TORRENTIMPORTDLG_H + +#include +#include +#include +#include + +namespace Ui { +class TorrentImportDlg; +} + +class QBtSession; + +class TorrentImportDlg : public QDialog +{ + Q_OBJECT + +public: + explicit TorrentImportDlg(QWidget *parent = 0); + ~TorrentImportDlg(); + static void importTorrent(QBtSession *BTSession); + QString getTorrentPath() const; + QString getContentPath() const; + bool fileRenamed() const; + boost::intrusive_ptr torrent() const; +#if LIBTORRENT_VERSION_MINOR >= 15 + bool skipFileChecking() const; +#endif + +protected slots: + void loadTorrent(const QString &torrent_path); + void initializeFilesPath(); + +private slots: + void on_browseTorrentBtn_clicked(); + + void on_browseContentBtn_clicked(); + + void on_importBtn_clicked(); + +private: + Ui::TorrentImportDlg *ui; + boost::intrusive_ptr t; + QStringList m_filesPath; + QString m_contentPath; + QString m_torrentPath; + bool m_fileRenamed; +}; + +#endif // TORRENTIMPORTDLG_H diff --git a/src/torrentpersistentdata.h b/src/torrentpersistentdata.h index fa7913cfe..eb5b6a2d4 100644 --- a/src/torrentpersistentdata.h +++ b/src/torrentpersistentdata.h @@ -295,10 +295,13 @@ public: } data["seed"] = h.is_seed(); data["priority"] = h.queue_position(); - if(save_path.isEmpty()) + if(save_path.isEmpty()) { + qDebug("TorrentPersistantData: save path is %s", qPrintable(h.save_path())); data["save_path"] = h.save_path(); - else + } else { + qDebug("TorrentPersistantData: overriding save path is %s", qPrintable(save_path)); data["save_path"] = save_path; // Override torrent save path (e.g. because it is a temp dir) + } // Label data["label"] = TorrentTempData::getLabel(h.hash()); // Save data @@ -322,7 +325,7 @@ public: data["save_path"] = save_path; all_data[hash] = data; settings.setValue("torrents", all_data); - qDebug("TorrentPersistentData: Saving save_path: %s, hash: %s", save_path.toLocal8Bit().data(), hash.toLocal8Bit().data()); + qDebug("TorrentPersistentData: Saving save_path: %s, hash: %s", qPrintable(save_path), qPrintable(hash)); } static void saveLabel(QString hash, QString label) { diff --git a/src/ui/mainwindow.ui b/src/ui/mainwindow.ui index 2983946e7..673a11273 100644 --- a/src/ui/mainwindow.ui +++ b/src/ui/mainwindow.ui @@ -74,6 +74,7 @@ + @@ -149,7 +150,10 @@ - E&xit + Exit + + + Exit @@ -357,6 +361,18 @@ Shutdown qBittorrent when downloads complete + + + + :/Icons/oxygen/list-add.png:/Icons/oxygen/list-add.png + + + Import torrent... + + + Import torrent... + + diff --git a/src/ui/torrentimportdlg.ui b/src/ui/torrentimportdlg.ui new file mode 100644 index 000000000..32bf4c0b6 --- /dev/null +++ b/src/ui/torrentimportdlg.ui @@ -0,0 +1,133 @@ + + + TorrentImportDlg + + + + 0 + 0 + 400 + 199 + + + + Torrent Import + + + + + + 15 + + + + + + 0 + 0 + + + + + + + :/Icons/skin/info.png + + + + + + + This assistant will help you share with qBittorrent a torrent that you have already downloaded. + + + true + + + + + + + + + Torrent file to import: + + + + + + + + + false + + + + + + + ... + + + + + + + + + Content location: + + + + + + + + + false + + + + + + + false + + + ... + + + + + + + + + false + + + Skip the data checking stage and start seeding immediately + + + + + + + false + + + Import + + + + :/Icons/oxygen/list-add.png:/Icons/oxygen/list-add.png + + + + + + + + + +