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 "addtorrentmanager.h"
32 #include "base/bittorrent/infohash.h"
33 #include "base/bittorrent/session.h"
34 #include "base/bittorrent/torrentdescriptor.h"
35 #include "base/logger.h"
36 #include "base/net/downloadmanager.h"
37 #include "base/preferences.h"
39 AddTorrentManager::AddTorrentManager(IApplication
*app
, BitTorrent::Session
*btSession
, QObject
*parent
)
40 : ApplicationComponent(app
, parent
)
41 , m_btSession
{btSession
}
44 connect(btSession
, &BitTorrent::Session::torrentAdded
, this, &AddTorrentManager::onSessionTorrentAdded
);
45 connect(btSession
, &BitTorrent::Session::addTorrentFailed
, this, &AddTorrentManager::onSessionAddTorrentFailed
);
48 BitTorrent::Session
*AddTorrentManager::btSession() const
53 bool AddTorrentManager::addTorrent(const QString
&source
, const BitTorrent::AddTorrentParams
¶ms
)
55 // `source`: .torrent file path, magnet URI or URL
60 if (Net::DownloadManager::hasSupportedScheme(source
))
62 LogMsg(tr("Downloading torrent... Source: \"%1\"").arg(source
));
63 const auto *pref
= Preferences::instance();
65 Net::DownloadManager::instance()->download(Net::DownloadRequest(source
).limit(pref
->getTorrentFileSizeLimit())
66 , pref
->useProxyForGeneralPurposes(), this, &AddTorrentManager::onDownloadFinished
);
67 m_downloadedTorrents
[source
] = params
;
71 if (const auto parseResult
= BitTorrent::TorrentDescriptor::parse(source
))
73 return processTorrent(source
, parseResult
.value(), params
);
75 else if (source
.startsWith(u
"magnet:", Qt::CaseInsensitive
))
77 handleAddTorrentFailed(source
, parseResult
.error());
81 const Path decodedPath
{source
.startsWith(u
"file://", Qt::CaseInsensitive
)
82 ? QUrl::fromEncoded(source
.toLocal8Bit()).toLocalFile() : source
};
83 auto torrentFileGuard
= std::make_shared
<TorrentFileGuard
>(decodedPath
);
84 if (const auto loadResult
= BitTorrent::TorrentDescriptor::loadFromFile(decodedPath
))
86 setTorrentFileGuard(source
, torrentFileGuard
);
87 return processTorrent(source
, loadResult
.value(), params
);
91 handleAddTorrentFailed(source
, loadResult
.error());
98 bool AddTorrentManager::addTorrentToSession(const QString
&source
, const BitTorrent::TorrentDescriptor
&torrentDescr
99 , const BitTorrent::AddTorrentParams
&addTorrentParams
)
101 const bool result
= btSession()->addTorrent(torrentDescr
, addTorrentParams
);
103 m_sourcesByInfoHash
[torrentDescr
.infoHash()] = source
;
108 void AddTorrentManager::onDownloadFinished(const Net::DownloadResult
&result
)
110 const QString
&source
= result
.url
;
111 const BitTorrent::AddTorrentParams addTorrentParams
= m_downloadedTorrents
.take(source
);
113 switch (result
.status
)
115 case Net::DownloadStatus::Success
:
116 if (const auto loadResult
= BitTorrent::TorrentDescriptor::load(result
.data
))
117 processTorrent(source
, loadResult
.value(), addTorrentParams
);
119 handleAddTorrentFailed(source
, loadResult
.error());
121 case Net::DownloadStatus::RedirectedToMagnet
:
122 if (const auto parseResult
= BitTorrent::TorrentDescriptor::parse(result
.magnetURI
))
123 processTorrent(source
, parseResult
.value(), addTorrentParams
);
125 handleAddTorrentFailed(source
, parseResult
.error());
128 handleAddTorrentFailed(source
, result
.errorString
);
132 void AddTorrentManager::onSessionTorrentAdded(BitTorrent::Torrent
*torrent
)
134 if (const QString source
= m_sourcesByInfoHash
.take(torrent
->infoHash()); !source
.isEmpty())
136 auto torrentFileGuard
= m_guardedTorrentFiles
.take(source
);
137 if (torrentFileGuard
)
138 torrentFileGuard
->markAsAddedToSession();
139 emit
torrentAdded(source
, torrent
);
143 void AddTorrentManager::onSessionAddTorrentFailed(const BitTorrent::InfoHash
&infoHash
, const QString
&reason
)
145 if (const QString source
= m_sourcesByInfoHash
.take(infoHash
); !source
.isEmpty())
147 auto torrentFileGuard
= m_guardedTorrentFiles
.take(source
);
148 if (torrentFileGuard
)
149 torrentFileGuard
->setAutoRemove(false);
150 emit
addTorrentFailed(source
, reason
);
154 void AddTorrentManager::handleAddTorrentFailed(const QString
&source
, const QString
&reason
)
156 LogMsg(tr("Failed to add torrent. Source: \"%1\". Reason: \"%2\"").arg(source
, reason
), Log::WARNING
);
157 emit
addTorrentFailed(source
, reason
);
160 void AddTorrentManager::handleDuplicateTorrent(const QString
&source
161 , const BitTorrent::TorrentDescriptor
&torrentDescr
, BitTorrent::Torrent
*existingTorrent
)
163 const bool hasMetadata
= torrentDescr
.info().has_value();
166 // Trying to set metadata to existing torrent in case if it has none
167 existingTorrent
->setMetadata(*torrentDescr
.info());
170 const bool isPrivate
= existingTorrent
->isPrivate() || (hasMetadata
&& torrentDescr
.info()->isPrivate());
172 if (!btSession()->isMergeTrackersEnabled())
174 message
= tr("Merging of trackers is disabled");
178 message
= tr("Trackers cannot be merged because it is a private torrent");
182 // merge trackers and web seeds
183 existingTorrent
->addTrackers(torrentDescr
.trackers());
184 existingTorrent
->addUrlSeeds(torrentDescr
.urlSeeds());
185 message
= tr("Trackers are merged from new source");
188 LogMsg(tr("Detected an attempt to add a duplicate torrent. Source: %1. Existing torrent: %2. Result: %3")
189 .arg(source
, existingTorrent
->name(), message
));
190 emit
addTorrentFailed(source
, message
);
193 void AddTorrentManager::setTorrentFileGuard(const QString
&source
, std::shared_ptr
<TorrentFileGuard
> torrentFileGuard
)
195 m_guardedTorrentFiles
.emplace(source
, std::move(torrentFileGuard
));
198 std::shared_ptr
<TorrentFileGuard
> AddTorrentManager::releaseTorrentFileGuard(const QString
&source
)
200 return m_guardedTorrentFiles
.take(source
);
203 bool AddTorrentManager::processTorrent(const QString
&source
, const BitTorrent::TorrentDescriptor
&torrentDescr
204 , const BitTorrent::AddTorrentParams
&addTorrentParams
)
206 const BitTorrent::InfoHash infoHash
= torrentDescr
.infoHash();
208 if (BitTorrent::Torrent
*torrent
= btSession()->findTorrent(infoHash
))
210 // a duplicate torrent is being added
211 handleDuplicateTorrent(source
, torrentDescr
, torrent
);
215 return addTorrentToSession(source
, torrentDescr
, addTorrentParams
);