Correctly handle share limits in torrent options dialog
[qBittorrent.git] / src / gui / torrentoptionsdialog.cpp
blob525ca1c4432942dc06dbd894521bcedf35ece118
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2020 thalieht
5 * Copyright (C) 2011 Christian Kandeler
6 * Copyright (C) 2011 Christophe Dumez <chris@qbittorrent.org>
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 * In addition, as a special exception, the copyright holders give permission to
23 * link this program with the OpenSSL project's "OpenSSL" library (or with
24 * modified versions of it that use the same license as the "OpenSSL" library),
25 * and distribute the linked executables. You must obey the GNU General Public
26 * License in all respects for all of the code used other than "OpenSSL". If you
27 * modify file(s), you may extend this exception to your version of the file(s),
28 * but you are not obligated to do so. If you do not wish to do so, delete this
29 * exception statement from your version.
32 #include "torrentoptionsdialog.h"
34 #include <algorithm>
36 #include <QLineEdit>
37 #include <QMessageBox>
38 #include <QString>
40 #include "base/bittorrent/infohash.h"
41 #include "base/bittorrent/session.h"
42 #include "base/bittorrent/torrent.h"
43 #include "base/global.h"
44 #include "base/unicodestrings.h"
45 #include "base/utils/fs.h"
46 #include "ui_torrentoptionsdialog.h"
47 #include "utils.h"
49 #define SETTINGS_KEY(name) u"TorrentOptionsDialog/" name
51 namespace
53 void updateSliderValue(QSlider *slider, const int value)
55 if (value > slider->maximum())
56 slider->setMaximum(value);
57 slider->setValue(value);
61 TorrentOptionsDialog::TorrentOptionsDialog(QWidget *parent, const QVector<BitTorrent::Torrent *> &torrents)
62 : QDialog {parent}
63 , m_ui {new Ui::TorrentOptionsDialog}
64 , m_storeDialogSize {SETTINGS_KEY(u"Size"_s)}
65 , m_currentCategoriesString {u"--%1--"_s.arg(tr("Currently used categories"))}
67 Q_ASSERT(!torrents.empty());
69 m_ui->setupUi(this);
71 connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
72 connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
74 m_ui->savePath->setMode(FileSystemPathEdit::Mode::DirectorySave);
75 m_ui->savePath->setDialogCaption(tr("Choose save path"));
76 m_ui->downloadPath->setMode(FileSystemPathEdit::Mode::DirectorySave);
77 m_ui->downloadPath->setDialogCaption(tr("Choose save path"));
79 const auto *session = BitTorrent::Session::instance();
80 bool allSameUpLimit = true;
81 bool allSameDownLimit = true;
82 bool allSameRatio = true;
83 bool allSameSeedingTime = true;
84 bool allSameInactiveSeedingTime = true;
85 bool allTorrentsArePrivate = true;
86 bool allSameDHT = true;
87 bool allSamePEX = true;
88 bool allSameLSD = true;
89 bool allSameSequential = true;
90 bool allSameFirstLastPieces = true;
91 bool allSameAutoTMM = true;
92 bool allSameSavePath = true;
93 bool allSameDownloadPath = true;
95 const bool isFirstTorrentAutoTMMEnabled = torrents[0]->isAutoTMMEnabled();
96 const Path firstTorrentSavePath = torrents[0]->savePath();
97 const Path firstTorrentDownloadPath = torrents[0]->downloadPath();
98 const QString firstTorrentCategory = torrents[0]->category();
100 const int firstTorrentUpLimit = std::max(0, torrents[0]->uploadLimit());
101 const int firstTorrentDownLimit = std::max(0, torrents[0]->downloadLimit());
103 const qreal firstTorrentRatio = torrents[0]->ratioLimit();
104 const int firstTorrentSeedingTime = torrents[0]->seedingTimeLimit();
105 const int firstTorrentInactiveSeedingTime = torrents[0]->inactiveSeedingTimeLimit();
107 const bool isFirstTorrentDHTDisabled = torrents[0]->isDHTDisabled();
108 const bool isFirstTorrentPEXDisabled = torrents[0]->isPEXDisabled();
109 const bool isFirstTorrentLSDDisabled = torrents[0]->isLSDDisabled();
110 const bool isFirstTorrentSequentialEnabled = torrents[0]->isSequentialDownload();
111 const bool isFirstTorrentFirstLastPiecesEnabled = torrents[0]->hasFirstLastPiecePriority();
113 m_torrentIDs.reserve(torrents.size());
114 for (const BitTorrent::Torrent *torrent : torrents)
116 m_torrentIDs << torrent->id();
118 if (allSameAutoTMM)
120 if (torrent->isAutoTMMEnabled() != isFirstTorrentAutoTMMEnabled)
121 allSameAutoTMM = false;
123 if (allSameSavePath)
125 if (torrent->savePath() != firstTorrentSavePath)
126 allSameSavePath = false;
128 if (allSameDownloadPath)
130 if (torrent->downloadPath() != firstTorrentDownloadPath)
131 allSameDownloadPath = false;
133 if (m_allSameCategory)
135 if (torrent->category() != firstTorrentCategory)
136 m_allSameCategory = false;
138 if (allSameUpLimit)
140 if (std::max(0, torrent->uploadLimit()) != firstTorrentUpLimit)
141 allSameUpLimit = false;
143 if (allSameDownLimit)
145 if (std::max(0, torrent->downloadLimit()) != firstTorrentDownLimit)
146 allSameDownLimit = false;
148 if (allSameRatio)
150 if (torrent->ratioLimit() != firstTorrentRatio)
151 allSameRatio = false;
153 if (allSameSeedingTime)
155 if (torrent->seedingTimeLimit() != firstTorrentSeedingTime)
156 allSameSeedingTime = false;
158 if (allSameInactiveSeedingTime)
160 if (torrent->inactiveSeedingTimeLimit() != firstTorrentInactiveSeedingTime)
161 allSameInactiveSeedingTime = false;
163 if (allTorrentsArePrivate)
165 if (!torrent->isPrivate())
166 allTorrentsArePrivate = false;
168 if (allSameDHT)
170 if (torrent->isDHTDisabled() != isFirstTorrentDHTDisabled)
171 allSameDHT = false;
173 if (allSamePEX)
175 if (torrent->isPEXDisabled() != isFirstTorrentPEXDisabled)
176 allSamePEX = false;
178 if (allSameLSD)
180 if (torrent->isLSDDisabled() != isFirstTorrentLSDDisabled)
181 allSameLSD = false;
183 if (allSameSequential)
185 if (torrent->isSequentialDownload() != isFirstTorrentSequentialEnabled)
186 allSameSequential = false;
188 if (allSameFirstLastPieces)
190 if (torrent->hasFirstLastPiecePriority() != isFirstTorrentFirstLastPiecesEnabled)
191 allSameFirstLastPieces = false;
195 if (allSameAutoTMM)
196 m_ui->checkAutoTMM->setChecked(isFirstTorrentAutoTMMEnabled);
197 else
198 m_ui->checkAutoTMM->setCheckState(Qt::PartiallyChecked);
200 if (allSameSavePath)
201 m_ui->savePath->setSelectedPath(firstTorrentSavePath);
203 if (allSameDownloadPath)
205 m_ui->downloadPath->setSelectedPath(firstTorrentDownloadPath);
206 m_ui->checkUseDownloadPath->setChecked(!firstTorrentDownloadPath.isEmpty());
208 else
210 m_ui->checkUseDownloadPath->setCheckState(Qt::PartiallyChecked);
213 if (!m_allSameCategory)
215 m_ui->comboCategory->addItem(m_currentCategoriesString);
216 m_ui->comboCategory->clearEditText();
217 m_ui->comboCategory->lineEdit()->setPlaceholderText(m_currentCategoriesString);
219 else if (!firstTorrentCategory.isEmpty())
221 m_ui->comboCategory->setCurrentText(firstTorrentCategory);
222 m_ui->comboCategory->addItem(firstTorrentCategory);
224 m_ui->comboCategory->addItem(QString());
226 m_categories = session->categories();
227 std::sort(m_categories.begin(), m_categories.end(), Utils::Compare::NaturalLessThan<Qt::CaseInsensitive>());
228 for (const QString &category : asConst(m_categories))
230 if (m_allSameCategory && (category == firstTorrentCategory))
231 continue;
233 m_ui->comboCategory->addItem(category);
236 const bool isAltLimitEnabled = session->isAltGlobalSpeedLimitEnabled();
237 const int globalUploadLimit = isAltLimitEnabled
238 ? (session->altGlobalUploadSpeedLimit() / 1024)
239 : (session->globalUploadSpeedLimit() / 1024);
240 const int globalDownloadLimit = isAltLimitEnabled
241 ? (session->altGlobalDownloadSpeedLimit() / 1024)
242 : (session->globalDownloadSpeedLimit() / 1024);
244 const int uploadVal = std::max(0, (firstTorrentUpLimit / 1024));
245 const int downloadVal = std::max(0, (firstTorrentDownLimit / 1024));
246 int maxUpload = (globalUploadLimit <= 0) ? 10000 : globalUploadLimit;
247 int maxDownload = (globalDownloadLimit <= 0) ? 10000 : globalDownloadLimit;
249 // This can happen for example if global rate limit is lower than torrent rate limit.
250 if (uploadVal > maxUpload)
251 maxUpload = uploadVal;
252 if (downloadVal > maxDownload)
253 maxDownload = downloadVal;
255 m_ui->sliderUploadLimit->setMaximum(maxUpload);
256 m_ui->sliderUploadLimit->setValue(allSameUpLimit ? uploadVal : (maxUpload / 2));
257 if (allSameUpLimit)
259 m_ui->spinUploadLimit->setValue(uploadVal);
261 else
263 m_ui->spinUploadLimit->setSpecialValueText(C_INEQUALITY);
264 m_ui->spinUploadLimit->setMinimum(-1);
265 m_ui->spinUploadLimit->setValue(-1);
266 connect(m_ui->spinUploadLimit, qOverload<int>(&QSpinBox::valueChanged)
267 , this, &TorrentOptionsDialog::handleUpSpeedLimitChanged);
270 m_ui->sliderDownloadLimit->setMaximum(maxDownload);
271 m_ui->sliderDownloadLimit->setValue(allSameDownLimit ? downloadVal : (maxDownload / 2));
272 if (allSameDownLimit)
274 m_ui->spinDownloadLimit->setValue(downloadVal);
276 else
278 m_ui->spinDownloadLimit->setSpecialValueText(C_INEQUALITY);
279 m_ui->spinDownloadLimit->setMinimum(-1);
280 m_ui->spinDownloadLimit->setValue(-1);
281 connect(m_ui->spinDownloadLimit, qOverload<int>(&QSpinBox::valueChanged)
282 , this, &TorrentOptionsDialog::handleDownSpeedLimitChanged);
285 m_ui->torrentShareLimitsWidget->setDefaultLimits(session->globalMaxRatio(), session->globalMaxSeedingMinutes(), session->globalMaxInactiveSeedingMinutes());
286 if (allSameRatio)
287 m_ui->torrentShareLimitsWidget->setRatioLimit(firstTorrentRatio);
288 if (allSameSeedingTime)
289 m_ui->torrentShareLimitsWidget->setSeedingTimeLimit(firstTorrentSeedingTime);
290 if (allSameInactiveSeedingTime)
291 m_ui->torrentShareLimitsWidget->setInactiveSeedingTimeLimit(firstTorrentInactiveSeedingTime);
293 if (!allTorrentsArePrivate)
295 if (allSameDHT)
296 m_ui->checkDisableDHT->setChecked(isFirstTorrentDHTDisabled);
297 else
298 m_ui->checkDisableDHT->setCheckState(Qt::PartiallyChecked);
300 if (allSamePEX)
301 m_ui->checkDisablePEX->setChecked(isFirstTorrentPEXDisabled);
302 else
303 m_ui->checkDisablePEX->setCheckState(Qt::PartiallyChecked);
305 if (allSameLSD)
306 m_ui->checkDisableLSD->setChecked(isFirstTorrentLSDDisabled);
307 else
308 m_ui->checkDisableLSD->setCheckState(Qt::PartiallyChecked);
310 else
312 m_ui->checkDisableDHT->setChecked(true);
313 m_ui->checkDisableDHT->setEnabled(false);
314 m_ui->checkDisablePEX->setChecked(true);
315 m_ui->checkDisablePEX->setEnabled(false);
316 m_ui->checkDisableLSD->setChecked(true);
317 m_ui->checkDisableLSD->setEnabled(false);
320 const QString privateTorrentsTooltip = tr("Not applicable to private torrents");
321 m_ui->checkDisableDHT->setToolTip(privateTorrentsTooltip);
322 m_ui->checkDisablePEX->setToolTip(privateTorrentsTooltip);
323 m_ui->checkDisableLSD->setToolTip(privateTorrentsTooltip);
325 if (allSameSequential)
326 m_ui->checkSequential->setChecked(isFirstTorrentSequentialEnabled);
327 else
328 m_ui->checkSequential->setCheckState(Qt::PartiallyChecked);
330 if (allSameFirstLastPieces)
331 m_ui->checkFirstLastPieces->setChecked(isFirstTorrentFirstLastPiecesEnabled);
332 else
333 m_ui->checkFirstLastPieces->setCheckState(Qt::PartiallyChecked);
335 m_initialValues =
337 .savePath = m_ui->savePath->selectedPath(),
338 .downloadPath = m_ui->downloadPath->selectedPath(),
339 .category = m_ui->comboCategory->currentText(),
340 .ratio = m_ui->torrentShareLimitsWidget->ratioLimit(),
341 .seedingTime = m_ui->torrentShareLimitsWidget->seedingTimeLimit(),
342 .inactiveSeedingTime = m_ui->torrentShareLimitsWidget->inactiveSeedingTimeLimit(),
343 .upSpeedLimit = m_ui->spinUploadLimit->value(),
344 .downSpeedLimit = m_ui->spinDownloadLimit->value(),
345 .autoTMM = m_ui->checkAutoTMM->checkState(),
346 .useDownloadPath = m_ui->checkUseDownloadPath->checkState(),
347 .disableDHT = m_ui->checkDisableDHT->checkState(),
348 .disablePEX = m_ui->checkDisablePEX->checkState(),
349 .disableLSD = m_ui->checkDisableLSD->checkState(),
350 .sequential = m_ui->checkSequential->checkState(),
351 .firstLastPieces = m_ui->checkFirstLastPieces->checkState()
354 // Needs to be called after the initial values struct is initialized
355 handleTMMChanged();
356 handleUseDownloadPathChanged();
358 connect(m_ui->checkAutoTMM, &QCheckBox::clicked, this, &TorrentOptionsDialog::handleTMMChanged);
359 connect(m_ui->checkUseDownloadPath, &QCheckBox::clicked, this, &TorrentOptionsDialog::handleUseDownloadPathChanged);
360 connect(m_ui->comboCategory, &QComboBox::activated, this, &TorrentOptionsDialog::handleCategoryChanged);
362 // Sync up/down speed limit sliders with their corresponding spinboxes
363 connect(m_ui->sliderUploadLimit, &QSlider::valueChanged, m_ui->spinUploadLimit, &QSpinBox::setValue);
364 connect(m_ui->sliderDownloadLimit, &QSlider::valueChanged, m_ui->spinDownloadLimit, &QSpinBox::setValue);
365 connect(m_ui->spinUploadLimit, qOverload<int>(&QSpinBox::valueChanged)
366 , this, [this](const int value) { updateSliderValue(m_ui->sliderUploadLimit, value); });
367 connect(m_ui->spinDownloadLimit, qOverload<int>(&QSpinBox::valueChanged)
368 , this, [this](const int value) { updateSliderValue(m_ui->sliderDownloadLimit, value); });
370 if (const QSize dialogSize = m_storeDialogSize; dialogSize.isValid())
371 resize(dialogSize);
374 TorrentOptionsDialog::~TorrentOptionsDialog()
376 m_storeDialogSize = size();
377 delete m_ui;
380 void TorrentOptionsDialog::accept()
382 auto *session = BitTorrent::Session::instance();
383 for (const BitTorrent::TorrentID &id : asConst(m_torrentIDs))
385 BitTorrent::Torrent *torrent = session->getTorrent(id);
386 if (!torrent) continue;
388 if (m_initialValues.autoTMM != m_ui->checkAutoTMM->checkState())
389 torrent->setAutoTMMEnabled(m_ui->checkAutoTMM->isChecked());
391 if (m_ui->checkAutoTMM->checkState() == Qt::Unchecked)
393 const Path savePath = m_ui->savePath->selectedPath();
394 if (m_initialValues.savePath != savePath)
395 torrent->setSavePath(savePath);
397 const Qt::CheckState useDownloadPathState = m_ui->checkUseDownloadPath->checkState();
398 if (useDownloadPathState == Qt::Checked)
400 const Path downloadPath = m_ui->downloadPath->selectedPath();
401 if (m_initialValues.downloadPath != downloadPath)
402 torrent->setDownloadPath(downloadPath);
404 else if (useDownloadPathState == Qt::Unchecked)
406 torrent->setDownloadPath({});
410 const QString category = m_ui->comboCategory->currentText();
411 // index 0 is always the current category
412 if ((m_initialValues.category != category) || (m_ui->comboCategory->currentIndex() != 0))
414 if (!m_categories.contains(category))
415 session->addCategory(category);
417 torrent->setCategory(category);
420 if (m_initialValues.upSpeedLimit != m_ui->spinUploadLimit->value())
421 torrent->setUploadLimit(m_ui->spinUploadLimit->value() * 1024);
422 if (m_initialValues.downSpeedLimit != m_ui->spinDownloadLimit->value())
423 torrent->setDownloadLimit(m_ui->spinDownloadLimit->value() * 1024);
425 if (const std::optional<qreal> ratioLimit = m_ui->torrentShareLimitsWidget->ratioLimit();
426 m_initialValues.ratio != ratioLimit)
428 torrent->setRatioLimit(ratioLimit.value());
431 if (const std::optional<int> seedingTimeLimit = m_ui->torrentShareLimitsWidget->seedingTimeLimit();
432 m_initialValues.seedingTime != seedingTimeLimit)
434 torrent->setSeedingTimeLimit(seedingTimeLimit.value());
437 if (const std::optional<int> inactiveSeedingTimeLimit = m_ui->torrentShareLimitsWidget->inactiveSeedingTimeLimit();
438 m_initialValues.inactiveSeedingTime != inactiveSeedingTimeLimit)
440 torrent->setInactiveSeedingTimeLimit(inactiveSeedingTimeLimit.value());
443 if (!torrent->isPrivate())
445 if (m_initialValues.disableDHT != m_ui->checkDisableDHT->checkState())
446 torrent->setDHTDisabled(m_ui->checkDisableDHT->isChecked());
447 if (m_initialValues.disablePEX != m_ui->checkDisablePEX->checkState())
448 torrent->setPEXDisabled(m_ui->checkDisablePEX->isChecked());
449 if (m_initialValues.disableLSD != m_ui->checkDisableLSD->checkState())
450 torrent->setLSDDisabled(m_ui->checkDisableLSD->isChecked());
453 if (m_initialValues.sequential != m_ui->checkSequential->checkState())
454 torrent->setSequentialDownload(m_ui->checkSequential->isChecked());
455 if (m_initialValues.firstLastPieces != m_ui->checkFirstLastPieces->checkState())
456 torrent->setFirstLastPiecePriority(m_ui->checkFirstLastPieces->isChecked());
459 QDialog::accept();
462 void TorrentOptionsDialog::handleCategoryChanged([[maybe_unused]] const int index)
464 if (m_ui->checkAutoTMM->checkState() == Qt::Checked)
466 if (!m_allSameCategory && (m_ui->comboCategory->currentIndex() == 0))
468 m_ui->savePath->setSelectedPath({});
470 else
472 const Path savePath = BitTorrent::Session::instance()->categorySavePath(m_ui->comboCategory->currentText());
473 m_ui->savePath->setSelectedPath(savePath);
474 const Path downloadPath = BitTorrent::Session::instance()->categoryDownloadPath(m_ui->comboCategory->currentText());
475 m_ui->downloadPath->setSelectedPath(downloadPath);
476 m_ui->checkUseDownloadPath->setChecked(!downloadPath.isEmpty());
480 if (!m_allSameCategory && (m_ui->comboCategory->currentIndex() == 0))
482 m_ui->comboCategory->clearEditText();
483 m_ui->comboCategory->lineEdit()->setPlaceholderText(m_currentCategoriesString);
485 else
487 m_ui->comboCategory->lineEdit()->setPlaceholderText(QString());
491 void TorrentOptionsDialog::handleTMMChanged()
493 if (m_ui->checkAutoTMM->checkState() == Qt::Unchecked)
495 m_ui->groupBoxSavePath->setEnabled(true);
496 m_ui->savePath->setSelectedPath(m_initialValues.savePath);
497 m_ui->downloadPath->setSelectedPath(m_initialValues.downloadPath);
498 m_ui->checkUseDownloadPath->setCheckState(m_initialValues.useDownloadPath);
500 else
502 m_ui->groupBoxSavePath->setEnabled(false);
503 if (m_ui->checkAutoTMM->checkState() == Qt::Checked)
505 if (!m_allSameCategory && (m_ui->comboCategory->currentIndex() == 0))
507 m_ui->savePath->setSelectedPath({});
508 m_ui->downloadPath->setSelectedPath({});
509 m_ui->checkUseDownloadPath->setCheckState(Qt::PartiallyChecked);
511 else
513 const Path savePath = BitTorrent::Session::instance()->categorySavePath(m_ui->comboCategory->currentText());
514 m_ui->savePath->setSelectedPath(savePath);
515 const Path downloadPath = BitTorrent::Session::instance()->categoryDownloadPath(m_ui->comboCategory->currentText());
516 m_ui->downloadPath->setSelectedPath(downloadPath);
517 m_ui->checkUseDownloadPath->setChecked(!downloadPath.isEmpty());
520 else // partially checked
522 m_ui->savePath->setSelectedPath({});
523 m_ui->downloadPath->setSelectedPath({});
524 m_ui->checkUseDownloadPath->setCheckState(Qt::PartiallyChecked);
529 void TorrentOptionsDialog::handleUseDownloadPathChanged()
531 const bool isChecked = m_ui->checkUseDownloadPath->checkState() == Qt::Checked;
532 m_ui->downloadPath->setEnabled(isChecked);
533 if (isChecked && m_ui->downloadPath->selectedPath().isEmpty())
534 m_ui->downloadPath->setSelectedPath(BitTorrent::Session::instance()->downloadPath());
537 void TorrentOptionsDialog::handleUpSpeedLimitChanged()
539 m_ui->spinUploadLimit->setMinimum(0);
540 m_ui->spinUploadLimit->setSpecialValueText(C_INFINITY);
541 disconnect(m_ui->spinUploadLimit, qOverload<int>(&QSpinBox::valueChanged)
542 , this, &TorrentOptionsDialog::handleUpSpeedLimitChanged);
545 void TorrentOptionsDialog::handleDownSpeedLimitChanged()
547 m_ui->spinDownloadLimit->setMinimum(0);
548 m_ui->spinDownloadLimit->setSpecialValueText(C_INFINITY);
549 disconnect(m_ui->spinDownloadLimit, qOverload<int>(&QSpinBox::valueChanged)
550 , this, &TorrentOptionsDialog::handleDownSpeedLimitChanged);