2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * In addition, as a special exception, the copyright holders give permission to
20 * link this program with the OpenSSL project's "OpenSSL" library (or with
21 * modified versions of it that use the same license as the "OpenSSL" library),
22 * and distribute the linked executables. You must obey the GNU General Public
23 * License in all respects for all of the code used other than "OpenSSL". If you
24 * modify file(s), you may extend this exception to your version of the file(s),
25 * but you are not obligated to do so. If you do not wish to do so, delete this
26 * exception statement from your version.
29 #include "addnewtorrentdialog.h"
33 #include <QFileDialog>
35 #include <QPushButton>
41 #include "base/bittorrent/downloadpriority.h"
42 #include "base/bittorrent/infohash.h"
43 #include "base/bittorrent/magneturi.h"
44 #include "base/bittorrent/session.h"
45 #include "base/bittorrent/torrent.h"
46 #include "base/exceptions.h"
47 #include "base/global.h"
48 #include "base/net/downloadmanager.h"
49 #include "base/settingsstorage.h"
50 #include "base/torrentfileguard.h"
51 #include "base/utils/compare.h"
52 #include "base/utils/fs.h"
53 #include "base/utils/misc.h"
54 #include "autoexpandabledialog.h"
55 #include "properties/proplistdelegate.h"
56 #include "raisedmessagebox.h"
57 #include "torrentcontentfiltermodel.h"
58 #include "torrentcontentmodel.h"
59 #include "ui_addnewtorrentdialog.h"
60 #include "uithememanager.h"
65 #define SETTINGS_KEY(name) "AddNewTorrentDialog/" name
66 const QString KEY_ENABLED
= QStringLiteral(SETTINGS_KEY("Enabled"));
67 const QString KEY_DEFAULTCATEGORY
= QStringLiteral(SETTINGS_KEY("DefaultCategory"));
68 const QString KEY_TREEHEADERSTATE
= QStringLiteral(SETTINGS_KEY("TreeHeaderState"));
69 const QString KEY_TOPLEVEL
= QStringLiteral(SETTINGS_KEY("TopLevel"));
70 const QString KEY_SAVEPATHHISTORY
= QStringLiteral(SETTINGS_KEY("SavePathHistory"));
71 const QString KEY_SAVEPATHHISTORYLENGTH
= QStringLiteral(SETTINGS_KEY("SavePathHistoryLength"));
72 const QString KEY_REMEMBERLASTSAVEPATH
= QStringLiteral(SETTINGS_KEY("RememberLastSavePath"));
75 inline SettingsStorage
*settings()
77 return SettingsStorage::instance();
81 const int AddNewTorrentDialog::minPathHistoryLength
;
82 const int AddNewTorrentDialog::maxPathHistoryLength
;
84 AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams
&inParams
, QWidget
*parent
)
86 , m_ui(new Ui::AddNewTorrentDialog
)
87 , m_contentModel(nullptr)
88 , m_contentDelegate(nullptr)
89 , m_hasMetadata(false)
91 , m_torrentParams(inParams
)
92 , m_storeDialogSize(SETTINGS_KEY("DialogSize"))
93 , m_storeSplitterState(SETTINGS_KEY("SplitterState"))
95 // TODO: set dialog file properties using m_torrentParams.filePriorities
97 setAttribute(Qt::WA_DeleteOnClose
);
99 m_ui
->lblMetaLoading
->setVisible(false);
100 m_ui
->progMetaLoading
->setVisible(false);
101 m_ui
->buttonSave
->setVisible(false);
102 connect(m_ui
->buttonSave
, &QPushButton::clicked
, this, &AddNewTorrentDialog::saveTorrentFile
);
104 m_ui
->savePath
->setMode(FileSystemPathEdit::Mode::DirectorySave
);
105 m_ui
->savePath
->setDialogCaption(tr("Choose save path"));
106 m_ui
->savePath
->setMaxVisibleItems(20);
108 const auto *session
= BitTorrent::Session::instance();
110 m_ui
->startTorrentCheckBox
->setChecked(!m_torrentParams
.addPaused
.value_or(session
->isAddTorrentPaused()));
112 m_ui
->comboTTM
->blockSignals(true); // the TreeView size isn't correct if the slot does it job at this point
113 m_ui
->comboTTM
->setCurrentIndex(!session
->isAutoTMMDisabledByDefault());
114 m_ui
->comboTTM
->blockSignals(false);
115 populateSavePathComboBox();
116 connect(m_ui
->savePath
, &FileSystemPathEdit::selectedPathChanged
, this, &AddNewTorrentDialog::onSavePathChanged
);
118 const bool rememberLastSavePath
= settings()->loadValue(KEY_REMEMBERLASTSAVEPATH
, false);
119 m_ui
->checkBoxRememberLastSavePath
->setChecked(rememberLastSavePath
);
121 m_ui
->contentLayoutComboBox
->setCurrentIndex(
122 static_cast<int>(m_torrentParams
.contentLayout
.value_or(session
->torrentContentLayout())));
124 m_ui
->sequentialCheckBox
->setChecked(m_torrentParams
.sequential
);
125 m_ui
->firstLastCheckBox
->setChecked(m_torrentParams
.firstLastPiecePriority
);
127 m_ui
->skipCheckingCheckBox
->setChecked(m_torrentParams
.skipChecking
);
128 m_ui
->doNotDeleteTorrentCheckBox
->setVisible(TorrentFileGuard::autoDeleteMode() != TorrentFileGuard::Never
);
131 QStringList categories
= session
->categories().keys();
132 std::sort(categories
.begin(), categories
.end(), Utils::Compare::NaturalLessThan
<Qt::CaseInsensitive
>());
133 auto defaultCategory
= settings()->loadValue
<QString
>(KEY_DEFAULTCATEGORY
);
135 if (!m_torrentParams
.category
.isEmpty())
136 m_ui
->categoryComboBox
->addItem(m_torrentParams
.category
);
137 if (!defaultCategory
.isEmpty())
138 m_ui
->categoryComboBox
->addItem(defaultCategory
);
139 m_ui
->categoryComboBox
->addItem("");
141 for (const QString
&category
: asConst(categories
))
142 if (category
!= defaultCategory
&& category
!= m_torrentParams
.category
)
143 m_ui
->categoryComboBox
->addItem(category
);
145 m_ui
->contentTreeView
->header()->setSortIndicator(0, Qt::AscendingOrder
);
148 connect(m_ui
->doNotDeleteTorrentCheckBox
, &QCheckBox::clicked
, this, &AddNewTorrentDialog::doNotDeleteTorrentClicked
);
149 QShortcut
*editHotkey
= new QShortcut(Qt::Key_F2
, m_ui
->contentTreeView
, nullptr, nullptr, Qt::WidgetShortcut
);
150 connect(editHotkey
, &QShortcut::activated
151 , this, [this]() { m_ui
->contentTreeView
->renameSelectedFile(m_torrentInfo
); });
152 connect(m_ui
->contentTreeView
, &QAbstractItemView::doubleClicked
153 , this, [this]() { m_ui
->contentTreeView
->renameSelectedFile(m_torrentInfo
); });
155 m_ui
->buttonBox
->button(QDialogButtonBox::Ok
)->setFocus();
158 AddNewTorrentDialog::~AddNewTorrentDialog()
162 delete m_contentDelegate
;
166 bool AddNewTorrentDialog::isEnabled()
168 return SettingsStorage::instance()->loadValue(KEY_ENABLED
, true);
171 void AddNewTorrentDialog::setEnabled(bool value
)
173 SettingsStorage::instance()->storeValue(KEY_ENABLED
, value
);
176 bool AddNewTorrentDialog::isTopLevel()
178 return SettingsStorage::instance()->loadValue(KEY_TOPLEVEL
, true);
181 void AddNewTorrentDialog::setTopLevel(bool value
)
183 SettingsStorage::instance()->storeValue(KEY_TOPLEVEL
, value
);
186 int AddNewTorrentDialog::savePathHistoryLength()
188 const int defaultHistoryLength
= 8;
189 const int value
= settings()->loadValue(KEY_SAVEPATHHISTORYLENGTH
, defaultHistoryLength
);
190 return qBound(minPathHistoryLength
, value
, maxPathHistoryLength
);
193 void AddNewTorrentDialog::setSavePathHistoryLength(int value
)
195 const int clampedValue
= qBound(minPathHistoryLength
, value
, maxPathHistoryLength
);
196 const int oldValue
= savePathHistoryLength();
197 if (clampedValue
== oldValue
)
200 settings()->storeValue(KEY_SAVEPATHHISTORYLENGTH
, clampedValue
);
201 settings()->storeValue(KEY_SAVEPATHHISTORY
202 , QStringList(settings()->loadValue
<QStringList
>(KEY_SAVEPATHHISTORY
).mid(0, clampedValue
)));
205 void AddNewTorrentDialog::loadState()
207 Utils::Gui::resize(this, m_storeDialogSize
);
208 m_ui
->splitter
->restoreState(m_storeSplitterState
);
209 m_headerState
= settings()->loadValue
<QByteArray
>(KEY_TREEHEADERSTATE
);
212 void AddNewTorrentDialog::saveState()
214 m_storeDialogSize
= size();
215 m_storeSplitterState
= m_ui
->splitter
->saveState();
217 settings()->storeValue(KEY_TREEHEADERSTATE
, m_ui
->contentTreeView
->header()->saveState());
220 void AddNewTorrentDialog::show(const QString
&source
, const BitTorrent::AddTorrentParams
&inParams
, QWidget
*parent
)
222 auto *dlg
= new AddNewTorrentDialog(inParams
, parent
);
224 if (Net::DownloadManager::hasSupportedScheme(source
))
227 Net::DownloadManager::instance()->download(
228 Net::DownloadRequest(source
).limit(MAX_TORRENT_SIZE
)
229 , dlg
, &AddNewTorrentDialog::handleDownloadFinished
);
233 const BitTorrent::MagnetUri
magnetUri(source
);
234 const bool isLoaded
= magnetUri
.isValid()
235 ? dlg
->loadMagnet(magnetUri
)
236 : dlg
->loadTorrentFile(source
);
239 dlg
->QDialog::show();
244 void AddNewTorrentDialog::show(const QString
&source
, QWidget
*parent
)
246 show(source
, BitTorrent::AddTorrentParams(), parent
);
249 bool AddNewTorrentDialog::loadTorrentFile(const QString
&torrentPath
)
251 const QString decodedPath
= torrentPath
.startsWith("file://", Qt::CaseInsensitive
)
252 ? QUrl::fromEncoded(torrentPath
.toLocal8Bit()).toLocalFile()
256 m_torrentInfo
= BitTorrent::TorrentInfo::loadFromFile(decodedPath
, &error
);
257 if (!m_torrentInfo
.isValid())
259 RaisedMessageBox::critical(this, tr("Invalid torrent")
260 , tr("Failed to load the torrent: %1.\nError: %2", "Don't remove the '\n' characters. They insert a newline.")
261 .arg(Utils::Fs::toNativePath(decodedPath
), error
));
265 m_torrentGuard
= std::make_unique
<TorrentFileGuard
>(decodedPath
);
267 return loadTorrentImpl();
270 bool AddNewTorrentDialog::loadTorrentImpl()
272 m_hasMetadata
= true;
273 const auto torrentID
= BitTorrent::TorrentID::fromInfoHash(m_torrentInfo
.infoHash());
275 // Prevent showing the dialog if download is already present
276 if (BitTorrent::Session::instance()->isKnownTorrent(torrentID
))
278 BitTorrent::Torrent
*const torrent
= BitTorrent::Session::instance()->findTorrent(torrentID
);
281 if (torrent
->isPrivate() || m_torrentInfo
.isPrivate())
283 RaisedMessageBox::warning(this, tr("Torrent is already present"), tr("Torrent '%1' is already in the transfer list. Trackers haven't been merged because it is a private torrent.").arg(torrent
->name()), QMessageBox::Ok
);
287 torrent
->addTrackers(m_torrentInfo
.trackers());
288 torrent
->addUrlSeeds(m_torrentInfo
.urlSeeds());
289 RaisedMessageBox::information(this, tr("Torrent is already present"), tr("Torrent '%1' is already in the transfer list. Trackers have been merged.").arg(torrent
->name()), QMessageBox::Ok
);
294 RaisedMessageBox::information(this, tr("Torrent is already present"), tr("Torrent is already queued for processing."), QMessageBox::Ok
);
299 m_ui
->labelInfohash1Data
->setText(m_torrentInfo
.infoHash().v1().isValid() ? m_torrentInfo
.infoHash().v1().toString() : tr("N/A"));
300 m_ui
->labelInfohash2Data
->setText(m_torrentInfo
.infoHash().v2().isValid() ? m_torrentInfo
.infoHash().v2().toString() : tr("N/A"));
302 TMMChanged(m_ui
->comboTTM
->currentIndex());
306 bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri
&magnetUri
)
308 if (!magnetUri
.isValid())
310 RaisedMessageBox::critical(this, tr("Invalid magnet link"), tr("This magnet link was not recognized"));
314 m_torrentGuard
= std::make_unique
<TorrentFileGuard
>();
316 const auto torrentID
= BitTorrent::TorrentID::fromInfoHash(magnetUri
.infoHash());
317 // Prevent showing the dialog if download is already present
318 if (BitTorrent::Session::instance()->isKnownTorrent(torrentID
))
320 BitTorrent::Torrent
*const torrent
= BitTorrent::Session::instance()->findTorrent(torrentID
);
323 if (torrent
->isPrivate())
325 RaisedMessageBox::warning(this, tr("Torrent is already present"), tr("Torrent '%1' is already in the transfer list. Trackers haven't been merged because it is a private torrent.").arg(torrent
->name()), QMessageBox::Ok
);
329 torrent
->addTrackers(magnetUri
.trackers());
330 torrent
->addUrlSeeds(magnetUri
.urlSeeds());
331 RaisedMessageBox::information(this, tr("Torrent is already present"), tr("Magnet link '%1' is already in the transfer list. Trackers have been merged.").arg(torrent
->name()), QMessageBox::Ok
);
336 RaisedMessageBox::information(this, tr("Torrent is already present"), tr("Magnet link is already queued for processing."), QMessageBox::Ok
);
341 connect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataDownloaded
, this, &AddNewTorrentDialog::updateMetadata
);
344 const QString torrentName
= magnetUri
.name();
345 setWindowTitle(torrentName
.isEmpty() ? tr("Magnet link") : torrentName
);
348 TMMChanged(m_ui
->comboTTM
->currentIndex());
350 BitTorrent::Session::instance()->downloadMetadata(magnetUri
);
351 setMetadataProgressIndicator(true, tr("Retrieving metadata..."));
352 m_ui
->labelInfohash1Data
->setText(magnetUri
.infoHash().v1().isValid() ? magnetUri
.infoHash().v1().toString() : tr("N/A"));
353 m_ui
->labelInfohash2Data
->setText(magnetUri
.infoHash().v2().isValid() ? magnetUri
.infoHash().v2().toString() : tr("N/A"));
355 m_magnetURI
= magnetUri
;
359 void AddNewTorrentDialog::showEvent(QShowEvent
*event
)
361 QDialog::showEvent(event
);
362 if (!isTopLevel()) return;
368 void AddNewTorrentDialog::saveSavePathHistory() const
370 // Get current history
371 auto history
= settings()->loadValue
<QStringList
>(KEY_SAVEPATHHISTORY
);
372 QVector
<QDir
> historyDirs
;
373 for (const QString
&path
: asConst(history
))
374 historyDirs
<< QDir
{path
};
376 const QDir selectedSavePath
{m_ui
->savePath
->selectedPath()};
377 const int selectedSavePathIndex
= historyDirs
.indexOf(selectedSavePath
);
378 if (selectedSavePathIndex
> 0)
379 history
.removeAt(selectedSavePathIndex
);
380 if (selectedSavePathIndex
!= 0)
381 // Add last used save path to the front of history
382 history
.push_front(selectedSavePath
.absolutePath());
385 settings()->storeValue(KEY_SAVEPATHHISTORY
, QStringList
{history
.mid(0, savePathHistoryLength())});
388 // savePath is a folder, not an absolute file path
389 int AddNewTorrentDialog::indexOfSavePath(const QString
&savePath
)
391 QDir
saveDir(savePath
);
392 for (int i
= 0; i
< m_ui
->savePath
->count(); ++i
)
393 if (QDir(m_ui
->savePath
->item(i
)) == saveDir
)
398 void AddNewTorrentDialog::updateDiskSpaceLabel()
400 // Determine torrent size
401 qlonglong torrentSize
= 0;
407 const QVector
<BitTorrent::DownloadPriority
> priorities
= m_contentModel
->model()->getFilePriorities();
408 Q_ASSERT(priorities
.size() == m_torrentInfo
.filesCount());
409 for (int i
= 0; i
< priorities
.size(); ++i
)
410 if (priorities
[i
] > BitTorrent::DownloadPriority::Ignored
)
411 torrentSize
+= m_torrentInfo
.fileSize(i
);
415 torrentSize
= m_torrentInfo
.totalSize();
419 const QString sizeString
= tr("%1 (Free space on disk: %2)").arg(
420 ((torrentSize
> 0) ? Utils::Misc::friendlyUnit(torrentSize
) : tr("Not available", "This size is unavailable."))
421 , Utils::Misc::friendlyUnit(Utils::Fs::freeDiskSpaceOnPath(m_ui
->savePath
->selectedPath())));
422 m_ui
->labelSizeData
->setText(sizeString
);
425 void AddNewTorrentDialog::onSavePathChanged(const QString
&newPath
)
429 m_oldIndex
= m_ui
->savePath
->currentIndex();
430 updateDiskSpaceLabel();
433 void AddNewTorrentDialog::categoryChanged(int index
)
437 if (m_ui
->comboTTM
->currentIndex() == 1)
439 QString savePath
= BitTorrent::Session::instance()->categorySavePath(m_ui
->categoryComboBox
->currentText());
440 m_ui
->savePath
->setSelectedPath(Utils::Fs::toNativePath(savePath
));
441 updateDiskSpaceLabel();
445 void AddNewTorrentDialog::setSavePath(const QString
&newPath
)
447 int existingIndex
= indexOfSavePath(newPath
);
448 if (existingIndex
< 0)
450 // New path, prepend to combo box
451 m_ui
->savePath
->insertItem(0, newPath
);
454 m_ui
->savePath
->setCurrentIndex(existingIndex
);
455 onSavePathChanged(newPath
);
458 void AddNewTorrentDialog::saveTorrentFile()
460 Q_ASSERT(m_hasMetadata
);
462 const QString torrentFileExtension
{C_TORRENT_FILE_EXTENSION
};
463 const QString filter
{tr("Torrent file (*%1)").arg(torrentFileExtension
)};
465 QString path
= QFileDialog::getSaveFileName(
466 this, tr("Save as torrent file")
467 , QDir::home().absoluteFilePath(m_torrentInfo
.name() + torrentFileExtension
)
469 if (path
.isEmpty()) return;
471 if (!path
.endsWith(torrentFileExtension
, Qt::CaseInsensitive
))
472 path
+= torrentFileExtension
;
476 m_torrentInfo
.saveToFile(path
);
478 catch (const RuntimeError
&err
)
480 QMessageBox::critical(this, tr("I/O Error")
481 , tr("Couldn't export torrent metadata file '%1'. Reason: %2.").arg(path
, err
.message()));
485 void AddNewTorrentDialog::populateSavePathComboBox()
487 m_ui
->savePath
->clear();
489 // Load save path history
490 const auto savePathHistory
{settings()->loadValue
<QStringList
>(KEY_SAVEPATHHISTORY
)};
491 for (const QString
&savePath
: savePathHistory
)
492 m_ui
->savePath
->addItem(savePath
);
494 const bool rememberLastSavePath
{settings()->loadValue(KEY_REMEMBERLASTSAVEPATH
, false)};
495 const QString defSavePath
{BitTorrent::Session::instance()->defaultSavePath()};
497 if (!m_torrentParams
.savePath
.isEmpty())
498 setSavePath(m_torrentParams
.savePath
);
499 else if (!rememberLastSavePath
)
500 setSavePath(defSavePath
);
501 // else last used save path will be selected since it is the first in the list
504 void AddNewTorrentDialog::displayContentTreeMenu(const QPoint
&)
506 const QModelIndexList selectedRows
= m_ui
->contentTreeView
->selectionModel()->selectedRows(0);
508 const auto applyPriorities
= [this](const BitTorrent::DownloadPriority prio
)
510 const QModelIndexList selectedRows
= m_ui
->contentTreeView
->selectionModel()->selectedRows(0);
511 for (const QModelIndex
&index
: selectedRows
)
513 m_contentModel
->setData(index
.sibling(index
.row(), PRIORITY
)
514 , static_cast<int>(prio
));
517 const auto applyPrioritiesByOrder
= [this]()
519 // Equally distribute the selected items into groups and for each group assign
520 // a download priority that will apply to each item. The number of groups depends on how
521 // many "download priority" are available to be assigned
523 const QModelIndexList selectedRows
= m_ui
->contentTreeView
->selectionModel()->selectedRows(0);
525 const qsizetype priorityGroups
= 3;
526 const auto priorityGroupSize
= std::max
<qsizetype
>((selectedRows
.length() / priorityGroups
), 1);
528 for (qsizetype i
= 0; i
< selectedRows
.length(); ++i
)
530 auto priority
= BitTorrent::DownloadPriority::Ignored
;
531 switch (i
/ priorityGroupSize
)
534 priority
= BitTorrent::DownloadPriority::Maximum
;
537 priority
= BitTorrent::DownloadPriority::High
;
541 priority
= BitTorrent::DownloadPriority::Normal
;
545 const QModelIndex
&index
= selectedRows
[i
];
546 m_contentModel
->setData(index
.sibling(index
.row(), PRIORITY
)
547 , static_cast<int>(priority
));
551 QMenu
*menu
= new QMenu(this);
552 menu
->setAttribute(Qt::WA_DeleteOnClose
);
553 if (selectedRows
.size() == 1)
555 menu
->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename...")
556 , this, [this]() { m_ui
->contentTreeView
->renameSelectedFile(m_torrentInfo
); });
557 menu
->addSeparator();
559 QMenu
*priorityMenu
= menu
->addMenu(tr("Priority"));
560 priorityMenu
->addAction(tr("Do not download"), priorityMenu
, [applyPriorities
]()
562 applyPriorities(BitTorrent::DownloadPriority::Ignored
);
564 priorityMenu
->addAction(tr("Normal"), priorityMenu
, [applyPriorities
]()
566 applyPriorities(BitTorrent::DownloadPriority::Normal
);
568 priorityMenu
->addAction(tr("High"), priorityMenu
, [applyPriorities
]()
570 applyPriorities(BitTorrent::DownloadPriority::High
);
572 priorityMenu
->addAction(tr("Maximum"), priorityMenu
, [applyPriorities
]()
574 applyPriorities(BitTorrent::DownloadPriority::Maximum
);
576 priorityMenu
->addSeparator();
577 priorityMenu
->addAction(tr("By shown file order"), priorityMenu
, applyPrioritiesByOrder
);
581 menu
->addAction(tr("Do not download"), menu
, [applyPriorities
]()
583 applyPriorities(BitTorrent::DownloadPriority::Ignored
);
585 menu
->addAction(tr("Normal priority"), menu
, [applyPriorities
]()
587 applyPriorities(BitTorrent::DownloadPriority::Normal
);
589 menu
->addAction(tr("High priority"), menu
, [applyPriorities
]()
591 applyPriorities(BitTorrent::DownloadPriority::High
);
593 menu
->addAction(tr("Maximum priority"), menu
, [applyPriorities
]()
595 applyPriorities(BitTorrent::DownloadPriority::Maximum
);
597 menu
->addSeparator();
598 menu
->addAction(tr("Priority by shown file order"), menu
, applyPrioritiesByOrder
);
601 menu
->popup(QCursor::pos());
604 void AddNewTorrentDialog::accept()
606 // TODO: Check if destination actually exists
607 m_torrentParams
.skipChecking
= m_ui
->skipCheckingCheckBox
->isChecked();
610 m_torrentParams
.category
= m_ui
->categoryComboBox
->currentText();
611 if (m_ui
->defaultCategoryCheckbox
->isChecked())
612 settings()->storeValue(KEY_DEFAULTCATEGORY
, m_torrentParams
.category
);
614 settings()->storeValue(KEY_REMEMBERLASTSAVEPATH
, m_ui
->checkBoxRememberLastSavePath
->isChecked());
616 // Save file priorities
618 m_torrentParams
.filePriorities
= m_contentModel
->model()->getFilePriorities();
620 m_torrentParams
.addPaused
= !m_ui
->startTorrentCheckBox
->isChecked();
621 m_torrentParams
.contentLayout
= static_cast<BitTorrent::TorrentContentLayout
>(m_ui
->contentLayoutComboBox
->currentIndex());
623 m_torrentParams
.sequential
= m_ui
->sequentialCheckBox
->isChecked();
624 m_torrentParams
.firstLastPiecePriority
= m_ui
->firstLastCheckBox
->isChecked();
626 QString savePath
= m_ui
->savePath
->selectedPath();
627 if (m_ui
->comboTTM
->currentIndex() != 1)
628 { // 0 is Manual mode and 1 is Automatic mode. Handle all non 1 values as manual mode.
629 m_torrentParams
.useAutoTMM
= false;
630 m_torrentParams
.savePath
= savePath
;
631 saveSavePathHistory();
635 m_torrentParams
.useAutoTMM
= true;
638 setEnabled(!m_ui
->checkBoxNeverShow
->isChecked());
642 BitTorrent::Session::instance()->addTorrent(m_magnetURI
, m_torrentParams
);
644 BitTorrent::Session::instance()->addTorrent(m_torrentInfo
, m_torrentParams
);
646 m_torrentGuard
->markAsAddedToSession();
650 void AddNewTorrentDialog::reject()
654 setMetadataProgressIndicator(false);
655 BitTorrent::Session::instance()->cancelDownloadMetadata(m_magnetURI
.infoHash().toTorrentID());
661 void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo
&metadata
)
663 if (metadata
.infoHash() != m_magnetURI
.infoHash()) return;
665 disconnect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataDownloaded
, this, &AddNewTorrentDialog::updateMetadata
);
667 if (!metadata
.isValid())
669 RaisedMessageBox::critical(this, tr("I/O Error"), ("Invalid metadata."));
670 setMetadataProgressIndicator(false, tr("Invalid metadata"));
675 m_torrentInfo
= metadata
;
676 m_hasMetadata
= true;
677 setMetadataProgressIndicator(true, tr("Parsing metadata..."));
681 setMetadataProgressIndicator(false, tr("Metadata retrieval complete"));
683 m_ui
->buttonSave
->setVisible(true);
684 if (m_torrentInfo
.infoHash().v2().isValid())
686 m_ui
->buttonSave
->setEnabled(false);
687 m_ui
->buttonSave
->setToolTip(tr("Cannot create v2 torrent until its data is fully downloaded."));
691 void AddNewTorrentDialog::setMetadataProgressIndicator(bool visibleIndicator
, const QString
&labelText
)
693 // Always show info label when waiting for metadata
694 m_ui
->lblMetaLoading
->setVisible(true);
695 m_ui
->lblMetaLoading
->setText(labelText
);
696 m_ui
->progMetaLoading
->setVisible(visibleIndicator
);
699 void AddNewTorrentDialog::setupTreeview()
703 m_ui
->labelCommentData
->setText(tr("Not Available", "This comment is unavailable"));
704 m_ui
->labelDateData
->setText(tr("Not Available", "This date is unavailable"));
709 setWindowTitle(m_torrentInfo
.name());
711 // Set torrent information
712 m_ui
->labelCommentData
->setText(Utils::Misc::parseHtmlLinks(m_torrentInfo
.comment().toHtmlEscaped()));
713 m_ui
->labelDateData
->setText(!m_torrentInfo
.creationDate().isNull() ? QLocale().toString(m_torrentInfo
.creationDate(), QLocale::ShortFormat
) : tr("Not available"));
715 // Prepare content tree
716 m_contentModel
= new TorrentContentFilterModel(this);
717 connect(m_contentModel
->model(), &TorrentContentModel::filteredFilesChanged
, this, &AddNewTorrentDialog::updateDiskSpaceLabel
);
718 m_ui
->contentTreeView
->setModel(m_contentModel
);
719 m_contentDelegate
= new PropListDelegate(nullptr);
720 m_ui
->contentTreeView
->setItemDelegate(m_contentDelegate
);
721 connect(m_ui
->contentTreeView
, &QAbstractItemView::clicked
, m_ui
->contentTreeView
722 , qOverload
<const QModelIndex
&>(&QAbstractItemView::edit
));
723 connect(m_ui
->contentTreeView
, &QWidget::customContextMenuRequested
, this, &AddNewTorrentDialog::displayContentTreeMenu
);
725 // List files in torrent
726 m_contentModel
->model()->setupModelData(m_torrentInfo
);
727 if (!m_headerState
.isEmpty())
728 m_ui
->contentTreeView
->header()->restoreState(m_headerState
);
730 // Hide useless columns after loading the header state
731 m_ui
->contentTreeView
->hideColumn(PROGRESS
);
732 m_ui
->contentTreeView
->hideColumn(REMAINING
);
733 m_ui
->contentTreeView
->hideColumn(AVAILABILITY
);
735 // Expand single-item folders recursively
736 QModelIndex currentIndex
;
737 while (m_contentModel
->rowCount(currentIndex
) == 1)
739 currentIndex
= m_contentModel
->index(0, 0, currentIndex
);
740 m_ui
->contentTreeView
->setExpanded(currentIndex
, true);
744 updateDiskSpaceLabel();
747 void AddNewTorrentDialog::handleDownloadFinished(const Net::DownloadResult
&result
)
750 switch (result
.status
)
752 case Net::DownloadStatus::Success
:
753 m_torrentInfo
= BitTorrent::TorrentInfo::load(result
.data
, &error
);
754 if (!m_torrentInfo
.isValid())
756 RaisedMessageBox::critical(this, tr("Invalid torrent"), tr("Failed to load from URL: %1.\nError: %2")
757 .arg(result
.url
, error
));
761 m_torrentGuard
= std::make_unique
<TorrentFileGuard
>();
763 if (loadTorrentImpl())
768 case Net::DownloadStatus::RedirectedToMagnet
:
769 if (loadMagnet(BitTorrent::MagnetUri(result
.magnet
)))
775 RaisedMessageBox::critical(this, tr("Download Error"),
776 tr("Cannot download '%1': %2").arg(result
.url
, result
.errorString
));
781 void AddNewTorrentDialog::TMMChanged(int index
)
784 { // 0 is Manual mode and 1 is Automatic mode. Handle all non 1 values as manual mode.
785 populateSavePathComboBox();
786 m_ui
->groupBoxSavePath
->setEnabled(true);
787 m_ui
->savePath
->blockSignals(false);
788 m_ui
->savePath
->setCurrentIndex(m_oldIndex
< m_ui
->savePath
->count() ? m_oldIndex
: m_ui
->savePath
->count() - 1);
792 m_ui
->groupBoxSavePath
->setEnabled(false);
793 m_ui
->savePath
->blockSignals(true);
794 m_ui
->savePath
->clear();
795 QString savePath
= BitTorrent::Session::instance()->categorySavePath(m_ui
->categoryComboBox
->currentText());
796 m_ui
->savePath
->addItem(savePath
);
797 updateDiskSpaceLabel();
801 void AddNewTorrentDialog::doNotDeleteTorrentClicked(bool checked
)
803 m_torrentGuard
->setAutoRemove(!checked
);