mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-19 15:07:22 -06:00
Arguments include adding torrents as paused/started, skiping hash check, setting category, downloading in sequential order, downloading first and last pieces first, and skipping the 'add new torrent' dialog. Added TriStateBoolOption class for specifying options that don't have a default value when unused (e.g. add-paused). Also improved command line usage text to include more information, have better organization, and not exceed 80 columns in width. Also also added firstLastPiecePriority field to BitTorrent::AddTorrentData, and modified TorrentHandle so that if first/last piece priority should be on, it will be toggled on after the torrent's metadata has loaded.
835 lines
32 KiB
C++
835 lines
32 KiB
C++
/*
|
|
* Bittorrent Client using Qt and libtorrent.
|
|
* Copyright (C) 2012 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 <QDebug>
|
|
#include <QString>
|
|
#include <QFile>
|
|
#include <QUrl>
|
|
#include <QMenu>
|
|
#include <QFileDialog>
|
|
|
|
#include "base/preferences.h"
|
|
#include "base/settingsstorage.h"
|
|
#include "base/net/downloadmanager.h"
|
|
#include "base/net/downloadhandler.h"
|
|
#include "base/bittorrent/session.h"
|
|
#include "base/bittorrent/magneturi.h"
|
|
#include "base/bittorrent/torrentinfo.h"
|
|
#include "base/bittorrent/torrenthandle.h"
|
|
#include "base/utils/fs.h"
|
|
#include "base/utils/misc.h"
|
|
#include "base/utils/string.h"
|
|
#include "base/torrentfileguard.h"
|
|
#include "base/unicodestrings.h"
|
|
#include "guiiconprovider.h"
|
|
#include "autoexpandabledialog.h"
|
|
#include "messageboxraised.h"
|
|
#include "proplistdelegate.h"
|
|
#include "torrentcontentmodel.h"
|
|
#include "torrentcontentfiltermodel.h"
|
|
#include "ui_addnewtorrentdialog.h"
|
|
#include "addnewtorrentdialog.h"
|
|
|
|
#define SETTINGS_KEY(name) "AddNewTorrentDialog/" name
|
|
const QString KEY_ENABLED = SETTINGS_KEY("Enabled");
|
|
const QString KEY_DEFAULTCATEGORY = SETTINGS_KEY("DefaultCategory");
|
|
const QString KEY_TREEHEADERSTATE = SETTINGS_KEY("TreeHeaderState");
|
|
const QString KEY_WIDTH = SETTINGS_KEY("Width");
|
|
const QString KEY_EXPANDED = SETTINGS_KEY("Expanded");
|
|
const QString KEY_POSITION = SETTINGS_KEY("Position");
|
|
const QString KEY_TOPLEVEL = SETTINGS_KEY("TopLevel");
|
|
const QString KEY_SAVEPATHHISTORY = SETTINGS_KEY("SavePathHistory");
|
|
|
|
namespace
|
|
{
|
|
// just a shortcut
|
|
inline SettingsStorage *settings()
|
|
{
|
|
return SettingsStorage::instance();
|
|
}
|
|
}
|
|
|
|
AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inParams, QWidget *parent)
|
|
: QDialog(parent)
|
|
, ui(new Ui::AddNewTorrentDialog)
|
|
, m_contentModel(0)
|
|
, m_contentDelegate(0)
|
|
, m_hasMetadata(false)
|
|
, m_oldIndex(0)
|
|
, m_torrentParams(inParams)
|
|
{
|
|
// TODO: set dialog file properties using m_torrentParams.filePriorities
|
|
ui->setupUi(this);
|
|
setAttribute(Qt::WA_DeleteOnClose);
|
|
ui->lblMetaLoading->setVisible(false);
|
|
ui->progMetaLoading->setVisible(false);
|
|
|
|
auto session = BitTorrent::Session::instance();
|
|
|
|
if (m_torrentParams.addPaused == TriStateBool::True)
|
|
ui->startTorrentCheckBox->setChecked(false);
|
|
else if (m_torrentParams.addPaused == TriStateBool::False)
|
|
ui->startTorrentCheckBox->setChecked(true);
|
|
else
|
|
ui->startTorrentCheckBox->setChecked(!session->isAddTorrentPaused());
|
|
|
|
ui->comboTTM->blockSignals(true); // the TreeView size isn't correct if the slot does it job at this point
|
|
ui->comboTTM->setCurrentIndex(!session->isAutoTMMDisabledByDefault());
|
|
ui->comboTTM->blockSignals(false);
|
|
populateSavePathComboBox();
|
|
connect(ui->savePathComboBox, SIGNAL(currentIndexChanged(int)), SLOT(onSavePathChanged(int)));
|
|
connect(ui->browseButton, SIGNAL(clicked()), SLOT(browseButton_clicked()));
|
|
ui->defaultSavePathCheckBox->setVisible(false); // Default path is selected by default
|
|
|
|
if (m_torrentParams.createSubfolder == TriStateBool::True)
|
|
ui->createSubfolderCheckBox->setChecked(true);
|
|
else if (m_torrentParams.createSubfolder == TriStateBool::False)
|
|
ui->createSubfolderCheckBox->setChecked(false);
|
|
else
|
|
ui->createSubfolderCheckBox->setChecked(session->isCreateTorrentSubfolder());
|
|
|
|
ui->skipCheckingCheckBox->setChecked(m_torrentParams.skipChecking);
|
|
ui->doNotDeleteTorrentCheckBox->setVisible(TorrentFileGuard::autoDeleteMode() != TorrentFileGuard::Never);
|
|
|
|
// Load categories
|
|
QStringList categories = session->categories();
|
|
std::sort(categories.begin(), categories.end(), Utils::String::naturalCompareCaseInsensitive);
|
|
QString defaultCategory = settings()->loadValue(KEY_DEFAULTCATEGORY).toString();
|
|
|
|
if (!m_torrentParams.category.isEmpty())
|
|
ui->categoryComboBox->addItem(m_torrentParams.category);
|
|
if (!defaultCategory.isEmpty())
|
|
ui->categoryComboBox->addItem(defaultCategory);
|
|
ui->categoryComboBox->addItem("");
|
|
|
|
foreach (const QString &category, categories)
|
|
if (category != defaultCategory && category != m_torrentParams.category)
|
|
ui->categoryComboBox->addItem(category);
|
|
|
|
ui->categoryComboBox->model()->sort(0);
|
|
ui->contentTreeView->header()->setSortIndicator(0, Qt::AscendingOrder);
|
|
loadState();
|
|
// Signal / slots
|
|
connect(ui->adv_button, SIGNAL(clicked(bool)), SLOT(showAdvancedSettings(bool)));
|
|
connect(ui->doNotDeleteTorrentCheckBox, SIGNAL(clicked(bool)), SLOT(doNotDeleteTorrentClicked(bool)));
|
|
editHotkey = new QShortcut(Qt::Key_F2, ui->contentTreeView, 0, 0, Qt::WidgetShortcut);
|
|
connect(editHotkey, SIGNAL(activated()), SLOT(renameSelectedFile()));
|
|
connect(ui->contentTreeView, SIGNAL(doubleClicked(QModelIndex)), SLOT(renameSelectedFile()));
|
|
|
|
ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus();
|
|
}
|
|
|
|
AddNewTorrentDialog::~AddNewTorrentDialog()
|
|
{
|
|
saveState();
|
|
delete ui;
|
|
if (m_contentModel)
|
|
delete m_contentModel;
|
|
delete editHotkey;
|
|
}
|
|
|
|
bool AddNewTorrentDialog::isEnabled()
|
|
{
|
|
return SettingsStorage::instance()->loadValue(KEY_ENABLED, true).toBool();
|
|
}
|
|
|
|
void AddNewTorrentDialog::setEnabled(bool value)
|
|
{
|
|
SettingsStorage::instance()->storeValue(KEY_ENABLED, value);
|
|
}
|
|
|
|
bool AddNewTorrentDialog::isTopLevel()
|
|
{
|
|
return SettingsStorage::instance()->loadValue(KEY_TOPLEVEL, true).toBool();
|
|
}
|
|
|
|
void AddNewTorrentDialog::setTopLevel(bool value)
|
|
{
|
|
SettingsStorage::instance()->storeValue(KEY_TOPLEVEL, value);
|
|
}
|
|
|
|
void AddNewTorrentDialog::loadState()
|
|
{
|
|
m_headerState = settings()->loadValue(KEY_TREEHEADERSTATE).toByteArray();
|
|
int width = settings()->loadValue(KEY_WIDTH, -1).toInt();
|
|
if (width >= 0) {
|
|
QRect geo = geometry();
|
|
geo.setWidth(width);
|
|
setGeometry(geo);
|
|
}
|
|
ui->adv_button->setChecked(settings()->loadValue(KEY_EXPANDED).toBool());
|
|
}
|
|
|
|
void AddNewTorrentDialog::saveState()
|
|
{
|
|
if (m_contentModel)
|
|
settings()->storeValue(KEY_TREEHEADERSTATE, ui->contentTreeView->header()->saveState());
|
|
settings()->storeValue(KEY_POSITION, pos().y());
|
|
settings()->storeValue(KEY_WIDTH, width());
|
|
settings()->storeValue(KEY_EXPANDED, ui->adv_button->isChecked());
|
|
}
|
|
|
|
void AddNewTorrentDialog::show(QString source, const BitTorrent::AddTorrentParams &inParams, QWidget *parent)
|
|
{
|
|
AddNewTorrentDialog *dlg = new AddNewTorrentDialog(inParams, parent);
|
|
|
|
if (Utils::Misc::isUrl(source)) {
|
|
// Launch downloader
|
|
Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(source, true, 10485760 /* 10MB */, true);
|
|
connect(handler, SIGNAL(downloadFinished(QString,QString)), dlg, SLOT(handleDownloadFinished(QString,QString)));
|
|
connect(handler, SIGNAL(downloadFailed(QString,QString)), dlg, SLOT(handleDownloadFailed(QString,QString)));
|
|
connect(handler, SIGNAL(redirectedToMagnet(QString,QString)), dlg, SLOT(handleRedirectedToMagnet(QString,QString)));
|
|
}
|
|
else {
|
|
bool ok = false;
|
|
BitTorrent::MagnetUri magnetUri(source);
|
|
if (magnetUri.isValid())
|
|
ok = dlg->loadMagnet(magnetUri);
|
|
else
|
|
ok = dlg->loadTorrent(source);
|
|
|
|
if (ok)
|
|
dlg->open();
|
|
else
|
|
delete dlg;
|
|
}
|
|
}
|
|
|
|
void AddNewTorrentDialog::show(QString source, QWidget *parent)
|
|
{
|
|
show(source, BitTorrent::AddTorrentParams(), parent);
|
|
}
|
|
|
|
bool AddNewTorrentDialog::loadTorrent(const QString &torrentPath)
|
|
{
|
|
if (torrentPath.startsWith("file://", Qt::CaseInsensitive))
|
|
m_filePath = QUrl::fromEncoded(torrentPath.toLocal8Bit()).toLocalFile();
|
|
else
|
|
m_filePath = torrentPath;
|
|
|
|
if (!QFile::exists(m_filePath)) {
|
|
MessageBoxRaised::critical(0, tr("I/O Error"), tr("The torrent file '%1' does not exist.").arg(Utils::Fs::toNativePath(m_filePath)));
|
|
return false;
|
|
}
|
|
|
|
QFileInfo fileinfo(m_filePath);
|
|
if (!fileinfo.isReadable()) {
|
|
MessageBoxRaised::critical(0, tr("I/O Error"), tr("The torrent file '%1' cannot be read from the disk. Probably you don't have enough permissions.").arg(Utils::Fs::toNativePath(m_filePath)));
|
|
return false;
|
|
}
|
|
|
|
m_hasMetadata = true;
|
|
QString error;
|
|
m_torrentInfo = BitTorrent::TorrentInfo::loadFromFile(m_filePath, error);
|
|
if (!m_torrentInfo.isValid()) {
|
|
MessageBoxRaised::critical(0, tr("Invalid torrent"), tr("Failed to load the torrent: %1.\nError: %2", "Don't remove the '\n' characters. They insert a newline.").arg(Utils::Fs::toNativePath(m_filePath)).arg(error));
|
|
return false;
|
|
}
|
|
|
|
m_torrentGuard.reset(new TorrentFileGuard(m_filePath));
|
|
m_hash = m_torrentInfo.hash();
|
|
|
|
// Prevent showing the dialog if download is already present
|
|
if (BitTorrent::Session::instance()->isKnownTorrent(m_hash)) {
|
|
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(m_hash);
|
|
if (torrent) {
|
|
if (torrent->isPrivate() || m_torrentInfo.isPrivate()) {
|
|
MessageBoxRaised::critical(0, tr("Already in download list"), tr("Torrent is already in download list. Trackers weren't merged because it is a private torrent."), QMessageBox::Ok);
|
|
}
|
|
else {
|
|
torrent->addTrackers(m_torrentInfo.trackers());
|
|
torrent->addUrlSeeds(m_torrentInfo.urlSeeds());
|
|
MessageBoxRaised::information(0, tr("Already in download list"), tr("Torrent is already in download list. Trackers were merged."), QMessageBox::Ok);
|
|
}
|
|
}
|
|
else {
|
|
MessageBoxRaised::critical(0, tr("Cannot add torrent"), tr("Cannot add this torrent. Perhaps it is already in adding state."), QMessageBox::Ok);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
ui->lblhash->setText(m_hash);
|
|
setupTreeview();
|
|
TMMChanged(ui->comboTTM->currentIndex());
|
|
return true;
|
|
}
|
|
|
|
bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri)
|
|
{
|
|
if (!magnetUri.isValid()) {
|
|
MessageBoxRaised::critical(0, tr("Invalid magnet link"), tr("This magnet link was not recognized"));
|
|
return false;
|
|
}
|
|
|
|
m_torrentGuard.reset(new TorrentFileGuard(QString()));
|
|
m_hash = magnetUri.hash();
|
|
// Prevent showing the dialog if download is already present
|
|
if (BitTorrent::Session::instance()->isKnownTorrent(m_hash)) {
|
|
BitTorrent::TorrentHandle *const torrent = BitTorrent::Session::instance()->findTorrent(m_hash);
|
|
if (torrent) {
|
|
if (torrent->isPrivate()) {
|
|
MessageBoxRaised::critical(0, tr("Already in download list"), tr("Torrent is already in download list. Trackers weren't merged because it is a private torrent."), QMessageBox::Ok);
|
|
}
|
|
else {
|
|
torrent->addTrackers(magnetUri.trackers());
|
|
torrent->addUrlSeeds(magnetUri.urlSeeds());
|
|
MessageBoxRaised::information(0, tr("Already in download list"), tr("Magnet link is already in download list. Trackers were merged."), QMessageBox::Ok);
|
|
}
|
|
}
|
|
else {
|
|
MessageBoxRaised::critical(0, tr("Cannot add torrent"), tr("Cannot add this torrent. Perhaps it is already in adding."), QMessageBox::Ok);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
connect(BitTorrent::Session::instance(), SIGNAL(metadataLoaded(BitTorrent::TorrentInfo)), SLOT(updateMetadata(BitTorrent::TorrentInfo)));
|
|
|
|
// Set dialog title
|
|
QString torrent_name = magnetUri.name();
|
|
setWindowTitle(torrent_name.isEmpty() ? tr("Magnet link") : torrent_name);
|
|
|
|
setupTreeview();
|
|
TMMChanged(ui->comboTTM->currentIndex());
|
|
// Set dialog position
|
|
setdialogPosition();
|
|
|
|
BitTorrent::Session::instance()->loadMetadata(magnetUri);
|
|
setMetadataProgressIndicator(true, tr("Retrieving metadata..."));
|
|
ui->lblhash->setText(m_hash);
|
|
|
|
return true;
|
|
}
|
|
|
|
void AddNewTorrentDialog::showEvent(QShowEvent *event)
|
|
{
|
|
QDialog::showEvent(event);
|
|
if (!isTopLevel()) return;
|
|
|
|
activateWindow();
|
|
raise();
|
|
}
|
|
|
|
void AddNewTorrentDialog::showAdvancedSettings(bool show)
|
|
{
|
|
const int minimumW = minimumWidth();
|
|
setMinimumWidth(width()); // to remain the same width
|
|
if (show) {
|
|
ui->adv_button->setText(QString::fromUtf8(C_UP));
|
|
ui->settings_group->setVisible(true);
|
|
ui->infoGroup->setVisible(true);
|
|
ui->contentTreeView->setVisible(m_hasMetadata);
|
|
static_cast<QVBoxLayout *>(layout())->insertWidget(layout()->indexOf(ui->never_show_cb) + 1, ui->adv_button);
|
|
}
|
|
else {
|
|
ui->adv_button->setText(QString::fromUtf8(C_DOWN));
|
|
ui->settings_group->setVisible(false);
|
|
ui->infoGroup->setVisible(false);
|
|
ui->buttonsHLayout->insertWidget(0, layout()->takeAt(layout()->indexOf(ui->never_show_cb) + 1)->widget());
|
|
}
|
|
adjustSize();
|
|
setMinimumWidth(minimumW);
|
|
}
|
|
|
|
void AddNewTorrentDialog::saveSavePathHistory() const
|
|
{
|
|
QDir selectedSavePath(ui->savePathComboBox->itemData(ui->savePathComboBox->currentIndex()).toString());
|
|
// Get current history
|
|
QStringList history = settings()->loadValue(KEY_SAVEPATHHISTORY).toStringList();
|
|
QList<QDir> historyDirs;
|
|
foreach (const QString dir, history)
|
|
historyDirs << QDir(dir);
|
|
if (!historyDirs.contains(selectedSavePath)) {
|
|
// Add save path to history
|
|
history.push_front(selectedSavePath.absolutePath());
|
|
// Limit list size
|
|
if (history.size() > 8)
|
|
history.pop_back();
|
|
// Save history
|
|
settings()->storeValue(KEY_SAVEPATHHISTORY, history);
|
|
}
|
|
}
|
|
|
|
// save_path is a folder, not an absolute file path
|
|
int AddNewTorrentDialog::indexOfSavePath(const QString &save_path)
|
|
{
|
|
QDir saveDir(save_path);
|
|
for (int i = 0; i < ui->savePathComboBox->count(); ++i)
|
|
if (QDir(ui->savePathComboBox->itemData(i).toString()) == saveDir)
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
void AddNewTorrentDialog::updateDiskSpaceLabel()
|
|
{
|
|
// Determine torrent size
|
|
qulonglong torrent_size = 0;
|
|
|
|
if (m_hasMetadata) {
|
|
if (m_contentModel) {
|
|
const QVector<int> priorities = m_contentModel->model()->getFilePriorities();
|
|
Q_ASSERT(priorities.size() == m_torrentInfo.filesCount());
|
|
for (int i = 0; i < priorities.size(); ++i)
|
|
if (priorities[i] > 0)
|
|
torrent_size += m_torrentInfo.fileSize(i);
|
|
}
|
|
else {
|
|
torrent_size = m_torrentInfo.totalSize();
|
|
}
|
|
}
|
|
|
|
QString size_string = torrent_size ? Utils::Misc::friendlyUnit(torrent_size) : QString(tr("Not Available", "This size is unavailable."));
|
|
size_string += " (";
|
|
size_string += tr("Free space on disk: %1").arg(Utils::Misc::friendlyUnit(Utils::Fs::freeDiskSpaceOnPath(
|
|
ui->savePathComboBox->itemData(
|
|
ui->savePathComboBox->currentIndex()).toString())));
|
|
size_string += ")";
|
|
ui->size_lbl->setText(size_string);
|
|
}
|
|
|
|
void AddNewTorrentDialog::onSavePathChanged(int index)
|
|
{
|
|
// Toggle default save path setting checkbox visibility
|
|
ui->defaultSavePathCheckBox->setChecked(false);
|
|
ui->defaultSavePathCheckBox->setVisible(
|
|
QDir(ui->savePathComboBox->itemData(ui->savePathComboBox->currentIndex()).toString())
|
|
!= QDir(BitTorrent::Session::instance()->defaultSavePath()));
|
|
|
|
// Remember index
|
|
m_oldIndex = index;
|
|
|
|
updateDiskSpaceLabel();
|
|
}
|
|
|
|
void AddNewTorrentDialog::categoryChanged(int index)
|
|
{
|
|
Q_UNUSED(index);
|
|
|
|
if (ui->comboTTM->currentIndex() == 1) {
|
|
QString savePath = BitTorrent::Session::instance()->categorySavePath(ui->categoryComboBox->currentText());
|
|
ui->savePathComboBox->setItemText(0, Utils::Fs::toNativePath(savePath));
|
|
ui->savePathComboBox->setItemData(0, savePath);
|
|
}
|
|
}
|
|
|
|
void AddNewTorrentDialog::setSavePath(const QString &newPath)
|
|
{
|
|
int existingIndex = indexOfSavePath(newPath);
|
|
if (existingIndex < 0) {
|
|
// New path, prepend to combo box
|
|
ui->savePathComboBox->insertItem(0, Utils::Fs::toNativePath(newPath), newPath);
|
|
existingIndex = 0;
|
|
}
|
|
ui->savePathComboBox->setCurrentIndex(existingIndex);
|
|
onSavePathChanged(existingIndex);
|
|
}
|
|
|
|
void AddNewTorrentDialog::browseButton_clicked()
|
|
{
|
|
disconnect(ui->savePathComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onSavePathChanged(int)));
|
|
|
|
// User is asking for a new save path
|
|
QString curSavePath = ui->savePathComboBox->itemText(m_oldIndex);
|
|
QString newPath;
|
|
|
|
if (!curSavePath.isEmpty() && QDir(curSavePath).exists())
|
|
newPath = QFileDialog::getExistingDirectory(this, tr("Choose save path"), curSavePath);
|
|
else
|
|
newPath = QFileDialog::getExistingDirectory(this, tr("Choose save path"), QDir::homePath());
|
|
|
|
if (!newPath.isEmpty()) {
|
|
setSavePath(newPath);
|
|
}
|
|
else {
|
|
// Restore index
|
|
ui->savePathComboBox->setCurrentIndex(m_oldIndex);
|
|
}
|
|
|
|
connect(ui->savePathComboBox, SIGNAL(currentIndexChanged(int)), SLOT(onSavePathChanged(int)));
|
|
}
|
|
|
|
void AddNewTorrentDialog::renameSelectedFile()
|
|
{
|
|
const QModelIndexList selectedIndexes = ui->contentTreeView->selectionModel()->selectedRows(0);
|
|
if (selectedIndexes.size() != 1)
|
|
return;
|
|
const QModelIndex &index = selectedIndexes.first();
|
|
if (!index.isValid())
|
|
return;
|
|
// Ask for new name
|
|
bool ok;
|
|
const QString new_name_last = AutoExpandableDialog::getText(this, tr("Rename the file"),
|
|
tr("New name:"), QLineEdit::Normal,
|
|
index.data().toString(), &ok).trimmed();
|
|
if (ok && !new_name_last.isEmpty()) {
|
|
if (!Utils::Fs::isValidFileSystemName(new_name_last)) {
|
|
MessageBoxRaised::warning(this, tr("The file could not be renamed"),
|
|
tr("This file name contains forbidden characters, please choose a different one."),
|
|
QMessageBox::Ok);
|
|
return;
|
|
}
|
|
if (m_contentModel->itemType(index) == TorrentContentModelItem::FileType) {
|
|
// File renaming
|
|
const int file_index = m_contentModel->getFileIndex(index);
|
|
QString old_name = Utils::Fs::fromNativePath(m_torrentInfo.filePath(file_index));
|
|
qDebug("Old name: %s", qPrintable(old_name));
|
|
QStringList path_items = old_name.split("/");
|
|
path_items.removeLast();
|
|
path_items << new_name_last;
|
|
QString new_name = path_items.join("/");
|
|
if (Utils::Fs::sameFileNames(old_name, new_name)) {
|
|
qDebug("Name did not change");
|
|
return;
|
|
}
|
|
new_name = Utils::Fs::expandPath(new_name);
|
|
qDebug("New name: %s", qPrintable(new_name));
|
|
// Check if that name is already used
|
|
for (int i = 0; i < m_torrentInfo.filesCount(); ++i) {
|
|
if (i == file_index) continue;
|
|
if (Utils::Fs::sameFileNames(m_torrentInfo.filePath(i), new_name)) {
|
|
// Display error message
|
|
MessageBoxRaised::warning(this, tr("The file could not be renamed"),
|
|
tr("This name is already in use in this folder. Please use a different name."),
|
|
QMessageBox::Ok);
|
|
return;
|
|
}
|
|
}
|
|
qDebug("Renaming %s to %s", qPrintable(old_name), qPrintable(new_name));
|
|
m_torrentInfo.renameFile(file_index, new_name);
|
|
// Rename in torrent files model too
|
|
m_contentModel->setData(index, new_name_last);
|
|
}
|
|
else {
|
|
// Folder renaming
|
|
QStringList path_items;
|
|
path_items << index.data().toString();
|
|
QModelIndex parent = m_contentModel->parent(index);
|
|
while (parent.isValid()) {
|
|
path_items.prepend(parent.data().toString());
|
|
parent = m_contentModel->parent(parent);
|
|
}
|
|
const QString old_path = path_items.join("/");
|
|
path_items.removeLast();
|
|
path_items << new_name_last;
|
|
QString new_path = path_items.join("/");
|
|
if (Utils::Fs::sameFileNames(old_path, new_path)) {
|
|
qDebug("Name did not change");
|
|
return;
|
|
}
|
|
if (!new_path.endsWith("/")) new_path += "/";
|
|
// Check for overwriting
|
|
for (int i = 0; i < m_torrentInfo.filesCount(); ++i) {
|
|
const QString ¤t_name = m_torrentInfo.filePath(i);
|
|
#if defined(Q_OS_UNIX) || defined(Q_WS_QWS)
|
|
if (current_name.startsWith(new_path, Qt::CaseSensitive)) {
|
|
#else
|
|
if (current_name.startsWith(new_path, Qt::CaseInsensitive)) {
|
|
#endif
|
|
MessageBoxRaised::warning(this, tr("The folder could not be renamed"),
|
|
tr("This name is already in use in this folder. Please use a different name."),
|
|
QMessageBox::Ok);
|
|
return;
|
|
}
|
|
}
|
|
// Replace path in all files
|
|
for (int i = 0; i < m_torrentInfo.filesCount(); ++i) {
|
|
const QString ¤t_name = m_torrentInfo.filePath(i);
|
|
if (current_name.startsWith(old_path)) {
|
|
QString new_name = current_name;
|
|
new_name.replace(0, old_path.length(), new_path);
|
|
new_name = Utils::Fs::expandPath(new_name);
|
|
qDebug("Rename %s to %s", qPrintable(current_name), qPrintable(new_name));
|
|
m_torrentInfo.renameFile(i, new_name);
|
|
}
|
|
}
|
|
|
|
// Rename folder in torrent files model too
|
|
m_contentModel->setData(index, new_name_last);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AddNewTorrentDialog::setdialogPosition()
|
|
{
|
|
qApp->processEvents();
|
|
QPoint center(Utils::Misc::screenCenter(this));
|
|
// Adjust y
|
|
int y = settings()->loadValue(KEY_POSITION, -1).toInt();
|
|
if (y >= 0) {
|
|
center.setY(y);
|
|
}
|
|
else {
|
|
center.ry() -= 120;
|
|
if (center.y() < 0)
|
|
center.setY(0);
|
|
}
|
|
move(center);
|
|
}
|
|
|
|
void AddNewTorrentDialog::populateSavePathComboBox()
|
|
{
|
|
QString defSavePath = BitTorrent::Session::instance()->defaultSavePath();
|
|
|
|
ui->savePathComboBox->clear();
|
|
ui->savePathComboBox->addItem(Utils::Fs::toNativePath(defSavePath), defSavePath);
|
|
QDir defaultSaveDir(defSavePath);
|
|
// Load save path history
|
|
foreach (const QString &savePath, settings()->loadValue(KEY_SAVEPATHHISTORY).toStringList())
|
|
if (QDir(savePath) != defaultSaveDir)
|
|
ui->savePathComboBox->addItem(Utils::Fs::toNativePath(savePath), savePath);
|
|
|
|
if (!m_torrentParams.savePath.isEmpty())
|
|
setSavePath(m_torrentParams.savePath);
|
|
}
|
|
|
|
void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
|
|
{
|
|
QMenu myFilesLlistMenu;
|
|
const QModelIndexList selectedRows = ui->contentTreeView->selectionModel()->selectedRows(0);
|
|
QAction *actRename = 0;
|
|
if (selectedRows.size() == 1) {
|
|
actRename = myFilesLlistMenu.addAction(GuiIconProvider::instance()->getIcon("edit-rename"), tr("Rename..."));
|
|
myFilesLlistMenu.addSeparator();
|
|
}
|
|
QMenu subMenu;
|
|
subMenu.setTitle(tr("Priority"));
|
|
subMenu.addAction(ui->actionNot_downloaded);
|
|
subMenu.addAction(ui->actionNormal);
|
|
subMenu.addAction(ui->actionHigh);
|
|
subMenu.addAction(ui->actionMaximum);
|
|
myFilesLlistMenu.addMenu(&subMenu);
|
|
// Call menu
|
|
QAction *act = myFilesLlistMenu.exec(QCursor::pos());
|
|
if (act) {
|
|
if (act == actRename) {
|
|
renameSelectedFile();
|
|
}
|
|
else {
|
|
int prio = prio::NORMAL;
|
|
if (act == ui->actionHigh)
|
|
prio = prio::HIGH;
|
|
else if (act == ui->actionMaximum)
|
|
prio = prio::MAXIMUM;
|
|
else if (act == ui->actionNot_downloaded)
|
|
prio = prio::IGNORED;
|
|
|
|
qDebug("Setting files priority");
|
|
foreach (const QModelIndex &index, selectedRows) {
|
|
qDebug("Setting priority(%d) for file at row %d", prio, index.row());
|
|
m_contentModel->setData(m_contentModel->index(index.row(), PRIORITY, index.parent()), prio);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void AddNewTorrentDialog::accept()
|
|
{
|
|
if (!m_hasMetadata)
|
|
disconnect(this, SLOT(updateMetadata(const BitTorrent::TorrentInfo&)));
|
|
|
|
// TODO: Check if destination actually exists
|
|
m_torrentParams.skipChecking = ui->skipCheckingCheckBox->isChecked();
|
|
|
|
// Category
|
|
m_torrentParams.category = ui->categoryComboBox->currentText();
|
|
|
|
if (ui->defaultCategoryCheckbox->isChecked())
|
|
settings()->storeValue(KEY_DEFAULTCATEGORY, m_torrentParams.category);
|
|
|
|
// Save file priorities
|
|
if (m_contentModel)
|
|
m_torrentParams.filePriorities = m_contentModel->model()->getFilePriorities();
|
|
|
|
m_torrentParams.addPaused = TriStateBool(!ui->startTorrentCheckBox->isChecked());
|
|
m_torrentParams.createSubfolder = TriStateBool(ui->createSubfolderCheckBox->isChecked());
|
|
|
|
QString savePath = ui->savePathComboBox->itemData(ui->savePathComboBox->currentIndex()).toString();
|
|
if (ui->comboTTM->currentIndex() != 1) { // 0 is Manual mode and 1 is Automatic mode. Handle all non 1 values as manual mode.
|
|
m_torrentParams.savePath = savePath;
|
|
saveSavePathHistory();
|
|
if (ui->defaultSavePathCheckBox->isChecked())
|
|
BitTorrent::Session::instance()->setDefaultSavePath(savePath);
|
|
}
|
|
|
|
setEnabled(!ui->never_show_cb->isChecked());
|
|
|
|
// Add torrent
|
|
if (!m_hasMetadata)
|
|
BitTorrent::Session::instance()->addTorrent(m_hash, m_torrentParams);
|
|
else
|
|
BitTorrent::Session::instance()->addTorrent(m_torrentInfo, m_torrentParams);
|
|
|
|
m_torrentGuard->markAsAddedToSession();
|
|
QDialog::accept();
|
|
}
|
|
|
|
void AddNewTorrentDialog::reject()
|
|
{
|
|
if (!m_hasMetadata) {
|
|
disconnect(this, SLOT(updateMetadata(BitTorrent::TorrentInfo)));
|
|
setMetadataProgressIndicator(false);
|
|
BitTorrent::Session::instance()->cancelLoadMetadata(m_hash);
|
|
}
|
|
|
|
QDialog::reject();
|
|
}
|
|
|
|
void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &info)
|
|
{
|
|
if (info.hash() != m_hash) return;
|
|
|
|
disconnect(this, SLOT(updateMetadata(BitTorrent::TorrentInfo)));
|
|
if (!info.isValid()) {
|
|
MessageBoxRaised::critical(0, tr("I/O Error"), ("Invalid metadata."));
|
|
setMetadataProgressIndicator(false, tr("Invalid metadata"));
|
|
return;
|
|
}
|
|
|
|
// Good to go
|
|
m_torrentInfo = info;
|
|
m_hasMetadata = true;
|
|
setMetadataProgressIndicator(true, tr("Parsing metadata..."));
|
|
|
|
// Update UI
|
|
setupTreeview();
|
|
setMetadataProgressIndicator(false, tr("Metadata retrieval complete"));
|
|
}
|
|
|
|
void AddNewTorrentDialog::setMetadataProgressIndicator(bool visibleIndicator, const QString &labelText)
|
|
{
|
|
// Always show info label when waiting for metadata
|
|
ui->lblMetaLoading->setVisible(true);
|
|
ui->lblMetaLoading->setText(labelText);
|
|
ui->progMetaLoading->setVisible(visibleIndicator);
|
|
}
|
|
|
|
void AddNewTorrentDialog::setupTreeview()
|
|
{
|
|
if (!m_hasMetadata) {
|
|
setCommentText(tr("Not Available", "This comment is unavailable"));
|
|
ui->date_lbl->setText(tr("Not Available", "This date is unavailable"));
|
|
}
|
|
else {
|
|
// Set dialog title
|
|
setWindowTitle(m_torrentInfo.name());
|
|
|
|
// Set torrent information
|
|
setCommentText(Utils::Misc::parseHtmlLinks(m_torrentInfo.comment()));
|
|
ui->date_lbl->setText(!m_torrentInfo.creationDate().isNull() ? m_torrentInfo.creationDate().toString(Qt::DefaultLocaleShortDate) : tr("Not available"));
|
|
|
|
// Prepare content tree
|
|
m_contentModel = new TorrentContentFilterModel(this);
|
|
connect(m_contentModel->model(), SIGNAL(filteredFilesChanged()), SLOT(updateDiskSpaceLabel()));
|
|
ui->contentTreeView->setModel(m_contentModel);
|
|
m_contentDelegate = new PropListDelegate(nullptr);
|
|
ui->contentTreeView->setItemDelegate(m_contentDelegate);
|
|
connect(ui->contentTreeView, SIGNAL(clicked(const QModelIndex&)), ui->contentTreeView, SLOT(edit(const QModelIndex&)));
|
|
connect(ui->contentTreeView, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(displayContentTreeMenu(const QPoint&)));
|
|
|
|
// List files in torrent
|
|
m_contentModel->model()->setupModelData(m_torrentInfo);
|
|
if (!m_headerState.isEmpty())
|
|
ui->contentTreeView->header()->restoreState(m_headerState);
|
|
|
|
// Hide useless columns after loading the header state
|
|
ui->contentTreeView->hideColumn(PROGRESS);
|
|
ui->contentTreeView->hideColumn(REMAINING);
|
|
|
|
// Expand root folder
|
|
ui->contentTreeView->setExpanded(m_contentModel->index(0, 0), true);
|
|
}
|
|
|
|
updateDiskSpaceLabel();
|
|
showAdvancedSettings(settings()->loadValue(KEY_EXPANDED, false).toBool());
|
|
// Set dialog position
|
|
setdialogPosition();
|
|
}
|
|
|
|
void AddNewTorrentDialog::handleDownloadFailed(const QString &url, const QString &reason)
|
|
{
|
|
MessageBoxRaised::critical(0, tr("Download Error"), QString("Cannot download '%1': %2").arg(url).arg(reason));
|
|
this->deleteLater();
|
|
}
|
|
|
|
void AddNewTorrentDialog::handleRedirectedToMagnet(const QString &url, const QString &magnetUri)
|
|
{
|
|
Q_UNUSED(url)
|
|
if (loadMagnet(BitTorrent::MagnetUri(magnetUri)))
|
|
open();
|
|
else
|
|
this->deleteLater();
|
|
}
|
|
|
|
void AddNewTorrentDialog::handleDownloadFinished(const QString &url, const QString &filePath)
|
|
{
|
|
Q_UNUSED(url)
|
|
if (loadTorrent(filePath))
|
|
open();
|
|
else
|
|
this->deleteLater();
|
|
}
|
|
|
|
void AddNewTorrentDialog::TMMChanged(int index)
|
|
{
|
|
if (index != 1) { // 0 is Manual mode and 1 is Automatic mode. Handle all non 1 values as manual mode.
|
|
populateSavePathComboBox();
|
|
ui->groupBoxSavePath->setEnabled(true);
|
|
ui->savePathComboBox->blockSignals(false);
|
|
ui->savePathComboBox->setCurrentIndex(m_oldIndex < ui->savePathComboBox->count() ? m_oldIndex : ui->savePathComboBox->count() - 1);
|
|
ui->adv_button->setEnabled(true);
|
|
}
|
|
else {
|
|
ui->groupBoxSavePath->setEnabled(false);
|
|
ui->savePathComboBox->blockSignals(true);
|
|
ui->savePathComboBox->clear();
|
|
QString savePath = BitTorrent::Session::instance()->categorySavePath(ui->categoryComboBox->currentText());
|
|
ui->savePathComboBox->addItem(Utils::Fs::toNativePath(savePath), savePath);
|
|
ui->defaultSavePathCheckBox->setVisible(false);
|
|
ui->adv_button->setChecked(true);
|
|
ui->adv_button->setEnabled(false);
|
|
showAdvancedSettings(true);
|
|
}
|
|
}
|
|
|
|
void AddNewTorrentDialog::setCommentText(const QString &str) const
|
|
{
|
|
ui->commentLabel->setText(str);
|
|
|
|
// workaround for the additional space introduced by QScrollArea
|
|
int lineHeight = ui->commentLabel->fontMetrics().lineSpacing();
|
|
int lines = 1 + str.count("\n");
|
|
int height = lineHeight * lines;
|
|
ui->scrollArea->setMaximumHeight(height);
|
|
}
|
|
|
|
void AddNewTorrentDialog::doNotDeleteTorrentClicked(bool checked)
|
|
{
|
|
m_torrentGuard->setAutoRemove(!checked);
|
|
}
|