Don't stuck loading on mismatching info-hashes in resume data
[qBittorrent.git] / src / gui / addnewtorrentdialog.cpp
blob5a02d3df7c593e8b5972958e541a78417f9bf07e
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2022-2023 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
30 #include "addnewtorrentdialog.h"
32 #include <algorithm>
33 #include <functional>
35 #include <QAction>
36 #include <QDateTime>
37 #include <QDebug>
38 #include <QDir>
39 #include <QFileDialog>
40 #include <QMenu>
41 #include <QMessageBox>
42 #include <QPushButton>
43 #include <QShortcut>
44 #include <QString>
45 #include <QUrl>
46 #include <QVector>
48 #include "base/bittorrent/downloadpriority.h"
49 #include "base/bittorrent/infohash.h"
50 #include "base/bittorrent/session.h"
51 #include "base/bittorrent/torrent.h"
52 #include "base/bittorrent/torrentcontenthandler.h"
53 #include "base/bittorrent/torrentcontentlayout.h"
54 #include "base/global.h"
55 #include "base/preferences.h"
56 #include "base/settingsstorage.h"
57 #include "base/torrentfileguard.h"
58 #include "base/utils/compare.h"
59 #include "base/utils/fs.h"
60 #include "base/utils/misc.h"
61 #include "base/utils/string.h"
62 #include "lineedit.h"
63 #include "torrenttagsdialog.h"
65 #include "ui_addnewtorrentdialog.h"
67 namespace
69 #define SETTINGS_KEY(name) u"AddNewTorrentDialog/" name
70 const QString KEY_SAVEPATHHISTORY = SETTINGS_KEY(u"SavePathHistory"_s);
71 const QString KEY_DOWNLOADPATHHISTORY = SETTINGS_KEY(u"DownloadPathHistory"_s);
73 // just a shortcut
74 inline SettingsStorage *settings()
76 return SettingsStorage::instance();
79 // savePath is a folder, not an absolute file path
80 int indexOfPath(const FileSystemPathComboEdit *fsPathEdit, const Path &savePath)
82 for (int i = 0; i < fsPathEdit->count(); ++i)
84 if (fsPathEdit->item(i) == savePath)
85 return i;
87 return -1;
90 qint64 queryFreeDiskSpace(const Path &path)
92 const Path root = path.rootItem();
93 Path current = path;
94 qint64 freeSpace = Utils::Fs::freeDiskSpaceOnPath(current);
96 // for non-existent directories (which will be created on demand) `Utils::Fs::freeDiskSpaceOnPath`
97 // will return invalid value so instead query its parent/ancestor paths
98 while ((freeSpace < 0) && (current != root))
100 current = current.parentPath();
101 freeSpace = Utils::Fs::freeDiskSpaceOnPath(current);
103 return freeSpace;
106 void setPath(FileSystemPathComboEdit *fsPathEdit, const Path &newPath)
108 int existingIndex = indexOfPath(fsPathEdit, newPath);
109 if (existingIndex < 0)
111 // New path, prepend to combo box
112 fsPathEdit->insertItem(0, newPath);
113 existingIndex = 0;
116 fsPathEdit->setCurrentIndex(existingIndex);
119 void updatePathHistory(const QString &settingsKey, const Path &path, const int maxLength)
121 // Add last used save path to the front of history
123 auto pathList = settings()->loadValue<QStringList>(settingsKey);
125 const int selectedSavePathIndex = pathList.indexOf(path.toString());
126 if (selectedSavePathIndex > -1)
127 pathList.move(selectedSavePathIndex, 0);
128 else
129 pathList.prepend(path.toString());
131 settings()->storeValue(settingsKey, QStringList(pathList.mid(0, maxLength)));
135 class AddNewTorrentDialog::TorrentContentAdaptor final
136 : public BitTorrent::TorrentContentHandler
138 public:
139 TorrentContentAdaptor(const BitTorrent::TorrentInfo &torrentInfo, PathList &filePaths
140 , QVector<BitTorrent::DownloadPriority> &filePriorities, std::function<void ()> onFilePrioritiesChanged)
141 : m_torrentInfo {torrentInfo}
142 , m_filePaths {filePaths}
143 , m_filePriorities {filePriorities}
144 , m_onFilePrioritiesChanged {std::move(onFilePrioritiesChanged)}
146 Q_ASSERT(filePaths.isEmpty() || (filePaths.size() == m_torrentInfo.filesCount()));
148 m_originalRootFolder = Path::findRootFolder(m_torrentInfo.filePaths());
149 m_currentContentLayout = (m_originalRootFolder.isEmpty()
150 ? BitTorrent::TorrentContentLayout::NoSubfolder
151 : BitTorrent::TorrentContentLayout::Subfolder);
153 if (const int fileCount = filesCount(); !m_filePriorities.isEmpty() && (fileCount >= 0))
154 m_filePriorities.resize(fileCount, BitTorrent::DownloadPriority::Normal);
157 bool hasMetadata() const override
159 return m_torrentInfo.isValid();
162 int filesCount() const override
164 return m_torrentInfo.filesCount();
167 qlonglong fileSize(const int index) const override
169 Q_ASSERT((index >= 0) && (index < filesCount()));
170 return m_torrentInfo.fileSize(index);
173 Path filePath(const int index) const override
175 Q_ASSERT((index >= 0) && (index < filesCount()));
176 return (m_filePaths.isEmpty() ? m_torrentInfo.filePath(index) : m_filePaths.at(index));
179 void renameFile(const int index, const Path &newFilePath) override
181 Q_ASSERT((index >= 0) && (index < filesCount()));
182 const Path currentFilePath = filePath(index);
183 if (currentFilePath == newFilePath)
184 return;
186 if (m_filePaths.isEmpty())
187 m_filePaths = m_torrentInfo.filePaths();
189 m_filePaths[index] = newFilePath;
192 void applyContentLayout(const BitTorrent::TorrentContentLayout contentLayout)
194 Q_ASSERT(hasMetadata());
195 Q_ASSERT(!m_filePaths.isEmpty());
197 const auto originalContentLayout = (m_originalRootFolder.isEmpty()
198 ? BitTorrent::TorrentContentLayout::NoSubfolder
199 : BitTorrent::TorrentContentLayout::Subfolder);
200 const auto newContentLayout = ((contentLayout == BitTorrent::TorrentContentLayout::Original)
201 ? originalContentLayout : contentLayout);
202 if (newContentLayout != m_currentContentLayout)
204 if (newContentLayout == BitTorrent::TorrentContentLayout::NoSubfolder)
206 Path::stripRootFolder(m_filePaths);
208 else
210 const auto rootFolder = ((originalContentLayout == BitTorrent::TorrentContentLayout::Subfolder)
211 ? m_originalRootFolder : m_filePaths.at(0).removedExtension());
212 Path::addRootFolder(m_filePaths, rootFolder);
215 m_currentContentLayout = newContentLayout;
219 QVector<BitTorrent::DownloadPriority> filePriorities() const override
221 return m_filePriorities.isEmpty()
222 ? QVector<BitTorrent::DownloadPriority>(filesCount(), BitTorrent::DownloadPriority::Normal)
223 : m_filePriorities;
226 QVector<qreal> filesProgress() const override
228 return QVector<qreal>(filesCount(), 0);
231 QVector<qreal> availableFileFractions() const override
233 return QVector<qreal>(filesCount(), 0);
236 void fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const override
238 resultHandler(availableFileFractions());
241 void prioritizeFiles(const QVector<BitTorrent::DownloadPriority> &priorities) override
243 Q_ASSERT(priorities.size() == filesCount());
244 m_filePriorities = priorities;
245 if (m_onFilePrioritiesChanged)
246 m_onFilePrioritiesChanged();
249 Path actualStorageLocation() const override
251 return {};
254 Path actualFilePath([[maybe_unused]] int fileIndex) const override
256 return {};
259 void flushCache() const override
263 private:
264 const BitTorrent::TorrentInfo &m_torrentInfo;
265 PathList &m_filePaths;
266 QVector<BitTorrent::DownloadPriority> &m_filePriorities;
267 std::function<void ()> m_onFilePrioritiesChanged;
268 Path m_originalRootFolder;
269 BitTorrent::TorrentContentLayout m_currentContentLayout;
272 AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &torrentDescr
273 , const BitTorrent::AddTorrentParams &inParams, QWidget *parent)
274 : QDialog(parent)
275 , m_ui {new Ui::AddNewTorrentDialog}
276 , m_torrentDescr {torrentDescr}
277 , m_torrentParams {inParams}
278 , m_filterLine {new LineEdit(this)}
279 , m_storeDialogSize {SETTINGS_KEY(u"DialogSize"_s)}
280 , m_storeDefaultCategory {SETTINGS_KEY(u"DefaultCategory"_s)}
281 , m_storeRememberLastSavePath {SETTINGS_KEY(u"RememberLastSavePath"_s)}
282 , m_storeTreeHeaderState {u"GUI/Qt6/" SETTINGS_KEY(u"TreeHeaderState"_s)}
283 , m_storeSplitterState {u"GUI/Qt6/" SETTINGS_KEY(u"SplitterState"_s)}
285 // TODO: set dialog file properties using m_torrentParams.filePriorities
286 m_ui->setupUi(this);
288 connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
289 connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
291 m_ui->lblMetaLoading->setVisible(false);
292 m_ui->progMetaLoading->setVisible(false);
293 m_ui->buttonSave->setVisible(false);
294 connect(m_ui->buttonSave, &QPushButton::clicked, this, &AddNewTorrentDialog::saveTorrentFile);
296 m_ui->savePath->setMode(FileSystemPathEdit::Mode::DirectorySave);
297 m_ui->savePath->setDialogCaption(tr("Choose save path"));
298 m_ui->savePath->setMaxVisibleItems(20);
300 const auto *session = BitTorrent::Session::instance();
302 m_ui->downloadPath->setMode(FileSystemPathEdit::Mode::DirectorySave);
303 m_ui->downloadPath->setDialogCaption(tr("Choose save path"));
304 m_ui->downloadPath->setMaxVisibleItems(20);
306 m_ui->addToQueueTopCheckBox->setChecked(m_torrentParams.addToQueueTop.value_or(session->isAddTorrentToQueueTop()));
307 m_ui->startTorrentCheckBox->setChecked(!m_torrentParams.addPaused.value_or(session->isAddTorrentPaused()));
308 m_ui->stopConditionComboBox->setToolTip(
309 u"<html><body><p><b>" + tr("None") + u"</b> - " + tr("No stop condition is set.") + u"</p><p><b>" +
310 tr("Metadata received") + u"</b> - " + tr("Torrent will stop after metadata is received.") +
311 u" <em>" + tr("Torrents that have metadata initially aren't affected.") + u"</em></p><p><b>" +
312 tr("Files checked") + u"</b> - " + tr("Torrent will stop after files are initially checked.") +
313 u" <em>" + tr("This will also download metadata if it wasn't there initially.") + u"</em></p></body></html>");
314 m_ui->stopConditionComboBox->setItemData(0, QVariant::fromValue(BitTorrent::Torrent::StopCondition::None));
315 m_ui->stopConditionComboBox->setItemData(1, QVariant::fromValue(BitTorrent::Torrent::StopCondition::MetadataReceived));
316 m_ui->stopConditionComboBox->setItemData(2, QVariant::fromValue(BitTorrent::Torrent::StopCondition::FilesChecked));
317 m_ui->stopConditionComboBox->setCurrentIndex(m_ui->stopConditionComboBox->findData(
318 QVariant::fromValue(m_torrentParams.stopCondition.value_or(session->torrentStopCondition()))));
319 m_ui->stopConditionLabel->setEnabled(m_ui->startTorrentCheckBox->isChecked());
320 m_ui->stopConditionComboBox->setEnabled(m_ui->startTorrentCheckBox->isChecked());
321 connect(m_ui->startTorrentCheckBox, &QCheckBox::toggled, this, [this](const bool checked)
323 m_ui->stopConditionLabel->setEnabled(checked);
324 m_ui->stopConditionComboBox->setEnabled(checked);
327 m_ui->comboTTM->blockSignals(true); // the TreeView size isn't correct if the slot does its job at this point
328 m_ui->comboTTM->setCurrentIndex(session->isAutoTMMDisabledByDefault() ? 0 : 1);
329 m_ui->comboTTM->blockSignals(false);
330 connect(m_ui->comboTTM, &QComboBox::currentIndexChanged, this, &AddNewTorrentDialog::TMMChanged);
332 connect(m_ui->savePath, &FileSystemPathEdit::selectedPathChanged, this, &AddNewTorrentDialog::onSavePathChanged);
333 connect(m_ui->downloadPath, &FileSystemPathEdit::selectedPathChanged, this, &AddNewTorrentDialog::onDownloadPathChanged);
334 connect(m_ui->groupBoxDownloadPath, &QGroupBox::toggled, this, &AddNewTorrentDialog::onUseDownloadPathChanged);
336 m_ui->checkBoxRememberLastSavePath->setChecked(m_storeRememberLastSavePath);
338 m_ui->contentLayoutComboBox->setCurrentIndex(
339 static_cast<int>(m_torrentParams.contentLayout.value_or(session->torrentContentLayout())));
340 connect(m_ui->contentLayoutComboBox, &QComboBox::currentIndexChanged, this, &AddNewTorrentDialog::contentLayoutChanged);
342 m_ui->sequentialCheckBox->setChecked(m_torrentParams.sequential);
343 m_ui->firstLastCheckBox->setChecked(m_torrentParams.firstLastPiecePriority);
345 m_ui->skipCheckingCheckBox->setChecked(m_torrentParams.skipChecking);
346 m_ui->doNotDeleteTorrentCheckBox->setVisible(TorrentFileGuard::autoDeleteMode() != TorrentFileGuard::Never);
348 // Load categories
349 QStringList categories = session->categories();
350 std::sort(categories.begin(), categories.end(), Utils::Compare::NaturalLessThan<Qt::CaseInsensitive>());
351 const QString defaultCategory = m_storeDefaultCategory;
353 if (!m_torrentParams.category.isEmpty())
354 m_ui->categoryComboBox->addItem(m_torrentParams.category);
355 if (!defaultCategory.isEmpty())
356 m_ui->categoryComboBox->addItem(defaultCategory);
357 m_ui->categoryComboBox->addItem(u""_s);
359 for (const QString &category : asConst(categories))
361 if ((category != defaultCategory) && (category != m_torrentParams.category))
362 m_ui->categoryComboBox->addItem(category);
365 connect(m_ui->categoryComboBox, &QComboBox::currentIndexChanged, this, &AddNewTorrentDialog::categoryChanged);
367 m_ui->tagsLineEdit->setText(Utils::String::joinIntoString(m_torrentParams.tags, u", "_s));
368 connect(m_ui->tagsEditButton, &QAbstractButton::clicked, this, [this]
370 auto *dlg = new TorrentTagsDialog(m_torrentParams.tags, this);
371 dlg->setAttribute(Qt::WA_DeleteOnClose);
372 connect(dlg, &TorrentTagsDialog::accepted, this, [this, dlg]
374 m_torrentParams.tags = dlg->tags();
375 m_ui->tagsLineEdit->setText(Utils::String::joinIntoString(m_torrentParams.tags, u", "_s));
377 dlg->open();
380 // Torrent content filtering
381 m_filterLine->setPlaceholderText(tr("Filter files..."));
382 m_filterLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
383 connect(m_filterLine, &LineEdit::textChanged, m_ui->contentTreeView, &TorrentContentWidget::setFilterPattern);
384 m_ui->contentFilterLayout->insertWidget(3, m_filterLine);
385 const auto *focusSearchHotkey = new QShortcut(QKeySequence::Find, this);
386 connect(focusSearchHotkey, &QShortcut::activated, this, [this]()
388 m_filterLine->setFocus();
389 m_filterLine->selectAll();
392 loadState();
394 connect(m_ui->buttonSelectAll, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkAll);
395 connect(m_ui->buttonSelectNone, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkNone);
397 if (const QByteArray state = m_storeTreeHeaderState; !state.isEmpty())
398 m_ui->contentTreeView->header()->restoreState(state);
399 // Hide useless columns after loading the header state
400 m_ui->contentTreeView->hideColumn(TorrentContentWidget::Progress);
401 m_ui->contentTreeView->hideColumn(TorrentContentWidget::Remaining);
402 m_ui->contentTreeView->hideColumn(TorrentContentWidget::Availability);
403 m_ui->contentTreeView->setColumnsVisibilityMode(TorrentContentWidget::ColumnsVisibilityMode::Locked);
404 m_ui->contentTreeView->setDoubleClickAction(TorrentContentWidget::DoubleClickAction::Rename);
406 m_ui->labelCommentData->setText(tr("Not Available", "This comment is unavailable"));
407 m_ui->labelDateData->setText(tr("Not Available", "This date is unavailable"));
409 m_filterLine->blockSignals(true);
411 // Default focus
412 if (m_ui->comboTTM->currentIndex() == 0) // 0 is Manual mode
413 m_ui->savePath->setFocus();
414 else
415 m_ui->categoryComboBox->setFocus();
417 connect(Preferences::instance(), &Preferences::changed, []
419 const int length = Preferences::instance()->addNewTorrentDialogSavePathHistoryLength();
420 settings()->storeValue(KEY_SAVEPATHHISTORY
421 , QStringList(settings()->loadValue<QStringList>(KEY_SAVEPATHHISTORY).mid(0, length)));
424 const BitTorrent::InfoHash infoHash = m_torrentDescr.infoHash();
426 m_ui->labelInfohash1Data->setText(infoHash.v1().isValid() ? infoHash.v1().toString() : tr("N/A"));
427 m_ui->labelInfohash2Data->setText(infoHash.v2().isValid() ? infoHash.v2().toString() : tr("N/A"));
429 if (hasMetadata())
431 setupTreeview();
433 else
435 // Set dialog title
436 const QString torrentName = m_torrentDescr.name();
437 setWindowTitle(torrentName.isEmpty() ? tr("Magnet link") : torrentName);
438 updateDiskSpaceLabel();
439 setMetadataProgressIndicator(true, tr("Retrieving metadata..."));
442 TMMChanged(m_ui->comboTTM->currentIndex());
445 AddNewTorrentDialog::~AddNewTorrentDialog()
447 saveState();
448 delete m_ui;
451 BitTorrent::TorrentDescriptor AddNewTorrentDialog::torrentDescriptor() const
453 return m_torrentDescr;
456 BitTorrent::AddTorrentParams AddNewTorrentDialog::addTorrentParams() const
458 return m_torrentParams;
461 bool AddNewTorrentDialog::isDoNotDeleteTorrentChecked() const
463 return m_ui->doNotDeleteTorrentCheckBox->isChecked();
466 void AddNewTorrentDialog::loadState()
468 if (const QSize dialogSize = m_storeDialogSize; dialogSize.isValid())
469 resize(dialogSize);
471 m_ui->splitter->restoreState(m_storeSplitterState);;
474 void AddNewTorrentDialog::saveState()
476 m_storeDialogSize = size();
477 m_storeSplitterState = m_ui->splitter->saveState();
478 if (hasMetadata())
479 m_storeTreeHeaderState = m_ui->contentTreeView->header()->saveState();
482 void AddNewTorrentDialog::showEvent(QShowEvent *event)
484 QDialog::showEvent(event);
485 if (!Preferences::instance()->isAddNewTorrentDialogTopLevel())
486 return;
488 activateWindow();
489 raise();
492 void AddNewTorrentDialog::updateDiskSpaceLabel()
494 // Determine torrent size
495 qlonglong torrentSize = 0;
497 if (hasMetadata())
499 const auto torrentInfo = *m_torrentDescr.info();
500 const QVector<BitTorrent::DownloadPriority> &priorities = m_contentAdaptor->filePriorities();
501 Q_ASSERT(priorities.size() == torrentInfo.filesCount());
502 for (int i = 0; i < priorities.size(); ++i)
504 if (priorities[i] > BitTorrent::DownloadPriority::Ignored)
505 torrentSize += torrentInfo.fileSize(i);
509 const QString freeSpace = Utils::Misc::friendlyUnit(queryFreeDiskSpace(m_ui->savePath->selectedPath()));
510 const QString sizeString = tr("%1 (Free space on disk: %2)").arg(
511 ((torrentSize > 0) ? Utils::Misc::friendlyUnit(torrentSize) : tr("Not available", "This size is unavailable."))
512 , freeSpace);
513 m_ui->labelSizeData->setText(sizeString);
516 void AddNewTorrentDialog::onSavePathChanged([[maybe_unused]] const Path &newPath)
518 // Remember index
519 m_savePathIndex = m_ui->savePath->currentIndex();
520 updateDiskSpaceLabel();
523 void AddNewTorrentDialog::onDownloadPathChanged([[maybe_unused]] const Path &newPath)
525 // Remember index
526 const int currentPathIndex = m_ui->downloadPath->currentIndex();
527 if (currentPathIndex >= 0)
528 m_downloadPathIndex = m_ui->downloadPath->currentIndex();
531 void AddNewTorrentDialog::onUseDownloadPathChanged(const bool checked)
533 m_useDownloadPath = checked;
534 m_ui->downloadPath->setCurrentIndex(checked ? m_downloadPathIndex : -1);
537 void AddNewTorrentDialog::categoryChanged([[maybe_unused]] const int index)
539 if (m_ui->comboTTM->currentIndex() == 1)
541 const auto *btSession = BitTorrent::Session::instance();
542 const QString categoryName = m_ui->categoryComboBox->currentText();
544 const Path savePath = btSession->categorySavePath(categoryName);
545 m_ui->savePath->setSelectedPath(savePath);
547 const Path downloadPath = btSession->categoryDownloadPath(categoryName);
548 m_ui->downloadPath->setSelectedPath(downloadPath);
550 m_ui->groupBoxDownloadPath->setChecked(!m_ui->downloadPath->selectedPath().isEmpty());
552 updateDiskSpaceLabel();
556 void AddNewTorrentDialog::contentLayoutChanged()
558 if (!hasMetadata())
559 return;
561 const auto contentLayout = static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex());
562 m_contentAdaptor->applyContentLayout(contentLayout);
563 m_ui->contentTreeView->setContentHandler(m_contentAdaptor.get()); // to cause reloading
566 void AddNewTorrentDialog::saveTorrentFile()
568 Q_ASSERT(hasMetadata());
569 if (!hasMetadata()) [[unlikely]]
570 return;
572 const auto torrentInfo = *m_torrentDescr.info();
574 const QString filter {tr("Torrent file (*%1)").arg(TORRENT_FILE_EXTENSION)};
576 Path path {QFileDialog::getSaveFileName(this, tr("Save as torrent file")
577 , QDir::home().absoluteFilePath(torrentInfo.name() + TORRENT_FILE_EXTENSION)
578 , filter)};
579 if (path.isEmpty()) return;
581 if (!path.hasExtension(TORRENT_FILE_EXTENSION))
582 path += TORRENT_FILE_EXTENSION;
584 const auto result = m_torrentDescr.saveToFile(path);
585 if (!result)
587 QMessageBox::critical(this, tr("I/O Error")
588 , tr("Couldn't export torrent metadata file '%1'. Reason: %2.").arg(path.toString(), result.error()));
592 bool AddNewTorrentDialog::hasMetadata() const
594 return m_torrentDescr.info().has_value();
597 void AddNewTorrentDialog::populateSavePaths()
599 const auto *btSession = BitTorrent::Session::instance();
601 m_ui->savePath->blockSignals(true);
602 m_ui->savePath->clear();
603 const auto savePathHistory = settings()->loadValue<QStringList>(KEY_SAVEPATHHISTORY);
604 if (savePathHistory.size() > 0)
606 for (const QString &path : savePathHistory)
607 m_ui->savePath->addItem(Path(path));
609 else
611 m_ui->savePath->addItem(btSession->savePath());
614 if (m_savePathIndex >= 0)
616 m_ui->savePath->setCurrentIndex(std::min(m_savePathIndex, (m_ui->savePath->count() - 1)));
618 else
620 if (!m_torrentParams.savePath.isEmpty())
621 setPath(m_ui->savePath, m_torrentParams.savePath);
622 else if (!m_storeRememberLastSavePath)
623 setPath(m_ui->savePath, btSession->savePath());
624 else
625 m_ui->savePath->setCurrentIndex(0);
627 m_savePathIndex = m_ui->savePath->currentIndex();
630 m_ui->savePath->blockSignals(false);
632 m_ui->downloadPath->blockSignals(true);
633 m_ui->downloadPath->clear();
634 const auto downloadPathHistory = settings()->loadValue<QStringList>(KEY_DOWNLOADPATHHISTORY);
635 if (downloadPathHistory.size() > 0)
637 for (const QString &path : downloadPathHistory)
638 m_ui->downloadPath->addItem(Path(path));
640 else
642 m_ui->downloadPath->addItem(btSession->downloadPath());
645 if (m_downloadPathIndex >= 0)
647 m_ui->downloadPath->setCurrentIndex(m_useDownloadPath ? std::min(m_downloadPathIndex, (m_ui->downloadPath->count() - 1)) : -1);
648 m_ui->groupBoxDownloadPath->setChecked(m_useDownloadPath);
650 else
652 const bool useDownloadPath = m_torrentParams.useDownloadPath.value_or(btSession->isDownloadPathEnabled());
653 m_ui->groupBoxDownloadPath->setChecked(useDownloadPath);
655 if (!m_torrentParams.downloadPath.isEmpty())
656 setPath(m_ui->downloadPath, m_torrentParams.downloadPath);
657 else if (!m_storeRememberLastSavePath)
658 setPath(m_ui->downloadPath, btSession->downloadPath());
659 else
660 m_ui->downloadPath->setCurrentIndex(0);
662 m_downloadPathIndex = m_ui->downloadPath->currentIndex();
663 if (!useDownloadPath)
664 m_ui->downloadPath->setCurrentIndex(-1);
667 m_ui->downloadPath->blockSignals(false);
668 m_ui->groupBoxDownloadPath->blockSignals(false);
671 void AddNewTorrentDialog::accept()
673 // TODO: Check if destination actually exists
674 m_torrentParams.skipChecking = m_ui->skipCheckingCheckBox->isChecked();
676 // Category
677 m_torrentParams.category = m_ui->categoryComboBox->currentText();
678 if (m_ui->defaultCategoryCheckbox->isChecked())
679 m_storeDefaultCategory = m_torrentParams.category;
681 m_storeRememberLastSavePath = m_ui->checkBoxRememberLastSavePath->isChecked();
683 m_torrentParams.addToQueueTop = m_ui->addToQueueTopCheckBox->isChecked();
684 m_torrentParams.addPaused = !m_ui->startTorrentCheckBox->isChecked();
685 m_torrentParams.stopCondition = m_ui->stopConditionComboBox->currentData().value<BitTorrent::Torrent::StopCondition>();
686 m_torrentParams.contentLayout = static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex());
688 m_torrentParams.sequential = m_ui->sequentialCheckBox->isChecked();
689 m_torrentParams.firstLastPiecePriority = m_ui->firstLastCheckBox->isChecked();
691 const bool useAutoTMM = (m_ui->comboTTM->currentIndex() == 1); // 1 is Automatic mode. Handle all non 1 values as manual mode.
692 m_torrentParams.useAutoTMM = useAutoTMM;
693 if (!useAutoTMM)
695 const int savePathHistoryLength = Preferences::instance()->addNewTorrentDialogSavePathHistoryLength();
696 const Path savePath = m_ui->savePath->selectedPath();
697 m_torrentParams.savePath = savePath;
698 updatePathHistory(KEY_SAVEPATHHISTORY, savePath, savePathHistoryLength);
700 m_torrentParams.useDownloadPath = m_ui->groupBoxDownloadPath->isChecked();
701 if (m_torrentParams.useDownloadPath)
703 const Path downloadPath = m_ui->downloadPath->selectedPath();
704 m_torrentParams.downloadPath = downloadPath;
705 updatePathHistory(KEY_DOWNLOADPATHHISTORY, downloadPath, savePathHistoryLength);
707 else
709 m_torrentParams.downloadPath = Path();
712 else
714 m_torrentParams.savePath = Path();
715 m_torrentParams.downloadPath = Path();
716 m_torrentParams.useDownloadPath = std::nullopt;
719 setEnabled(!m_ui->checkBoxNeverShow->isChecked());
721 QDialog::accept();
724 void AddNewTorrentDialog::reject()
726 if (!hasMetadata())
728 setMetadataProgressIndicator(false);
729 BitTorrent::Session::instance()->cancelDownloadMetadata(m_torrentDescr.infoHash().toTorrentID());
732 QDialog::reject();
735 void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata)
737 Q_ASSERT(metadata.isValid());
738 if (!metadata.isValid()) [[unlikely]]
739 return;
741 Q_ASSERT(metadata.matchesInfoHash(m_torrentDescr.infoHash()));
742 if (!metadata.matchesInfoHash(m_torrentDescr.infoHash())) [[unlikely]]
743 return;
745 m_torrentDescr.setTorrentInfo(metadata);
746 setMetadataProgressIndicator(true, tr("Parsing metadata..."));
747 const auto stopCondition = m_ui->stopConditionComboBox->currentData().value<BitTorrent::Torrent::StopCondition>();
748 if (stopCondition == BitTorrent::Torrent::StopCondition::MetadataReceived)
749 m_ui->startTorrentCheckBox->setChecked(false);
751 // Update UI
752 setupTreeview();
753 setMetadataProgressIndicator(false, tr("Metadata retrieval complete"));
755 m_ui->buttonSave->setVisible(true);
756 if (m_torrentDescr.infoHash().v2().isValid())
758 m_ui->buttonSave->setEnabled(false);
759 m_ui->buttonSave->setToolTip(tr("Cannot create v2 torrent until its data is fully downloaded."));
763 void AddNewTorrentDialog::setMetadataProgressIndicator(bool visibleIndicator, const QString &labelText)
765 // Always show info label when waiting for metadata
766 m_ui->lblMetaLoading->setVisible(true);
767 m_ui->lblMetaLoading->setText(labelText);
768 m_ui->progMetaLoading->setVisible(visibleIndicator);
771 void AddNewTorrentDialog::setupTreeview()
773 Q_ASSERT(hasMetadata());
774 if (!hasMetadata()) [[unlikely]]
775 return;
777 // Set dialog title
778 setWindowTitle(m_torrentDescr.name());
780 const auto &torrentInfo = *m_torrentDescr.info();
782 // Set torrent information
783 m_ui->labelCommentData->setText(Utils::Misc::parseHtmlLinks(torrentInfo.comment().toHtmlEscaped()));
784 m_ui->labelDateData->setText(!torrentInfo.creationDate().isNull() ? QLocale().toString(torrentInfo.creationDate(), QLocale::ShortFormat) : tr("Not available"));
786 if (m_torrentParams.filePaths.isEmpty())
787 m_torrentParams.filePaths = torrentInfo.filePaths();
789 m_contentAdaptor = std::make_unique<TorrentContentAdaptor>(torrentInfo, m_torrentParams.filePaths
790 , m_torrentParams.filePriorities, [this] { updateDiskSpaceLabel(); });
792 const auto contentLayout = static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex());
793 m_contentAdaptor->applyContentLayout(contentLayout);
795 if (BitTorrent::Session::instance()->isExcludedFileNamesEnabled())
797 // Check file name blacklist for torrents that are manually added
798 QVector<BitTorrent::DownloadPriority> priorities = m_contentAdaptor->filePriorities();
799 for (int i = 0; i < priorities.size(); ++i)
801 if (priorities[i] == BitTorrent::DownloadPriority::Ignored)
802 continue;
804 if (BitTorrent::Session::instance()->isFilenameExcluded(torrentInfo.filePath(i).filename()))
805 priorities[i] = BitTorrent::DownloadPriority::Ignored;
808 m_contentAdaptor->prioritizeFiles(priorities);
811 m_ui->contentTreeView->setContentHandler(m_contentAdaptor.get());
813 m_filterLine->blockSignals(false);
815 updateDiskSpaceLabel();
818 void AddNewTorrentDialog::TMMChanged(int index)
820 if (index != 1)
821 { // 0 is Manual mode and 1 is Automatic mode. Handle all non 1 values as manual mode.
822 populateSavePaths();
823 m_ui->groupBoxSavePath->setEnabled(true);
825 else
827 const auto *session = BitTorrent::Session::instance();
829 m_ui->groupBoxSavePath->setEnabled(false);
831 m_ui->savePath->blockSignals(true);
832 m_ui->savePath->clear();
833 const Path savePath = session->categorySavePath(m_ui->categoryComboBox->currentText());
834 m_ui->savePath->addItem(savePath);
836 m_ui->downloadPath->blockSignals(true);
837 m_ui->downloadPath->clear();
838 const Path downloadPath = session->categoryDownloadPath(m_ui->categoryComboBox->currentText());
839 m_ui->downloadPath->addItem(downloadPath);
841 m_ui->groupBoxDownloadPath->blockSignals(true);
842 m_ui->groupBoxDownloadPath->setChecked(!downloadPath.isEmpty());
845 updateDiskSpaceLabel();