diff --git a/src/app/application.cpp b/src/app/application.cpp index 1dde0513d588..28e1f179af1f 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -743,7 +743,9 @@ void Application::torrentFinished(const BitTorrent::Torrent *torrent) const Preferences *pref = Preferences::instance(); // AutoRun program - if (pref->isAutoRunOnTorrentFinishedEnabled()) + if (!torrent->runOnFinishedProgram().isEmpty()) + runExternalProgram(torrent->runOnFinishedProgram().trimmed(), torrent); + else if (pref->isAutoRunOnTorrentFinishedEnabled()) runExternalProgram(pref->getAutoRunOnTorrentFinishedProgram().trimmed(), torrent); // Mail notification diff --git a/src/base/bittorrent/bencoderesumedatastorage.cpp b/src/base/bittorrent/bencoderesumedatastorage.cpp index ee87b56015aa..6812fa625a5a 100644 --- a/src/base/bittorrent/bencoderesumedatastorage.cpp +++ b/src/base/bittorrent/bencoderesumedatastorage.cpp @@ -234,6 +234,7 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre return nonstd::make_unexpected(tr("Cannot parse resume data: invalid format")); LoadTorrentParams torrentParams; + torrentParams.runOnFinishedProgram = fromLTString(resumeDataRoot.dict_find_string_value("qBt-runOnFinishedProgram")); torrentParams.category = fromLTString(resumeDataRoot.dict_find_string_value("qBt-category")); torrentParams.name = fromLTString(resumeDataRoot.dict_find_string_value("qBt-name")); torrentParams.comment = fromLTString(resumeDataRoot.dict_find_string_value("qBt-comment")); @@ -435,6 +436,7 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co data["qBt-inactiveSeedingTimeLimit"] = resumeData.inactiveSeedingTimeLimit; data["qBt-shareLimitAction"] = Utils::String::fromEnum(resumeData.shareLimitAction).toStdString(); + data["qBt-runOnFinishedProgram"] = resumeData.runOnFinishedProgram.toStdString(); data["qBt-category"] = resumeData.category.toStdString(); data["qBt-tags"] = setToEntryList(resumeData.tags); data["qBt-name"] = resumeData.name.toStdString(); diff --git a/src/base/bittorrent/dbresumedatastorage.cpp b/src/base/bittorrent/dbresumedatastorage.cpp index 76479ac15ed0..574f674580ab 100644 --- a/src/base/bittorrent/dbresumedatastorage.cpp +++ b/src/base/bittorrent/dbresumedatastorage.cpp @@ -129,6 +129,7 @@ namespace const Column DB_COLUMN_TORRENT_ID = makeColumn(u"torrent_id"_s); const Column DB_COLUMN_QUEUE_POSITION = makeColumn(u"queue_position"_s); const Column DB_COLUMN_NAME = makeColumn(u"name"_s); + const Column DB_COLUMN_RUN_ON_FINISHED_PROGRAM = makeColumn(u"run_on_finished_program"_s); const Column DB_COLUMN_CATEGORY = makeColumn(u"category"_s); const Column DB_COLUMN_TAGS = makeColumn(u"tags"_s); const Column DB_COLUMN_COMMENT = makeColumn(u"comment"_s); @@ -460,6 +461,7 @@ void BitTorrent::DBResumeDataStorage::createDB() const makeColumnDefinition(DB_COLUMN_TORRENT_ID, u"BLOB NOT NULL UNIQUE"_s), makeColumnDefinition(DB_COLUMN_QUEUE_POSITION, u"INTEGER NOT NULL DEFAULT -1"_s), makeColumnDefinition(DB_COLUMN_NAME, u"TEXT"_s), + makeColumnDefinition(DB_COLUMN_RUN_ON_FINISHED_PROGRAM, u"TEXT"_s), makeColumnDefinition(DB_COLUMN_CATEGORY, u"TEXT"_s), makeColumnDefinition(DB_COLUMN_TAGS, u"TEXT"_s), makeColumnDefinition(DB_COLUMN_COMMENT, u"TEXT"_s), @@ -623,6 +625,7 @@ LoadResumeDataResult DBResumeDataStorage::parseQueryResultRow(const QSqlQuery &q { LoadTorrentParams resumeData; resumeData.name = query.value(DB_COLUMN_NAME.name).toString(); + resumeData.runOnFinishedProgram = query.value(DB_COLUMN_RUN_ON_FINISHED_PROGRAM.name).toString(); resumeData.category = query.value(DB_COLUMN_CATEGORY.name).toString(); resumeData.comment = query.value(DB_COLUMN_COMMENT.name).toString(); const QString tagsData = query.value(DB_COLUMN_TAGS.name).toString(); @@ -838,6 +841,7 @@ StoreJob::StoreJob(const TorrentID &torrentID, LoadTorrentParams resumeData) QList columns { DB_COLUMN_TORRENT_ID, DB_COLUMN_NAME, + DB_COLUMN_RUN_ON_FINISHED_PROGRAM, DB_COLUMN_CATEGORY, DB_COLUMN_TAGS, DB_COLUMN_COMMENT, @@ -903,6 +907,7 @@ StoreJob::StoreJob(const TorrentID &torrentID, LoadTorrentParams resumeData) query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, m_torrentID.toString()); query.bindValue(DB_COLUMN_NAME.placeholder, m_resumeData.name); + query.bindValue(DB_COLUMN_RUN_ON_FINISHED_PROGRAM.placeholder, m_resumeData.runOnFinishedProgram); query.bindValue(DB_COLUMN_CATEGORY.placeholder, m_resumeData.category); query.bindValue(DB_COLUMN_TAGS.placeholder, (m_resumeData.tags.isEmpty() ? QString() : Utils::String::joinIntoString(m_resumeData.tags, u","_s))); diff --git a/src/base/bittorrent/loadtorrentparams.h b/src/base/bittorrent/loadtorrentparams.h index 24ebc10d1e19..0f60c54d4267 100644 --- a/src/base/bittorrent/loadtorrentparams.h +++ b/src/base/bittorrent/loadtorrentparams.h @@ -61,6 +61,7 @@ namespace BitTorrent bool addToQueueTop = false; // only for new torrents + QString runOnFinishedProgram; qreal ratioLimit = Torrent::USE_GLOBAL_RATIO; int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME; int inactiveSeedingTimeLimit = Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME; diff --git a/src/base/bittorrent/session.h b/src/base/bittorrent/session.h index 7d024d5db195..83038eea07f0 100644 --- a/src/base/bittorrent/session.h +++ b/src/base/bittorrent/session.h @@ -510,6 +510,7 @@ namespace BitTorrent void torrentMetadataReceived(Torrent *torrent); void torrentStopped(Torrent *torrent); void torrentStarted(Torrent *torrent); + void torrentRunOnFinishedProgramChanged(Torrent *torrent); void torrentSavePathChanged(Torrent *torrent); void torrentSavingModeChanged(Torrent *torrent); void torrentsLoaded(const QList &torrents); diff --git a/src/base/bittorrent/sessionimpl.cpp b/src/base/bittorrent/sessionimpl.cpp index 72f6e6a2a9a0..ab3cf5909080 100644 --- a/src/base/bittorrent/sessionimpl.cpp +++ b/src/base/bittorrent/sessionimpl.cpp @@ -5245,6 +5245,10 @@ void SessionImpl::handleTorrentSavePathChanged(TorrentImpl *const torrent) emit torrentSavePathChanged(torrent); } +void SessionImpl::handleTorrentRunOnFinishedProgramChanged(TorrentImpl *const torrent) { + emit torrentRunOnFinishedProgramChanged(torrent); +} + void SessionImpl::handleTorrentCategoryChanged(TorrentImpl *const torrent, const QString &oldCategory) { emit torrentCategoryChanged(torrent, oldCategory); diff --git a/src/base/bittorrent/sessionimpl.h b/src/base/bittorrent/sessionimpl.h index b40622a7f273..9921f0781fee 100644 --- a/src/base/bittorrent/sessionimpl.h +++ b/src/base/bittorrent/sessionimpl.h @@ -463,6 +463,7 @@ namespace BitTorrent void handleTorrentShareLimitChanged(TorrentImpl *torrent); void handleTorrentNameChanged(TorrentImpl *torrent); void handleTorrentSavePathChanged(TorrentImpl *torrent); + void handleTorrentRunOnFinishedProgramChanged(TorrentImpl *torrent); void handleTorrentCategoryChanged(TorrentImpl *torrent, const QString &oldCategory); void handleTorrentTagAdded(TorrentImpl *torrent, const Tag &tag); void handleTorrentTagRemoved(TorrentImpl *torrent, const Tag &tag); diff --git a/src/base/bittorrent/torrent.h b/src/base/bittorrent/torrent.h index 8a2d0bc76ab9..f664285497a0 100644 --- a/src/base/bittorrent/torrent.h +++ b/src/base/bittorrent/torrent.h @@ -204,6 +204,8 @@ namespace BitTorrent virtual void setDownloadPath(const Path &downloadPath) = 0; virtual Path rootPath() const = 0; virtual Path contentPath() const = 0; + virtual QString runOnFinishedProgram() const = 0; + virtual void setRunOnFinishedProgram(const QString &program) = 0; virtual QString category() const = 0; virtual bool belongsToCategory(const QString &category) const = 0; virtual bool setCategory(const QString &category) = 0; diff --git a/src/base/bittorrent/torrentimpl.cpp b/src/base/bittorrent/torrentimpl.cpp index 84450fe4f542..8d3bdefc9978 100644 --- a/src/base/bittorrent/torrentimpl.cpp +++ b/src/base/bittorrent/torrentimpl.cpp @@ -311,6 +311,7 @@ TorrentImpl::TorrentImpl(SessionImpl *session, const lt::torrent_handle &nativeH , m_name(params.name) , m_savePath(params.savePath) , m_downloadPath(params.downloadPath) + , m_runOnFinishedProgram(params.runOnFinishedProgram) , m_category(params.category) , m_tags(params.tags) , m_ratioLimit(params.ratioLimit) @@ -2256,6 +2257,7 @@ void TorrentImpl::prepareResumeData(lt::add_torrent_params params) .stopped = m_isStopped, .stopCondition = m_stopCondition, .addToQueueTop = false, + .runOnFinishedProgram = m_runOnFinishedProgram, .ratioLimit = m_ratioLimit, .seedingTimeLimit = m_seedingTimeLimit, .inactiveSeedingTimeLimit = m_inactiveSeedingTimeLimit, @@ -2621,6 +2623,18 @@ void TorrentImpl::updateProgress() } } +QString TorrentImpl::runOnFinishedProgram() const { + return m_runOnFinishedProgram; +} + +void TorrentImpl::setRunOnFinishedProgram(const QString &program) { + if (m_runOnFinishedProgram != program) { + m_runOnFinishedProgram = program; + deferredRequestResumeData(); + m_session->handleTorrentRunOnFinishedProgramChanged(this); + } +} + void TorrentImpl::setRatioLimit(qreal limit) { if (limit < USE_GLOBAL_RATIO) diff --git a/src/base/bittorrent/torrentimpl.h b/src/base/bittorrent/torrentimpl.h index f10c53177ec4..49b12b3808d9 100644 --- a/src/base/bittorrent/torrentimpl.h +++ b/src/base/bittorrent/torrentimpl.h @@ -124,6 +124,8 @@ namespace BitTorrent Path actualStorageLocation() const override; Path rootPath() const override; Path contentPath() const override; + QString runOnFinishedProgram() const override; + void setRunOnFinishedProgram(const QString &program) override; QString category() const override; bool belongsToCategory(const QString &category) const override; bool setCategory(const QString &category) override; @@ -358,6 +360,7 @@ namespace BitTorrent QString m_name; Path m_savePath; Path m_downloadPath; + QString m_runOnFinishedProgram; QString m_category; TagSet m_tags; qreal m_ratioLimit = 0; diff --git a/src/gui/torrentoptionsdialog.cpp b/src/gui/torrentoptionsdialog.cpp index be35440b31cf..abeaf99b6235 100644 --- a/src/gui/torrentoptionsdialog.cpp +++ b/src/gui/torrentoptionsdialog.cpp @@ -77,6 +77,7 @@ TorrentOptionsDialog::TorrentOptionsDialog(QWidget *parent, const QListdownloadPath->setDialogCaption(tr("Choose save path")); const auto *session = BitTorrent::Session::instance(); + bool allSameRunOnFinishedProgram = true; bool allSameUpLimit = true; bool allSameDownLimit = true; bool allSameRatio = true; @@ -96,6 +97,7 @@ TorrentOptionsDialog::TorrentOptionsDialog(QWidget *parent, const QListisAutoTMMEnabled(); const Path firstTorrentSavePath = torrents[0]->savePath(); const Path firstTorrentDownloadPath = torrents[0]->downloadPath(); + const QString firstRunOnFinishedProgram = torrents[0]->runOnFinishedProgram(); const QString firstTorrentCategory = torrents[0]->category(); const int firstTorrentUpLimit = std::max(0, torrents[0]->uploadLimit()); @@ -132,6 +134,11 @@ TorrentOptionsDialog::TorrentOptionsDialog(QWidget *parent, const QListdownloadPath() != firstTorrentDownloadPath) allSameDownloadPath = false; } + if (allSameRunOnFinishedProgram) + { + if (torrent->runOnFinishedProgram() != firstRunOnFinishedProgram) + allSameRunOnFinishedProgram = false; + } if (m_allSameCategory) { if (torrent->category() != firstTorrentCategory) @@ -217,6 +224,27 @@ TorrentOptionsDialog::TorrentOptionsDialog(QWidget *parent, const QListcheckUseDownloadPath->setCheckState(Qt::PartiallyChecked); } + if (allSameRunOnFinishedProgram) + { + m_ui->runOnFinishedProgram->setText(firstRunOnFinishedProgram); + const bool active = !firstRunOnFinishedProgram.isEmpty(); + m_ui->runOnFinishedEnabled->setChecked(active); + m_ui->runOnFinishedProgram->setEnabled(active); + } + else + { + m_ui->runOnFinishedEnabled->setCheckState(Qt::PartiallyChecked); + m_ui->runOnFinishedProgram->setEnabled(false); + } + + QAction *runOnFinishedWarning = new QAction(this); + m_ui->runOnFinishedProgram->addAction(runOnFinishedWarning, QLineEdit::TrailingPosition); + runOnFinishedWarning->setIcon(style()->standardIcon(QStyle::SP_MessageBoxInformation)); + runOnFinishedWarning->setToolTip(tr("This overrides the global setting")); + + connect(m_ui->runOnFinishedEnabled, &QCheckBox::clicked, + this, &TorrentOptionsDialog::handleRunOnFinishedEnabledChanged); + if (!m_allSameCategory) { m_ui->comboCategory->addItem(m_currentCategoriesString); @@ -345,6 +373,8 @@ TorrentOptionsDialog::TorrentOptionsDialog(QWidget *parent, const QListsavePath->selectedPath(), .downloadPath = m_ui->downloadPath->selectedPath(), + .runOnFinishedEnabled = m_ui->runOnFinishedEnabled->checkState(), + .runOnFinishedProgram = m_ui->runOnFinishedProgram->text(), .category = m_ui->comboCategory->currentText(), .ratio = m_ui->torrentShareLimitsWidget->ratioLimit(), .seedingTime = m_ui->torrentShareLimitsWidget->seedingTimeLimit(), @@ -417,6 +447,16 @@ void TorrentOptionsDialog::accept() } } + if(m_ui->runOnFinishedEnabled->checkState() == Qt::Checked) + { + const QString runOnFinishedProgram = m_ui->runOnFinishedProgram->text(); + torrent->setRunOnFinishedProgram(runOnFinishedProgram); + } + else + { + torrent->setRunOnFinishedProgram(QString()); + } + const QString category = m_ui->comboCategory->currentText(); // index 0 is always the current category if ((m_initialValues.category != category) || (m_ui->comboCategory->currentIndex() != 0)) @@ -542,6 +582,12 @@ void TorrentOptionsDialog::handleTMMChanged() } } +void TorrentOptionsDialog::handleRunOnFinishedEnabledChanged() +{ + const bool isChecked = m_ui->runOnFinishedEnabled->checkState() == Qt::Checked; + m_ui->runOnFinishedProgram->setEnabled(isChecked); +} + void TorrentOptionsDialog::handleUseDownloadPathChanged() { const bool isChecked = m_ui->checkUseDownloadPath->checkState() == Qt::Checked; diff --git a/src/gui/torrentoptionsdialog.h b/src/gui/torrentoptionsdialog.h index 25accf3dbbc0..f79222f36603 100644 --- a/src/gui/torrentoptionsdialog.h +++ b/src/gui/torrentoptionsdialog.h @@ -67,6 +67,7 @@ public slots: private slots: void handleCategoryChanged(int index); void handleTMMChanged(); + void handleRunOnFinishedEnabledChanged(); void handleUseDownloadPathChanged(); void handleUpSpeedLimitChanged(); @@ -84,6 +85,8 @@ private slots: { Path savePath; Path downloadPath; + Qt::CheckState runOnFinishedEnabled; + QString runOnFinishedProgram; QString category; std::optional ratio; std::optional seedingTime; diff --git a/src/gui/torrentoptionsdialog.ui b/src/gui/torrentoptionsdialog.ui index 9fcb65d5240f..a9c09515e59a 100644 --- a/src/gui/torrentoptionsdialog.ui +++ b/src/gui/torrentoptionsdialog.ui @@ -47,6 +47,20 @@ + + + + + + Run external program when finished + + + + + + + +