WebAPI: Add `forced` parameter to `torrents/add`
[qBittorrent.git] / src / gui / guiaddtorrentmanager.cpp
blobd600a4e17c890d428e2db200564404dab82f3659
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2006 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 "guiaddtorrentmanager.h"
32 #include <QScreen>
34 #include "base/bittorrent/session.h"
35 #include "base/bittorrent/torrentdescriptor.h"
36 #include "base/logger.h"
37 #include "base/net/downloadmanager.h"
38 #include "base/preferences.h"
39 #include "base/torrentfileguard.h"
40 #include "addnewtorrentdialog.h"
41 #include "interfaces/iguiapplication.h"
42 #include "mainwindow.h"
43 #include "raisedmessagebox.h"
45 namespace
47 void adjustDialogGeometry(QWidget *dialog, const QWidget *parentWindow)
49 // It is preferable to place the dialog in the center of the parent window.
50 // However, if it goes beyond the current screen, then move it so that it fits there
51 // (or, if the dialog is larger than the current screen, at least make sure that
52 // the upper/left coordinates of the dialog are inside it).
54 QRect dialogGeometry = dialog->geometry();
56 dialogGeometry.moveCenter(parentWindow->geometry().center());
58 const QRect screenGeometry = parentWindow->screen()->availableGeometry();
60 QPoint delta = screenGeometry.bottomRight() - dialogGeometry.bottomRight();
61 if (delta.x() > 0)
62 delta.setX(0);
63 if (delta.y() > 0)
64 delta.setY(0);
65 dialogGeometry.translate(delta);
67 const QPoint frameOffset {10, 40};
68 delta = screenGeometry.topLeft() - dialogGeometry.topLeft() + frameOffset;
69 if (delta.x() < 0)
70 delta.setX(0);
71 if (delta.y() < 0)
72 delta.setY(0);
73 dialogGeometry.translate(delta);
75 dialog->setGeometry(dialogGeometry);
79 GUIAddTorrentManager::GUIAddTorrentManager(IGUIApplication *app, BitTorrent::Session *session, QObject *parent)
80 : GUIApplicationComponent(app, session, parent)
82 connect(btSession(), &BitTorrent::Session::metadataDownloaded, this, &GUIAddTorrentManager::onMetadataDownloaded);
85 bool GUIAddTorrentManager::addTorrent(const QString &source, const BitTorrent::AddTorrentParams &params, const AddTorrentOption option)
87 // `source`: .torrent file path, magnet URI or URL
89 if (source.isEmpty())
90 return false;
92 const auto *pref = Preferences::instance();
94 if ((option == AddTorrentOption::SkipDialog)
95 || ((option == AddTorrentOption::Default) && !pref->isAddNewTorrentDialogEnabled()))
97 return AddTorrentManager::addTorrent(source, params);
100 if (Net::DownloadManager::hasSupportedScheme(source))
102 LogMsg(tr("Downloading torrent... Source: \"%1\"").arg(source));
103 // Launch downloader
104 Net::DownloadManager::instance()->download(Net::DownloadRequest(source).limit(pref->getTorrentFileSizeLimit())
105 , pref->useProxyForGeneralPurposes(), this, &GUIAddTorrentManager::onDownloadFinished);
106 m_downloadedTorrents[source] = params;
108 return true;
111 if (const auto parseResult = BitTorrent::TorrentDescriptor::parse(source))
113 return processTorrent(source, parseResult.value(), params);
115 else if (source.startsWith(u"magnet:", Qt::CaseInsensitive))
117 handleAddTorrentFailed(source, parseResult.error());
118 return false;
121 const Path decodedPath {source.startsWith(u"file://", Qt::CaseInsensitive)
122 ? QUrl::fromEncoded(source.toLocal8Bit()).toLocalFile() : source};
123 auto torrentFileGuard = std::make_shared<TorrentFileGuard>(decodedPath);
124 if (const auto loadResult = BitTorrent::TorrentDescriptor::loadFromFile(decodedPath))
126 const BitTorrent::TorrentDescriptor &torrentDescriptor = loadResult.value();
127 const bool isProcessing = processTorrent(source, torrentDescriptor, params);
128 if (isProcessing)
129 setTorrentFileGuard(source, torrentFileGuard);
130 return isProcessing;
132 else
134 handleAddTorrentFailed(decodedPath.toString(), loadResult.error());
135 return false;
138 return false;
141 void GUIAddTorrentManager::onDownloadFinished(const Net::DownloadResult &result)
143 const QString &source = result.url;
144 const BitTorrent::AddTorrentParams addTorrentParams = m_downloadedTorrents.take(source);
146 switch (result.status)
148 case Net::DownloadStatus::Success:
149 if (const auto loadResult = BitTorrent::TorrentDescriptor::load(result.data))
150 processTorrent(source, loadResult.value(), addTorrentParams);
151 else
152 handleAddTorrentFailed(source, loadResult.error());
153 break;
154 case Net::DownloadStatus::RedirectedToMagnet:
155 if (const auto parseResult = BitTorrent::TorrentDescriptor::parse(result.magnetURI))
156 processTorrent(source, parseResult.value(), addTorrentParams);
157 else
158 handleAddTorrentFailed(source, parseResult.error());
159 break;
160 default:
161 handleAddTorrentFailed(source, result.errorString);
165 void GUIAddTorrentManager::onMetadataDownloaded(const BitTorrent::TorrentInfo &metadata)
167 Q_ASSERT(metadata.isValid());
168 if (!metadata.isValid()) [[unlikely]]
169 return;
171 for (const auto &[infoHash, dialog] : m_dialogs.asKeyValueRange())
173 if (metadata.matchesInfoHash(infoHash))
174 dialog->updateMetadata(metadata);
178 bool GUIAddTorrentManager::processTorrent(const QString &source
179 , const BitTorrent::TorrentDescriptor &torrentDescr, const BitTorrent::AddTorrentParams &params)
181 const bool hasMetadata = torrentDescr.info().has_value();
182 const BitTorrent::InfoHash infoHash = torrentDescr.infoHash();
184 // Prevent showing the dialog if download is already present
185 if (BitTorrent::Torrent *torrent = btSession()->findTorrent(infoHash))
187 if (Preferences::instance()->confirmMergeTrackers())
189 if (hasMetadata)
191 // Trying to set metadata to existing torrent in case if it has none
192 torrent->setMetadata(*torrentDescr.info());
195 const bool isPrivate = torrent->isPrivate() || (hasMetadata && torrentDescr.info()->isPrivate());
196 const QString dialogCaption = tr("Torrent is already present");
197 if (isPrivate)
199 // We cannot merge trackers for private torrent but we still notify user
200 // about duplicate torrent if confirmation dialog is enabled.
201 RaisedMessageBox::warning(app()->mainWindow(), dialogCaption
202 , tr("Trackers cannot be merged because it is a private torrent."));
204 else
206 const bool mergeTrackers = btSession()->isMergeTrackersEnabled();
207 const QMessageBox::StandardButton btn = RaisedMessageBox::question(app()->mainWindow(), dialogCaption
208 , tr("Torrent '%1' is already in the transfer list. Do you want to merge trackers from new source?").arg(torrent->name())
209 , (QMessageBox::Yes | QMessageBox::No), (mergeTrackers ? QMessageBox::Yes : QMessageBox::No));
210 if (btn == QMessageBox::Yes)
212 torrent->addTrackers(torrentDescr.trackers());
213 torrent->addUrlSeeds(torrentDescr.urlSeeds());
217 else
219 handleDuplicateTorrent(source, torrentDescr, torrent);
222 return false;
225 if (!hasMetadata)
226 btSession()->downloadMetadata(torrentDescr);
228 // By not setting a parent to the "AddNewTorrentDialog", all those dialogs
229 // will be displayed on top and will not overlap with the main window.
230 auto *dlg = new AddNewTorrentDialog(torrentDescr, params, nullptr);
231 // Qt::Window is required to avoid showing only two dialog on top (see #12852).
232 // Also improves the general convenience of adding multiple torrents.
233 dlg->setWindowFlags(Qt::Window);
235 dlg->setAttribute(Qt::WA_DeleteOnClose);
236 m_dialogs[infoHash] = dlg;
237 connect(dlg, &AddNewTorrentDialog::torrentAccepted, this
238 , [this, source, dlg](const BitTorrent::TorrentDescriptor &torrentDescr, const BitTorrent::AddTorrentParams &addTorrentParams)
240 if (dlg->isDoNotDeleteTorrentChecked())
242 if (auto torrentFileGuard = releaseTorrentFileGuard(source))
243 torrentFileGuard->setAutoRemove(false);
246 addTorrentToSession(source, torrentDescr, addTorrentParams);
248 connect(dlg, &AddNewTorrentDialog::torrentRejected, this, [this, source]
250 releaseTorrentFileGuard(source);
252 connect(dlg, &QDialog::finished, this, [this, source, infoHash]
254 m_dialogs.remove(infoHash);
257 adjustDialogGeometry(dlg, app()->mainWindow());
258 dlg->show();
260 return true;