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"
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"
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();
65 dialogGeometry
.translate(delta
);
67 const QPoint frameOffset
{10, 40};
68 delta
= screenGeometry
.topLeft() - dialogGeometry
.topLeft() + frameOffset
;
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
¶ms
, const AddTorrentOption option
)
87 // `source`: .torrent file path, magnet URI or URL
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
));
104 Net::DownloadManager::instance()->download(Net::DownloadRequest(source
).limit(pref
->getTorrentFileSizeLimit())
105 , pref
->useProxyForGeneralPurposes(), this, &GUIAddTorrentManager::onDownloadFinished
);
106 m_downloadedTorrents
[source
] = params
;
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());
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
);
129 setTorrentFileGuard(source
, torrentFileGuard
);
134 handleAddTorrentFailed(decodedPath
.toString(), loadResult
.error());
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
);
152 handleAddTorrentFailed(source
, loadResult
.error());
154 case Net::DownloadStatus::RedirectedToMagnet
:
155 if (const auto parseResult
= BitTorrent::TorrentDescriptor::parse(result
.magnetURI
))
156 processTorrent(source
, parseResult
.value(), addTorrentParams
);
158 handleAddTorrentFailed(source
, parseResult
.error());
161 handleAddTorrentFailed(source
, result
.errorString
);
165 void GUIAddTorrentManager::onMetadataDownloaded(const BitTorrent::TorrentInfo
&metadata
)
167 Q_ASSERT(metadata
.isValid());
168 if (!metadata
.isValid()) [[unlikely
]]
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
¶ms
)
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())
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");
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."));
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());
219 handleDuplicateTorrent(source
, torrentDescr
, torrent
);
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());