Merge pull request #10982 from Chocobo1/cleanup
[qBittorrent.git] / src / base / bittorrent / torrenthandle.cpp
blob59ef250baf8cc1469c644bcca33cceba562eec2d
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2015 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 "torrenthandle.h"
32 #include <algorithm>
33 #include <type_traits>
35 #ifdef Q_OS_WIN
36 #include <windows.h>
37 #endif
39 #include <libtorrent/address.hpp>
40 #include <libtorrent/alert_types.hpp>
41 #include <libtorrent/bencode.hpp>
42 #include <libtorrent/create_torrent.hpp>
43 #include <libtorrent/entry.hpp>
44 #include <libtorrent/magnet_uri.hpp>
45 #include <libtorrent/time.hpp>
46 #include <libtorrent/version.hpp>
48 #if (LIBTORRENT_VERSION_NUM >= 10200)
49 #include <libtorrent/write_resume_data.hpp>
50 #endif
52 #include <QBitArray>
53 #include <QDateTime>
54 #include <QDebug>
55 #include <QDir>
56 #include <QFile>
57 #include <QStringList>
58 #include <QUrl>
60 #include "base/global.h"
61 #include "base/logger.h"
62 #include "base/preferences.h"
63 #include "base/profile.h"
64 #include "base/tristatebool.h"
65 #include "base/utils/fs.h"
66 #include "base/utils/string.h"
67 #include "downloadpriority.h"
68 #include "peeraddress.h"
69 #include "peerinfo.h"
70 #include "private/ltunderlyingtype.h"
71 #include "session.h"
72 #include "trackerentry.h"
74 const QString QB_EXT {QStringLiteral(".!qB")};
76 using namespace BitTorrent;
78 #if (LIBTORRENT_VERSION_NUM >= 10200)
79 namespace libtorrent
81 namespace aux
83 template <typename T, typename Tag>
84 uint qHash(const strong_typedef<T, Tag> &key, const uint seed)
86 return static_cast<uint>((std::hash<strong_typedef<T, Tag>> {})(key) ^ seed);
90 #endif
92 namespace
94 #if (LIBTORRENT_VERSION_NUM < 10200)
95 using LTDownloadPriority = int;
96 using LTPieceIndex = int;
97 using LTQueuePosition = int;
98 #else
99 using LTDownloadPriority = lt::download_priority_t;
100 using LTPieceIndex = lt::piece_index_t;
101 using LTQueuePosition = lt::queue_position_t;
102 #endif
104 std::vector<LTDownloadPriority> toLTDownloadPriorities(const QVector<DownloadPriority> &priorities)
106 std::vector<LTDownloadPriority> out;
107 std::transform(priorities.cbegin(), priorities.cend()
108 , std::back_inserter(out), [](BitTorrent::DownloadPriority priority)
110 return static_cast<LTDownloadPriority>(
111 static_cast<LTUnderlyingType<LTDownloadPriority>>(priority));
113 return out;
116 using ListType = lt::entry::list_type;
118 ListType setToEntryList(const QSet<QString> &input)
120 ListType entryList;
121 for (const QString &setValue : input)
122 entryList.emplace_back(setValue.toStdString());
123 return entryList;
127 // AddTorrentData
129 CreateTorrentParams::CreateTorrentParams()
130 : restored(false)
131 , disableTempPath(false)
132 , sequential(false)
133 , firstLastPiecePriority(false)
134 , hasSeedStatus(false)
135 , skipChecking(false)
136 , hasRootFolder(true)
137 , forced(false)
138 , paused(false)
139 , uploadLimit(-1)
140 , downloadLimit(-1)
141 , ratioLimit(TorrentHandle::USE_GLOBAL_RATIO)
142 , seedingTimeLimit(TorrentHandle::USE_GLOBAL_SEEDING_TIME)
146 CreateTorrentParams::CreateTorrentParams(const AddTorrentParams &params)
147 : restored(false)
148 , name(params.name)
149 , category(params.category)
150 , tags(params.tags)
151 , savePath(params.savePath)
152 , disableTempPath(params.disableTempPath)
153 , sequential(params.sequential)
154 , firstLastPiecePriority(params.firstLastPiecePriority)
155 , hasSeedStatus(params.skipChecking) // do not react on 'torrent_finished_alert' when skipping
156 , skipChecking(params.skipChecking)
157 , hasRootFolder(params.createSubfolder == TriStateBool::Undefined
158 ? Session::instance()->isCreateTorrentSubfolder()
159 : params.createSubfolder == TriStateBool::True)
160 , forced(params.addForced == TriStateBool::True)
161 , paused(params.addPaused == TriStateBool::Undefined
162 ? Session::instance()->isAddTorrentPaused()
163 : params.addPaused == TriStateBool::True)
164 , uploadLimit(params.uploadLimit)
165 , downloadLimit(params.downloadLimit)
166 , filePriorities(params.filePriorities)
167 , ratioLimit(params.ignoreShareLimits ? TorrentHandle::NO_RATIO_LIMIT : TorrentHandle::USE_GLOBAL_RATIO)
168 , seedingTimeLimit(params.ignoreShareLimits ? TorrentHandle::NO_SEEDING_TIME_LIMIT : TorrentHandle::USE_GLOBAL_SEEDING_TIME)
170 bool useAutoTMM = (params.useAutoTMM == TriStateBool::Undefined
171 ? !Session::instance()->isAutoTMMDisabledByDefault()
172 : params.useAutoTMM == TriStateBool::True);
173 if (useAutoTMM)
174 savePath = "";
175 else if (savePath.trimmed().isEmpty())
176 savePath = Session::instance()->defaultSavePath();
179 // TorrentHandle
181 const qreal TorrentHandle::USE_GLOBAL_RATIO = -2.;
182 const qreal TorrentHandle::NO_RATIO_LIMIT = -1.;
184 const int TorrentHandle::USE_GLOBAL_SEEDING_TIME = -2;
185 const int TorrentHandle::NO_SEEDING_TIME_LIMIT = -1;
187 const qreal TorrentHandle::MAX_RATIO = 9999.;
188 const int TorrentHandle::MAX_SEEDING_TIME = 525600;
190 TorrentHandle::TorrentHandle(Session *session, const lt::torrent_handle &nativeHandle,
191 const CreateTorrentParams &params)
192 : QObject(session)
193 , m_session(session)
194 , m_nativeHandle(nativeHandle)
195 , m_state(TorrentState::Unknown)
196 , m_renameCount(0)
197 , m_useAutoTMM(params.savePath.isEmpty())
198 , m_name(params.name)
199 , m_savePath(Utils::Fs::toNativePath(params.savePath))
200 , m_category(params.category)
201 , m_tags(params.tags)
202 , m_hasSeedStatus(params.hasSeedStatus)
203 , m_ratioLimit(params.ratioLimit)
204 , m_seedingTimeLimit(params.seedingTimeLimit)
205 , m_tempPathDisabled(params.disableTempPath)
206 , m_fastresumeDataRejected(false)
207 , m_hasMissingFiles(false)
208 , m_hasRootFolder(params.hasRootFolder)
209 , m_needsToSetFirstLastPiecePriority(false)
210 , m_needsToStartForced(params.forced)
211 , m_pauseWhenReady(params.paused)
213 if (m_useAutoTMM)
214 m_savePath = Utils::Fs::toNativePath(m_session->categorySavePath(m_category));
216 updateStatus();
217 m_hash = InfoHash(m_nativeStatus.info_hash);
219 // NB: the following two if statements are present because we don't want
220 // to set either sequential download or first/last piece priority to false
221 // if their respective flags in data are false when a torrent is being
222 // resumed. This is because, in that circumstance, this constructor is
223 // called with those flags set to false, even if the torrent was set to
224 // download sequentially or have first/last piece priority enabled when
225 // its resume data was saved. These two settings are restored later. But
226 // if we set them to false now, both will erroneously not be restored.
227 if (!params.restored || params.sequential)
228 setSequentialDownload(params.sequential);
229 if (!params.restored || params.firstLastPiecePriority)
230 setFirstLastPiecePriority(params.firstLastPiecePriority);
232 if (!params.restored && hasMetadata()) {
233 if (filesCount() == 1)
234 m_hasRootFolder = false;
237 if (!hasMetadata()) {
238 // There is nothing to prepare
239 if (!m_pauseWhenReady) {
240 // Resume torrent because it was added in "resumed" state
241 // but it's actually paused during initialization.
242 m_startupState = Starting;
243 resume_impl(m_needsToStartForced);
245 else {
246 m_startupState = Started;
247 m_pauseWhenReady = false;
252 TorrentHandle::~TorrentHandle() {}
254 bool TorrentHandle::isValid() const
256 return m_nativeHandle.is_valid();
259 InfoHash TorrentHandle::hash() const
261 return m_hash;
264 QString TorrentHandle::name() const
266 QString name = m_name;
267 if (name.isEmpty())
268 name = QString::fromStdString(m_nativeStatus.name);
270 if (name.isEmpty() && hasMetadata())
271 name = QString::fromStdString(m_torrentInfo.nativeInfo()->orig_files().name());
273 if (name.isEmpty())
274 name = m_hash;
276 return name;
279 QDateTime TorrentHandle::creationDate() const
281 return m_torrentInfo.creationDate();
284 QString TorrentHandle::creator() const
286 return m_torrentInfo.creator();
289 QString TorrentHandle::comment() const
291 return m_torrentInfo.comment();
294 bool TorrentHandle::isPrivate() const
296 return m_torrentInfo.isPrivate();
299 qlonglong TorrentHandle::totalSize() const
301 return m_torrentInfo.totalSize();
304 // get the size of the torrent without the filtered files
305 qlonglong TorrentHandle::wantedSize() const
307 return m_nativeStatus.total_wanted;
310 qlonglong TorrentHandle::completedSize() const
312 return m_nativeStatus.total_wanted_done;
315 qlonglong TorrentHandle::incompletedSize() const
317 return (m_nativeStatus.total_wanted - m_nativeStatus.total_wanted_done);
320 qlonglong TorrentHandle::pieceLength() const
322 return m_torrentInfo.pieceLength();
325 qlonglong TorrentHandle::wastedSize() const
327 return (m_nativeStatus.total_failed_bytes + m_nativeStatus.total_redundant_bytes);
330 QString TorrentHandle::currentTracker() const
332 return QString::fromStdString(m_nativeStatus.current_tracker);
335 QString TorrentHandle::savePath(bool actual) const
337 if (actual)
338 return Utils::Fs::toUniformPath(nativeActualSavePath());
339 else
340 return Utils::Fs::toUniformPath(m_savePath);
343 QString TorrentHandle::rootPath(bool actual) const
345 if ((filesCount() > 1) && !hasRootFolder())
346 return {};
348 const QString firstFilePath = filePath(0);
349 const int slashIndex = firstFilePath.indexOf('/');
350 if (slashIndex >= 0)
351 return QDir(savePath(actual)).absoluteFilePath(firstFilePath.left(slashIndex));
352 else
353 return QDir(savePath(actual)).absoluteFilePath(firstFilePath);
356 QString TorrentHandle::contentPath(bool actual) const
358 if (filesCount() == 1)
359 return QDir(savePath(actual)).absoluteFilePath(filePath(0));
360 else if (hasRootFolder())
361 return rootPath(actual);
362 else
363 return savePath(actual);
366 bool TorrentHandle::isAutoTMMEnabled() const
368 return m_useAutoTMM;
371 void TorrentHandle::setAutoTMMEnabled(bool enabled)
373 if (m_useAutoTMM == enabled) return;
375 m_useAutoTMM = enabled;
376 m_session->handleTorrentSavingModeChanged(this);
378 if (m_useAutoTMM)
379 move_impl(m_session->categorySavePath(m_category), true);
382 bool TorrentHandle::hasRootFolder() const
384 return m_hasRootFolder;
387 QString TorrentHandle::nativeActualSavePath() const
389 return QString::fromStdString(m_nativeStatus.save_path);
392 bool TorrentHandle::isAutoManaged() const
394 #if (LIBTORRENT_VERSION_NUM < 10200)
395 return m_nativeStatus.auto_managed;
396 #else
397 return bool {m_nativeStatus.flags & lt::torrent_flags::auto_managed};
398 #endif
401 void TorrentHandle::setAutoManaged(const bool enable)
403 #if (LIBTORRENT_VERSION_NUM < 10200)
404 m_nativeHandle.auto_managed(enable);
405 #else
406 if (enable)
407 m_nativeHandle.set_flags(lt::torrent_flags::auto_managed);
408 else
409 m_nativeHandle.unset_flags(lt::torrent_flags::auto_managed);
410 #endif
413 QVector<TrackerEntry> TorrentHandle::trackers() const
415 const std::vector<lt::announce_entry> announces = m_nativeHandle.trackers();
417 QVector<TrackerEntry> entries;
418 entries.reserve(announces.size());
419 for (const lt::announce_entry &tracker : announces)
420 entries << tracker;
421 return entries;
424 QHash<QString, TrackerInfo> TorrentHandle::trackerInfos() const
426 return m_trackerInfos;
429 void TorrentHandle::addTrackers(const QVector<TrackerEntry> &trackers)
431 const QVector<TrackerEntry> currentTrackers = this->trackers();
433 QVector<TrackerEntry> newTrackers;
434 newTrackers.reserve(trackers.size());
436 for (const TrackerEntry &tracker : trackers) {
437 if (!currentTrackers.contains(tracker)) {
438 m_nativeHandle.add_tracker(tracker.nativeEntry());
439 newTrackers << tracker;
443 if (!newTrackers.isEmpty())
444 m_session->handleTorrentTrackersAdded(this, newTrackers);
447 void TorrentHandle::replaceTrackers(const QVector<TrackerEntry> &trackers)
449 QVector<TrackerEntry> currentTrackers = this->trackers();
451 QVector<TrackerEntry> newTrackers;
452 newTrackers.reserve(trackers.size());
454 std::vector<lt::announce_entry> announces;
456 for (const TrackerEntry &tracker : trackers) {
457 announces.emplace_back(tracker.nativeEntry());
459 if (!currentTrackers.removeOne(tracker))
460 newTrackers << tracker;
463 m_nativeHandle.replace_trackers(announces);
465 if (newTrackers.isEmpty() && currentTrackers.isEmpty()) {
466 // when existing tracker reorders
467 m_session->handleTorrentTrackersChanged(this);
469 else {
470 if (!currentTrackers.isEmpty())
471 m_session->handleTorrentTrackersRemoved(this, currentTrackers);
473 if (!newTrackers.isEmpty())
474 m_session->handleTorrentTrackersAdded(this, newTrackers);
478 QList<QUrl> TorrentHandle::urlSeeds() const
480 QList<QUrl> urlSeeds;
481 const std::set<std::string> seeds = m_nativeHandle.url_seeds();
483 for (const std::string &urlSeed : seeds)
484 urlSeeds.append(QUrl(urlSeed.c_str()));
486 return urlSeeds;
489 void TorrentHandle::addUrlSeeds(const QList<QUrl> &urlSeeds)
491 QList<QUrl> addedUrlSeeds;
492 for (const QUrl &urlSeed : urlSeeds) {
493 if (addUrlSeed(urlSeed))
494 addedUrlSeeds << urlSeed;
497 if (!addedUrlSeeds.isEmpty())
498 m_session->handleTorrentUrlSeedsAdded(this, addedUrlSeeds);
501 void TorrentHandle::removeUrlSeeds(const QList<QUrl> &urlSeeds)
503 QList<QUrl> removedUrlSeeds;
504 for (const QUrl &urlSeed : urlSeeds) {
505 if (removeUrlSeed(urlSeed))
506 removedUrlSeeds << urlSeed;
509 if (!removedUrlSeeds.isEmpty())
510 m_session->handleTorrentUrlSeedsRemoved(this, removedUrlSeeds);
513 bool TorrentHandle::addUrlSeed(const QUrl &urlSeed)
515 QList<QUrl> seeds = urlSeeds();
516 if (seeds.contains(urlSeed)) return false;
518 m_nativeHandle.add_url_seed(urlSeed.toString().toStdString());
519 return true;
522 bool TorrentHandle::removeUrlSeed(const QUrl &urlSeed)
524 QList<QUrl> seeds = urlSeeds();
525 if (!seeds.contains(urlSeed)) return false;
527 m_nativeHandle.remove_url_seed(urlSeed.toString().toStdString());
528 return true;
531 bool TorrentHandle::connectPeer(const PeerAddress &peerAddress)
533 lt::error_code ec;
534 const lt::address addr = lt::address::from_string(peerAddress.ip.toString().toStdString(), ec);
535 if (ec) return false;
537 const boost::asio::ip::tcp::endpoint ep(addr, peerAddress.port);
538 m_nativeHandle.connect_peer(ep);
539 return true;
542 bool TorrentHandle::needSaveResumeData() const
544 return m_nativeHandle.need_save_resume_data();
547 void TorrentHandle::saveResumeData()
549 m_nativeHandle.save_resume_data();
550 m_session->handleTorrentSaveResumeDataRequested(this);
553 int TorrentHandle::filesCount() const
555 return m_torrentInfo.filesCount();
558 int TorrentHandle::piecesCount() const
560 return m_torrentInfo.piecesCount();
563 int TorrentHandle::piecesHave() const
565 return m_nativeStatus.num_pieces;
568 qreal TorrentHandle::progress() const
570 if (!isChecking()) {
571 if (!m_nativeStatus.total_wanted)
572 return 0.;
574 if (m_nativeStatus.total_wanted_done == m_nativeStatus.total_wanted)
575 return 1.;
577 const qreal progress = static_cast<qreal>(m_nativeStatus.total_wanted_done) / m_nativeStatus.total_wanted;
578 Q_ASSERT((progress >= 0.f) && (progress <= 1.f));
579 return progress;
582 return m_nativeStatus.progress;
585 QString TorrentHandle::category() const
587 return m_category;
590 bool TorrentHandle::belongsToCategory(const QString &category) const
592 if (m_category.isEmpty()) return category.isEmpty();
593 if (!Session::isValidCategoryName(category)) return false;
595 if (m_category == category) return true;
597 if (m_session->isSubcategoriesEnabled() && m_category.startsWith(category + '/'))
598 return true;
600 return false;
603 QSet<QString> TorrentHandle::tags() const
605 return m_tags;
608 bool TorrentHandle::hasTag(const QString &tag) const
610 return m_tags.contains(tag);
613 bool TorrentHandle::addTag(const QString &tag)
615 if (!Session::isValidTag(tag))
616 return false;
618 if (!hasTag(tag)) {
619 if (!m_session->hasTag(tag))
620 if (!m_session->addTag(tag))
621 return false;
622 m_tags.insert(tag);
623 m_session->handleTorrentTagAdded(this, tag);
624 return true;
626 return false;
629 bool TorrentHandle::removeTag(const QString &tag)
631 if (m_tags.remove(tag)) {
632 m_session->handleTorrentTagRemoved(this, tag);
633 return true;
635 return false;
638 void TorrentHandle::removeAllTags()
640 for (const QString &tag : asConst(tags()))
641 removeTag(tag);
644 QDateTime TorrentHandle::addedTime() const
646 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.added_time);
649 qreal TorrentHandle::ratioLimit() const
651 return m_ratioLimit;
654 int TorrentHandle::seedingTimeLimit() const
656 return m_seedingTimeLimit;
659 QString TorrentHandle::filePath(int index) const
661 return m_torrentInfo.filePath(index);
664 QString TorrentHandle::fileName(int index) const
666 if (!hasMetadata()) return {};
667 return Utils::Fs::fileName(filePath(index));
670 qlonglong TorrentHandle::fileSize(int index) const
672 return m_torrentInfo.fileSize(index);
675 // Return a list of absolute paths corresponding
676 // to all files in a torrent
677 QStringList TorrentHandle::absoluteFilePaths() const
679 if (!hasMetadata()) return {};
681 const QDir saveDir(savePath(true));
682 QStringList res;
683 for (int i = 0; i < filesCount(); ++i)
684 res << Utils::Fs::expandPathAbs(saveDir.absoluteFilePath(filePath(i)));
685 return res;
688 QStringList TorrentHandle::absoluteFilePathsUnwanted() const
690 if (!hasMetadata()) return {};
692 const QDir saveDir(savePath(true));
693 #if (LIBTORRENT_VERSION_NUM < 10200)
694 const std::vector<LTDownloadPriority> fp = m_nativeHandle.file_priorities();
695 #else
696 const std::vector<LTDownloadPriority> fp = m_nativeHandle.get_file_priorities();
697 #endif
699 QStringList res;
700 for (int i = 0; i < static_cast<int>(fp.size()); ++i) {
701 if (fp[i] == LTDownloadPriority {0}) {
702 const QString path = Utils::Fs::expandPathAbs(saveDir.absoluteFilePath(filePath(i)));
703 if (path.contains(".unwanted"))
704 res << path;
708 return res;
711 QVector<DownloadPriority> TorrentHandle::filePriorities() const
713 #if (LIBTORRENT_VERSION_NUM < 10200)
714 const std::vector<LTDownloadPriority> fp = m_nativeHandle.file_priorities();
715 #else
716 const std::vector<LTDownloadPriority> fp = m_nativeHandle.get_file_priorities();
717 #endif
719 QVector<DownloadPriority> ret;
720 std::transform(fp.cbegin(), fp.cend(), std::back_inserter(ret), [](LTDownloadPriority priority)
722 return static_cast<DownloadPriority>(LTUnderlyingType<LTDownloadPriority> {priority});
724 return ret;
727 TorrentInfo TorrentHandle::info() const
729 return m_torrentInfo;
732 bool TorrentHandle::isPaused() const
734 #if (LIBTORRENT_VERSION_NUM < 10200)
735 return (m_nativeStatus.paused && !isAutoManaged());
736 #else
737 return ((m_nativeStatus.flags & lt::torrent_flags::paused)
738 && !isAutoManaged());
739 #endif
742 bool TorrentHandle::isResumed() const
744 return !isPaused();
747 bool TorrentHandle::isQueued() const
749 #if (LIBTORRENT_VERSION_NUM < 10200)
750 return (m_nativeStatus.paused && isAutoManaged());
751 #else
752 return ((m_nativeStatus.flags & lt::torrent_flags::paused)
753 && isAutoManaged());
754 #endif
757 bool TorrentHandle::isChecking() const
759 return ((m_nativeStatus.state == lt::torrent_status::checking_files)
760 || (m_nativeStatus.state == lt::torrent_status::checking_resume_data));
763 bool TorrentHandle::isDownloading() const
765 return m_state == TorrentState::Downloading
766 || m_state == TorrentState::DownloadingMetadata
767 || m_state == TorrentState::StalledDownloading
768 || m_state == TorrentState::CheckingDownloading
769 || m_state == TorrentState::PausedDownloading
770 || m_state == TorrentState::QueuedDownloading
771 || m_state == TorrentState::ForcedDownloading;
774 bool TorrentHandle::isUploading() const
776 return m_state == TorrentState::Uploading
777 || m_state == TorrentState::StalledUploading
778 || m_state == TorrentState::CheckingUploading
779 || m_state == TorrentState::QueuedUploading
780 || m_state == TorrentState::ForcedUploading;
783 bool TorrentHandle::isCompleted() const
785 return m_state == TorrentState::Uploading
786 || m_state == TorrentState::StalledUploading
787 || m_state == TorrentState::CheckingUploading
788 || m_state == TorrentState::PausedUploading
789 || m_state == TorrentState::QueuedUploading
790 || m_state == TorrentState::ForcedUploading;
793 bool TorrentHandle::isActive() const
795 if (m_state == TorrentState::StalledDownloading)
796 return (uploadPayloadRate() > 0);
798 return m_state == TorrentState::DownloadingMetadata
799 || m_state == TorrentState::Downloading
800 || m_state == TorrentState::ForcedDownloading
801 || m_state == TorrentState::Uploading
802 || m_state == TorrentState::ForcedUploading
803 || m_state == TorrentState::Moving;
806 bool TorrentHandle::isInactive() const
808 return !isActive();
811 bool TorrentHandle::isErrored() const
813 return m_state == TorrentState::MissingFiles
814 || m_state == TorrentState::Error;
817 bool TorrentHandle::isSeed() const
819 // Affected by bug http://code.rasterbar.com/libtorrent/ticket/402
820 //bool result;
821 //result = m_nativeHandle.is_seed());
822 //return result;
823 // May suffer from approximation problems
824 //return (progress() == 1.);
825 // This looks safe
826 return ((m_nativeStatus.state == lt::torrent_status::finished)
827 || (m_nativeStatus.state == lt::torrent_status::seeding));
830 bool TorrentHandle::isForced() const
832 #if (LIBTORRENT_VERSION_NUM < 10200)
833 return (!m_nativeStatus.paused && !isAutoManaged());
834 #else
835 return (!(m_nativeStatus.flags & lt::torrent_flags::paused)
836 && !isAutoManaged());
837 #endif
840 bool TorrentHandle::isSequentialDownload() const
842 #if (LIBTORRENT_VERSION_NUM < 10200)
843 return m_nativeStatus.sequential_download;
844 #else
845 return bool {m_nativeStatus.flags & lt::torrent_flags::sequential_download};
846 #endif
849 bool TorrentHandle::hasFirstLastPiecePriority() const
851 if (!hasMetadata())
852 return m_needsToSetFirstLastPiecePriority;
854 #if (LIBTORRENT_VERSION_NUM < 10200)
855 const std::vector<LTDownloadPriority> filePriorities = nativeHandle().file_priorities();
856 #else
857 const std::vector<LTDownloadPriority> filePriorities = nativeHandle().get_file_priorities();
858 #endif
859 for (int i = 0; i < static_cast<int>(filePriorities.size()); ++i) {
860 if (filePriorities[i] <= LTDownloadPriority {0})
861 continue;
863 const TorrentInfo::PieceRange extremities = info().filePieces(i);
864 const LTDownloadPriority firstPiecePrio = nativeHandle().piece_priority(LTPieceIndex {extremities.first()});
865 const LTDownloadPriority lastPiecePrio = nativeHandle().piece_priority(LTPieceIndex {extremities.last()});
866 return ((firstPiecePrio == LTDownloadPriority {7}) && (lastPiecePrio == LTDownloadPriority {7}));
869 return false;
872 TorrentState TorrentHandle::state() const
874 return m_state;
877 void TorrentHandle::updateState()
879 if (m_nativeStatus.state == lt::torrent_status::checking_resume_data) {
880 m_state = TorrentState::CheckingResumeData;
882 else if (isMoveInProgress()) {
883 m_state = TorrentState::Moving;
885 else if (isPaused()) {
886 if (hasMissingFiles())
887 m_state = TorrentState::MissingFiles;
888 else if (hasError())
889 m_state = TorrentState::Error;
890 else
891 m_state = isSeed() ? TorrentState::PausedUploading : TorrentState::PausedDownloading;
893 else {
894 if (m_session->isQueueingSystemEnabled() && isQueued() && !isChecking()) {
895 m_state = isSeed() ? TorrentState::QueuedUploading : TorrentState::QueuedDownloading;
897 else {
898 switch (m_nativeStatus.state) {
899 case lt::torrent_status::finished:
900 case lt::torrent_status::seeding:
901 if (isForced())
902 m_state = TorrentState::ForcedUploading;
903 else
904 m_state = m_nativeStatus.upload_payload_rate > 0 ? TorrentState::Uploading : TorrentState::StalledUploading;
905 break;
906 case lt::torrent_status::allocating:
907 m_state = TorrentState::Allocating;
908 break;
909 case lt::torrent_status::checking_files:
910 m_state = m_hasSeedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading;
911 break;
912 case lt::torrent_status::downloading_metadata:
913 m_state = TorrentState::DownloadingMetadata;
914 break;
915 case lt::torrent_status::downloading:
916 if (isForced())
917 m_state = TorrentState::ForcedDownloading;
918 else
919 m_state = m_nativeStatus.download_payload_rate > 0 ? TorrentState::Downloading : TorrentState::StalledDownloading;
920 break;
921 default:
922 qWarning("Unrecognized torrent status, should not happen!!! status was %d", m_nativeStatus.state);
923 m_state = TorrentState::Unknown;
929 bool TorrentHandle::hasMetadata() const
931 return m_nativeStatus.has_metadata;
934 bool TorrentHandle::hasMissingFiles() const
936 return m_hasMissingFiles;
939 bool TorrentHandle::hasError() const
941 #if (LIBTORRENT_VERSION_NUM < 10200)
942 return (m_nativeStatus.paused && m_nativeStatus.errc);
943 #else
944 return ((m_nativeStatus.flags & lt::torrent_flags::paused)
945 && m_nativeStatus.errc);
946 #endif
949 bool TorrentHandle::hasFilteredPieces() const
951 #if (LIBTORRENT_VERSION_NUM < 10200)
952 const std::vector<LTDownloadPriority> pp = m_nativeHandle.piece_priorities();
953 #else
954 const std::vector<LTDownloadPriority> pp = m_nativeHandle.get_piece_priorities();
955 #endif
956 return std::any_of(pp.cbegin(), pp.cend(), [](const LTDownloadPriority priority)
958 return (priority == LTDownloadPriority {0});
962 int TorrentHandle::queuePosition() const
964 if (m_nativeStatus.queue_position < LTQueuePosition {0}) return 0;
966 return static_cast<int>(m_nativeStatus.queue_position) + 1;
969 QString TorrentHandle::error() const
971 return QString::fromStdString(m_nativeStatus.errc.message());
974 qlonglong TorrentHandle::totalDownload() const
976 return m_nativeStatus.all_time_download;
979 qlonglong TorrentHandle::totalUpload() const
981 return m_nativeStatus.all_time_upload;
984 qlonglong TorrentHandle::activeTime() const
986 #if (LIBTORRENT_VERSION_NUM < 10200)
987 return m_nativeStatus.active_time;
988 #else
989 return lt::total_seconds(m_nativeStatus.active_duration);
990 #endif
993 qlonglong TorrentHandle::finishedTime() const
995 #if (LIBTORRENT_VERSION_NUM < 10200)
996 return m_nativeStatus.finished_time;
997 #else
998 return lt::total_seconds(m_nativeStatus.finished_duration);
999 #endif
1002 qlonglong TorrentHandle::seedingTime() const
1004 #if (LIBTORRENT_VERSION_NUM < 10200)
1005 return m_nativeStatus.seeding_time;
1006 #else
1007 return lt::total_seconds(m_nativeStatus.seeding_duration);
1008 #endif
1011 qulonglong TorrentHandle::eta() const
1013 if (isPaused()) return MAX_ETA;
1015 const SpeedSampleAvg speedAverage = m_speedMonitor.average();
1017 if (isSeed()) {
1018 const qreal maxRatioValue = maxRatio();
1019 const int maxSeedingTimeValue = maxSeedingTime();
1020 if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0)) return MAX_ETA;
1022 qlonglong ratioEta = MAX_ETA;
1024 if ((speedAverage.upload > 0) && (maxRatioValue >= 0)) {
1026 qlonglong realDL = totalDownload();
1027 if (realDL <= 0)
1028 realDL = wantedSize();
1030 ratioEta = ((realDL * maxRatioValue) - totalUpload()) / speedAverage.upload;
1033 qlonglong seedingTimeEta = MAX_ETA;
1035 if (maxSeedingTimeValue >= 0) {
1036 seedingTimeEta = (maxSeedingTimeValue * 60) - seedingTime();
1037 if (seedingTimeEta < 0)
1038 seedingTimeEta = 0;
1041 return qMin(ratioEta, seedingTimeEta);
1044 if (!speedAverage.download) return MAX_ETA;
1046 return (wantedSize() - completedSize()) / speedAverage.download;
1049 QVector<qreal> TorrentHandle::filesProgress() const
1051 std::vector<boost::int64_t> fp;
1052 m_nativeHandle.file_progress(fp, lt::torrent_handle::piece_granularity);
1054 const int count = static_cast<int>(fp.size());
1055 QVector<qreal> result;
1056 result.reserve(count);
1057 for (int i = 0; i < count; ++i) {
1058 const qlonglong size = fileSize(i);
1059 if ((size <= 0) || (fp[i] == size))
1060 result << 1;
1061 else
1062 result << (fp[i] / static_cast<qreal>(size));
1065 return result;
1068 int TorrentHandle::seedsCount() const
1070 return m_nativeStatus.num_seeds;
1073 int TorrentHandle::peersCount() const
1075 return m_nativeStatus.num_peers;
1078 int TorrentHandle::leechsCount() const
1080 return (m_nativeStatus.num_peers - m_nativeStatus.num_seeds);
1083 int TorrentHandle::totalSeedsCount() const
1085 return (m_nativeStatus.num_complete > 0) ? m_nativeStatus.num_complete : m_nativeStatus.list_seeds;
1088 int TorrentHandle::totalPeersCount() const
1090 const int peers = m_nativeStatus.num_complete + m_nativeStatus.num_incomplete;
1091 return (peers > 0) ? peers : m_nativeStatus.list_peers;
1094 int TorrentHandle::totalLeechersCount() const
1096 return (m_nativeStatus.num_incomplete > 0) ? m_nativeStatus.num_incomplete : (m_nativeStatus.list_peers - m_nativeStatus.list_seeds);
1099 int TorrentHandle::completeCount() const
1101 // additional info: https://github.com/qbittorrent/qBittorrent/pull/5300#issuecomment-267783646
1102 return m_nativeStatus.num_complete;
1105 int TorrentHandle::incompleteCount() const
1107 // additional info: https://github.com/qbittorrent/qBittorrent/pull/5300#issuecomment-267783646
1108 return m_nativeStatus.num_incomplete;
1111 QDateTime TorrentHandle::lastSeenComplete() const
1113 if (m_nativeStatus.last_seen_complete > 0)
1114 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.last_seen_complete);
1115 else
1116 return {};
1119 QDateTime TorrentHandle::completedTime() const
1121 if (m_nativeStatus.completed_time > 0)
1122 return QDateTime::fromSecsSinceEpoch(m_nativeStatus.completed_time);
1123 else
1124 return {};
1127 qlonglong TorrentHandle::timeSinceUpload() const
1129 #if (LIBTORRENT_VERSION_NUM < 10200)
1130 return m_nativeStatus.time_since_upload;
1131 #else
1132 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_upload);
1133 #endif
1136 qlonglong TorrentHandle::timeSinceDownload() const
1138 #if (LIBTORRENT_VERSION_NUM < 10200)
1139 return m_nativeStatus.time_since_download;
1140 #else
1141 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_download);
1142 #endif
1145 qlonglong TorrentHandle::timeSinceActivity() const
1147 const qlonglong upTime = timeSinceUpload();
1148 const qlonglong downTime = timeSinceDownload();
1149 return ((upTime < 0) != (downTime < 0))
1150 ? std::max(upTime, downTime)
1151 : std::min(upTime, downTime);
1154 int TorrentHandle::downloadLimit() const
1156 return m_nativeHandle.download_limit();
1159 int TorrentHandle::uploadLimit() const
1161 return m_nativeHandle.upload_limit();
1164 bool TorrentHandle::superSeeding() const
1166 #if (LIBTORRENT_VERSION_NUM < 10200)
1167 return m_nativeStatus.super_seeding;
1168 #else
1169 return bool {m_nativeStatus.flags & lt::torrent_flags::super_seeding};
1170 #endif
1173 QList<PeerInfo> TorrentHandle::peers() const
1175 QList<PeerInfo> peers;
1176 std::vector<lt::peer_info> nativePeers;
1178 m_nativeHandle.get_peer_info(nativePeers);
1180 for (const lt::peer_info &peer : nativePeers)
1181 peers << PeerInfo(this, peer);
1183 return peers;
1186 QBitArray TorrentHandle::pieces() const
1188 QBitArray result(m_nativeStatus.pieces.size());
1190 for (int i = 0; i < m_nativeStatus.pieces.size(); ++i)
1191 result.setBit(i, m_nativeStatus.pieces.get_bit(LTPieceIndex {i}));
1193 return result;
1196 QBitArray TorrentHandle::downloadingPieces() const
1198 QBitArray result(piecesCount());
1200 std::vector<lt::partial_piece_info> queue;
1201 m_nativeHandle.get_download_queue(queue);
1203 for (const lt::partial_piece_info &info : queue)
1204 #if (LIBTORRENT_VERSION_NUM < 10200)
1205 result.setBit(info.piece_index);
1206 #else
1207 result.setBit(LTUnderlyingType<LTPieceIndex> {info.piece_index});
1208 #endif
1210 return result;
1213 QVector<int> TorrentHandle::pieceAvailability() const
1215 std::vector<int> avail;
1216 m_nativeHandle.piece_availability(avail);
1218 return QVector<int>::fromStdVector(avail);
1221 qreal TorrentHandle::distributedCopies() const
1223 return m_nativeStatus.distributed_copies;
1226 qreal TorrentHandle::maxRatio() const
1228 if (m_ratioLimit == USE_GLOBAL_RATIO)
1229 return m_session->globalMaxRatio();
1231 return m_ratioLimit;
1234 int TorrentHandle::maxSeedingTime() const
1236 if (m_seedingTimeLimit == USE_GLOBAL_SEEDING_TIME)
1237 return m_session->globalMaxSeedingMinutes();
1239 return m_seedingTimeLimit;
1242 qreal TorrentHandle::realRatio() const
1244 const boost::int64_t upload = m_nativeStatus.all_time_upload;
1245 // special case for a seeder who lost its stats, also assume nobody will import a 99% done torrent
1246 const boost::int64_t download = (m_nativeStatus.all_time_download < m_nativeStatus.total_done * 0.01) ? m_nativeStatus.total_done : m_nativeStatus.all_time_download;
1248 if (download == 0)
1249 return (upload == 0) ? 0.0 : MAX_RATIO;
1251 const qreal ratio = upload / static_cast<qreal>(download);
1252 Q_ASSERT(ratio >= 0.0);
1253 return (ratio > MAX_RATIO) ? MAX_RATIO : ratio;
1256 int TorrentHandle::uploadPayloadRate() const
1258 return m_nativeStatus.upload_payload_rate;
1261 int TorrentHandle::downloadPayloadRate() const
1263 return m_nativeStatus.download_payload_rate;
1266 qlonglong TorrentHandle::totalPayloadUpload() const
1268 return m_nativeStatus.total_payload_upload;
1271 qlonglong TorrentHandle::totalPayloadDownload() const
1273 return m_nativeStatus.total_payload_download;
1276 int TorrentHandle::connectionsCount() const
1278 return m_nativeStatus.num_connections;
1281 int TorrentHandle::connectionsLimit() const
1283 return m_nativeStatus.connections_limit;
1286 qlonglong TorrentHandle::nextAnnounce() const
1288 return lt::total_seconds(m_nativeStatus.next_announce);
1291 void TorrentHandle::setName(const QString &name)
1293 if (m_name != name) {
1294 m_name = name;
1295 m_session->handleTorrentNameChanged(this);
1299 bool TorrentHandle::setCategory(const QString &category)
1301 if (m_category != category) {
1302 if (!category.isEmpty() && !m_session->categories().contains(category))
1303 return false;
1305 const QString oldCategory = m_category;
1306 m_category = category;
1307 m_session->handleTorrentCategoryChanged(this, oldCategory);
1309 if (m_useAutoTMM) {
1310 if (!m_session->isDisableAutoTMMWhenCategoryChanged())
1311 move_impl(m_session->categorySavePath(m_category), true);
1312 else
1313 setAutoTMMEnabled(false);
1317 return true;
1320 void TorrentHandle::move(QString path)
1322 if (m_startupState != Started) return;
1324 m_useAutoTMM = false;
1325 m_session->handleTorrentSavingModeChanged(this);
1327 path = Utils::Fs::toUniformPath(path.trimmed());
1328 if (path.isEmpty())
1329 path = m_session->defaultSavePath();
1330 if (!path.endsWith('/'))
1331 path += '/';
1333 move_impl(path, false);
1336 void TorrentHandle::move_impl(QString path, bool overwrite)
1338 if (path == savePath()) return;
1339 path = Utils::Fs::toNativePath(path);
1341 if (!useTempPath()) {
1342 moveStorage(path, overwrite);
1344 else {
1345 m_savePath = path;
1346 m_session->handleTorrentSavePathChanged(this);
1350 void TorrentHandle::forceReannounce(int index)
1352 m_nativeHandle.force_reannounce(0, index);
1355 void TorrentHandle::forceDHTAnnounce()
1357 m_nativeHandle.force_dht_announce();
1360 void TorrentHandle::forceRecheck()
1362 if (m_startupState != Started) return;
1363 if (!hasMetadata()) return;
1365 m_nativeHandle.force_recheck();
1366 m_unchecked = false;
1368 if (isPaused()) {
1369 #if (LIBTORRENT_VERSION_NUM < 10200)
1370 m_nativeHandle.stop_when_ready(true);
1371 #else
1372 m_nativeHandle.set_flags(lt::torrent_flags::stop_when_ready);
1373 #endif
1374 setAutoManaged(true);
1375 m_pauseWhenReady = true;
1379 void TorrentHandle::setSequentialDownload(const bool enable)
1381 #if (LIBTORRENT_VERSION_NUM < 10200)
1382 m_nativeHandle.set_sequential_download(enable);
1383 m_nativeStatus.sequential_download = enable; // prevent return cached value
1384 #else
1385 if (enable) {
1386 m_nativeHandle.set_flags(lt::torrent_flags::sequential_download);
1387 m_nativeStatus.flags |= lt::torrent_flags::sequential_download; // prevent return cached value
1389 else {
1390 m_nativeHandle.unset_flags(lt::torrent_flags::sequential_download);
1391 m_nativeStatus.flags &= ~lt::torrent_flags::sequential_download; // prevent return cached value
1393 #endif
1395 saveResumeData();
1398 void TorrentHandle::toggleSequentialDownload()
1400 setSequentialDownload(!isSequentialDownload());
1403 void TorrentHandle::setFirstLastPiecePriority(const bool enabled)
1405 setFirstLastPiecePriorityImpl(enabled);
1408 void TorrentHandle::setFirstLastPiecePriorityImpl(const bool enabled, const QVector<DownloadPriority> &updatedFilePrio)
1410 // Download first and last pieces first for every file in the torrent
1412 if (!hasMetadata()) {
1413 m_needsToSetFirstLastPiecePriority = enabled;
1414 return;
1417 #if (LIBTORRENT_VERSION_NUM < 10200)
1418 const std::vector<LTDownloadPriority> filePriorities = !updatedFilePrio.isEmpty() ? toLTDownloadPriorities(updatedFilePrio)
1419 : nativeHandle().file_priorities();
1420 std::vector<LTDownloadPriority> piecePriorities = nativeHandle().piece_priorities();
1421 #else
1422 const std::vector<LTDownloadPriority> filePriorities = !updatedFilePrio.isEmpty() ? toLTDownloadPriorities(updatedFilePrio)
1423 : nativeHandle().get_file_priorities();
1424 std::vector<LTDownloadPriority> piecePriorities = nativeHandle().get_piece_priorities();
1425 #endif
1426 // Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it
1427 // we might get the old/wrong values, so we rely on `updatedFilePrio` in this case.
1428 for (int index = 0; index < static_cast<int>(filePriorities.size()); ++index) {
1429 const LTDownloadPriority filePrio = filePriorities[index];
1430 if (filePrio <= LTDownloadPriority {0})
1431 continue;
1433 // Determine the priority to set
1434 const LTDownloadPriority newPrio = enabled ? LTDownloadPriority {7} : filePrio;
1435 const TorrentInfo::PieceRange extremities = info().filePieces(index);
1437 // worst case: AVI index = 1% of total file size (at the end of the file)
1438 const int nNumPieces = std::ceil(fileSize(index) * 0.01 / pieceLength());
1439 for (int i = 0; i < nNumPieces; ++i) {
1440 piecePriorities[extremities.first() + i] = newPrio;
1441 piecePriorities[extremities.last() - i] = newPrio;
1445 m_nativeHandle.prioritize_pieces(piecePriorities);
1447 LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
1448 .arg((enabled ? tr("On") : tr("Off")), name()));
1450 saveResumeData();
1453 void TorrentHandle::toggleFirstLastPiecePriority()
1455 setFirstLastPiecePriority(!hasFirstLastPiecePriority());
1458 void TorrentHandle::pause()
1460 if (m_startupState != Started) return;
1461 if (m_pauseWhenReady) return;
1462 if (isChecking()) {
1463 m_pauseWhenReady = true;
1464 return;
1467 if (isPaused()) return;
1469 setAutoManaged(false);
1470 m_nativeHandle.pause();
1472 // Libtorrent doesn't emit a torrent_paused_alert when the
1473 // torrent is queued (no I/O)
1474 // We test on the cached m_nativeStatus
1475 if (isQueued())
1476 m_session->handleTorrentPaused(this);
1479 void TorrentHandle::resume(bool forced)
1481 if (m_startupState != Started) return;
1483 m_pauseWhenReady = false;
1484 resume_impl(forced);
1487 void TorrentHandle::resume_impl(bool forced)
1489 if (hasError())
1490 m_nativeHandle.clear_error();
1492 if (m_hasMissingFiles) {
1493 m_hasMissingFiles = false;
1494 m_nativeHandle.force_recheck();
1497 setAutoManaged(!forced);
1498 m_nativeHandle.resume();
1501 void TorrentHandle::moveStorage(const QString &newPath, bool overwrite)
1503 if (isMoveInProgress()) {
1504 qDebug("enqueue move storage to %s", qUtf8Printable(newPath));
1505 m_moveStorageInfo.queuedPath = newPath;
1506 m_moveStorageInfo.queuedOverwrite = overwrite;
1508 else {
1509 const QString oldPath = nativeActualSavePath();
1510 if (QDir(oldPath) == QDir(newPath)) return;
1512 qDebug("move storage: %s to %s", qUtf8Printable(oldPath), qUtf8Printable(newPath));
1513 // Actually move the storage
1514 #if (LIBTORRENT_VERSION_NUM < 10200)
1515 m_nativeHandle.move_storage(newPath.toUtf8().constData()
1516 , (overwrite ? lt::always_replace_files : lt::dont_replace));
1517 #else
1518 m_nativeHandle.move_storage(newPath.toUtf8().constData()
1519 , (overwrite ? lt::move_flags_t::always_replace_files : lt::move_flags_t::dont_replace));
1520 #endif
1521 m_moveStorageInfo.oldPath = oldPath;
1522 m_moveStorageInfo.newPath = newPath;
1523 updateState();
1527 void TorrentHandle::renameFile(const int index, const QString &name)
1529 m_oldPath[LTFileIndex {index}].push_back(filePath(index));
1530 ++m_renameCount;
1531 m_nativeHandle.rename_file(LTFileIndex {index}, Utils::Fs::toNativePath(name).toStdString());
1534 bool TorrentHandle::saveTorrentFile(const QString &path)
1536 if (!m_torrentInfo.isValid()) return false;
1537 #if (LIBTORRENT_VERSION_NUM < 10200)
1538 const lt::create_torrent torrentCreator = lt::create_torrent(*(m_torrentInfo.nativeInfo()), true);
1539 #else
1540 const lt::create_torrent torrentCreator = lt::create_torrent(*(m_torrentInfo.nativeInfo()));
1541 #endif
1542 const lt::entry torrentEntry = torrentCreator.generate();
1544 QVector<char> out;
1545 lt::bencode(std::back_inserter(out), torrentEntry);
1546 QFile torrentFile(path);
1547 if (!out.empty() && torrentFile.open(QIODevice::WriteOnly))
1548 return (torrentFile.write(&out[0], out.size()) == out.size());
1550 return false;
1553 void TorrentHandle::handleStateUpdate(const lt::torrent_status &nativeStatus)
1555 updateStatus(nativeStatus);
1558 void TorrentHandle::handleStorageMovedAlert(const lt::storage_moved_alert *p)
1560 if (!isMoveInProgress()) {
1561 qWarning() << "Unexpected " << Q_FUNC_INFO << " call.";
1562 return;
1565 const QString newPath(p->storage_path());
1566 if (newPath != m_moveStorageInfo.newPath) {
1567 qWarning() << Q_FUNC_INFO << ": New path doesn't match a path in a queue.";
1568 return;
1571 LogMsg(tr("Successfully moved torrent: %1. New path: %2").arg(name(), m_moveStorageInfo.newPath));
1573 const QDir oldDir {m_moveStorageInfo.oldPath};
1574 if ((oldDir == QDir(m_session->torrentTempPath(info())))
1575 && (oldDir != QDir(m_session->tempPath()))) {
1576 // torrent without root folder still has it in its temporary save path
1577 // so its temp path isn't equal to temp path root
1578 qDebug() << "Removing torrent temp folder:" << m_moveStorageInfo.oldPath;
1579 Utils::Fs::smartRemoveEmptyFolderTree(m_moveStorageInfo.oldPath);
1582 m_moveStorageInfo.newPath.clear();
1583 updateStatus();
1585 if (!m_moveStorageInfo.queuedPath.isEmpty()) {
1586 moveStorage(m_moveStorageInfo.queuedPath, m_moveStorageInfo.queuedOverwrite);
1587 m_moveStorageInfo.queuedPath.clear();
1590 if (!useTempPath()) {
1591 m_savePath = newPath;
1592 m_session->handleTorrentSavePathChanged(this);
1595 while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
1596 m_moveFinishedTriggers.takeFirst()();
1599 void TorrentHandle::handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert *p)
1601 if (!isMoveInProgress()) {
1602 qWarning() << "Unexpected " << Q_FUNC_INFO << " call.";
1603 return;
1606 LogMsg(tr("Could not move torrent: '%1'. Reason: %2")
1607 .arg(name(), QString::fromStdString(p->message())), Log::CRITICAL);
1609 m_moveStorageInfo.newPath.clear();
1610 updateStatus();
1612 if (!m_moveStorageInfo.queuedPath.isEmpty()) {
1613 moveStorage(m_moveStorageInfo.queuedPath, m_moveStorageInfo.queuedOverwrite);
1614 m_moveStorageInfo.queuedPath.clear();
1617 while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
1618 m_moveFinishedTriggers.takeFirst()();
1621 void TorrentHandle::handleTrackerReplyAlert(const lt::tracker_reply_alert *p)
1623 const QString trackerUrl(p->tracker_url());
1624 qDebug("Received a tracker reply from %s (Num_peers = %d)", qUtf8Printable(trackerUrl), p->num_peers);
1625 // Connection was successful now. Remove possible old errors
1626 m_trackerInfos[trackerUrl] = {{}, p->num_peers};
1628 m_session->handleTorrentTrackerReply(this, trackerUrl);
1631 void TorrentHandle::handleTrackerWarningAlert(const lt::tracker_warning_alert *p)
1633 const QString trackerUrl = p->tracker_url();
1634 const QString message = p->warning_message();
1636 // Connection was successful now but there is a warning message
1637 m_trackerInfos[trackerUrl].lastMessage = message; // Store warning message
1639 m_session->handleTorrentTrackerWarning(this, trackerUrl);
1642 void TorrentHandle::handleTrackerErrorAlert(const lt::tracker_error_alert *p)
1644 const QString trackerUrl = p->tracker_url();
1645 const QString message = p->error_message();
1647 m_trackerInfos[trackerUrl].lastMessage = message;
1649 m_session->handleTorrentTrackerError(this, trackerUrl);
1652 void TorrentHandle::handleTorrentCheckedAlert(const lt::torrent_checked_alert *p)
1654 Q_UNUSED(p);
1655 qDebug("\"%s\" have just finished checking", qUtf8Printable(name()));
1657 if (m_startupState == Preparing) {
1658 if (!m_pauseWhenReady) {
1659 if (!m_hasMissingFiles) {
1660 // Resume torrent because it was added in "resumed" state
1661 // but it's actually paused during initialization.
1662 m_startupState = Starting;
1663 resume_impl(m_needsToStartForced);
1665 else {
1666 // Torrent that has missing files is paused.
1667 m_startupState = Started;
1670 else {
1671 m_startupState = Started;
1672 m_pauseWhenReady = false;
1673 if (m_fastresumeDataRejected && !m_hasMissingFiles)
1674 saveResumeData();
1678 updateStatus();
1680 if (!m_hasMissingFiles) {
1681 if ((progress() < 1.0) && (wantedSize() > 0))
1682 m_hasSeedStatus = false;
1683 else if (progress() == 1.0)
1684 m_hasSeedStatus = true;
1686 adjustActualSavePath();
1687 manageIncompleteFiles();
1690 m_session->handleTorrentChecked(this);
1693 void TorrentHandle::handleTorrentFinishedAlert(const lt::torrent_finished_alert *p)
1695 Q_UNUSED(p);
1696 qDebug("Got a torrent finished alert for \"%s\"", qUtf8Printable(name()));
1697 qDebug("Torrent has seed status: %s", m_hasSeedStatus ? "yes" : "no");
1698 m_hasMissingFiles = false;
1699 if (m_hasSeedStatus) return;
1701 updateStatus();
1702 m_hasSeedStatus = true;
1704 adjustActualSavePath();
1705 manageIncompleteFiles();
1707 const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion();
1708 if (isMoveInProgress() || (m_renameCount > 0)) {
1709 if (recheckTorrentsOnCompletion)
1710 m_moveFinishedTriggers.append([this]() { forceRecheck(); });
1711 m_moveFinishedTriggers.append([this]() { m_session->handleTorrentFinished(this); });
1713 else {
1714 if (recheckTorrentsOnCompletion && m_unchecked)
1715 forceRecheck();
1716 m_session->handleTorrentFinished(this);
1720 void TorrentHandle::handleTorrentPausedAlert(const lt::torrent_paused_alert *p)
1722 Q_UNUSED(p);
1724 if (m_startupState == Started) {
1725 if (!m_pauseWhenReady) {
1726 updateStatus();
1727 m_speedMonitor.reset();
1728 m_session->handleTorrentPaused(this);
1730 else {
1731 m_pauseWhenReady = false;
1736 void TorrentHandle::handleTorrentResumedAlert(const lt::torrent_resumed_alert *p)
1738 Q_UNUSED(p);
1740 if (m_startupState == Started)
1741 m_session->handleTorrentResumed(this);
1742 else if (m_startupState == Starting)
1743 m_startupState = Started;
1746 void TorrentHandle::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
1748 #if (LIBTORRENT_VERSION_NUM < 10200)
1749 const bool useDummyResumeData = !(p && p->resume_data);
1750 lt::entry dummyEntry;
1752 lt::entry &resumeData = useDummyResumeData ? dummyEntry : *(p->resume_data);
1753 #else
1754 const bool useDummyResumeData = !p;
1756 lt::entry resumeData = useDummyResumeData ? lt::entry() : lt::write_resume_data(p->params);
1757 #endif
1759 if (useDummyResumeData) {
1760 resumeData["qBt-magnetUri"] = toMagnetUri().toStdString();
1761 resumeData["paused"] = isPaused();
1762 resumeData["auto_managed"] = isAutoManaged();
1763 // Both firstLastPiecePriority and sequential need to be stored in the
1764 // resume data if there is no metadata, otherwise they won't be
1765 // restored if qBittorrent quits before the metadata are retrieved:
1766 resumeData["qBt-firstLastPiecePriority"] = hasFirstLastPiecePriority();
1767 resumeData["qBt-sequential"] = isSequentialDownload();
1769 else {
1770 const auto savePath = resumeData.find_key("save_path")->string();
1771 resumeData["save_path"] = Profile::instance().toPortablePath(QString::fromStdString(savePath)).toStdString();
1773 resumeData["qBt-savePath"] = m_useAutoTMM ? "" : Profile::instance().toPortablePath(m_savePath).toStdString();
1774 resumeData["qBt-ratioLimit"] = static_cast<int>(m_ratioLimit * 1000);
1775 resumeData["qBt-seedingTimeLimit"] = m_seedingTimeLimit;
1776 resumeData["qBt-category"] = m_category.toStdString();
1777 resumeData["qBt-tags"] = setToEntryList(m_tags);
1778 resumeData["qBt-name"] = m_name.toStdString();
1779 resumeData["qBt-seedStatus"] = m_hasSeedStatus;
1780 resumeData["qBt-tempPathDisabled"] = m_tempPathDisabled;
1781 resumeData["qBt-queuePosition"] = (static_cast<int>(nativeHandle().queue_position()) + 1); // qBt starts queue at 1
1782 resumeData["qBt-hasRootFolder"] = m_hasRootFolder;
1784 if (m_pauseWhenReady) {
1785 // We need to redefine these values when torrent starting/rechecking
1786 // in "paused" state since native values can be logically wrong
1787 // (torrent can be not paused and auto_managed when it is checking).
1788 resumeData["paused"] = true;
1789 resumeData["auto_managed"] = false;
1792 m_session->handleTorrentResumeDataReady(this, resumeData);
1795 void TorrentHandle::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p)
1797 // if torrent has no metadata we should save dummy fastresume data
1798 // containing Magnet URI and qBittorrent own resume data only
1799 if (p->error.value() == lt::errors::no_metadata) {
1800 handleSaveResumeDataAlert(nullptr);
1802 else {
1803 LogMsg(tr("Save resume data failed. Torrent: \"%1\", error: \"%2\"")
1804 .arg(name(), QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
1805 m_session->handleTorrentResumeDataFailed(this);
1809 void TorrentHandle::handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p)
1811 m_fastresumeDataRejected = true;
1813 if (p->error.value() == lt::errors::mismatching_file_size) {
1814 // Mismatching file size (files were probably moved)
1815 m_hasMissingFiles = true;
1816 LogMsg(tr("File sizes mismatch for torrent '%1', pausing it.").arg(name()), Log::CRITICAL);
1818 else {
1819 LogMsg(tr("Fast resume data was rejected for torrent '%1'. Reason: %2. Checking again...")
1820 .arg(name(), QString::fromStdString(p->message())), Log::WARNING);
1824 void TorrentHandle::handleFileRenamedAlert(const lt::file_renamed_alert *p)
1826 // We don't really need to call updateStatus() in this place.
1827 // All we need to do is make sure we have a valid instance of the TorrentInfo object.
1828 m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()};
1830 // remove empty leftover folders
1831 // for example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
1832 // be removed if they are empty
1833 const QString oldFilePath = m_oldPath[p->index].takeFirst();
1834 const QString newFilePath = Utils::Fs::toUniformPath(p->new_name());
1836 if (m_oldPath[p->index].isEmpty())
1837 m_oldPath.remove(p->index);
1839 QVector<QStringRef> oldPathParts = oldFilePath.splitRef('/', QString::SkipEmptyParts);
1840 oldPathParts.removeLast(); // drop file name part
1841 QVector<QStringRef> newPathParts = newFilePath.splitRef('/', QString::SkipEmptyParts);
1842 newPathParts.removeLast(); // drop file name part
1844 #if defined(Q_OS_WIN)
1845 const Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive;
1846 #else
1847 const Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive;
1848 #endif
1850 int pathIdx = 0;
1851 while ((pathIdx < oldPathParts.size()) && (pathIdx < newPathParts.size())) {
1852 if (oldPathParts[pathIdx].compare(newPathParts[pathIdx], caseSensitivity) != 0)
1853 break;
1854 ++pathIdx;
1857 for (int i = (oldPathParts.size() - 1); i >= pathIdx; --i) {
1858 QDir().rmdir(savePath() + Utils::String::join(oldPathParts, QLatin1String("/")));
1859 oldPathParts.removeLast();
1862 --m_renameCount;
1863 while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
1864 m_moveFinishedTriggers.takeFirst()();
1866 if (isPaused() && (m_renameCount == 0))
1867 saveResumeData(); // otherwise the new path will not be saved
1870 void TorrentHandle::handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p)
1872 LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
1873 .arg(name(), filePath(LTUnderlyingType<LTFileIndex> {p->index})
1874 , QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING);
1876 m_oldPath[p->index].removeFirst();
1877 if (m_oldPath[p->index].isEmpty())
1878 m_oldPath.remove(p->index);
1880 --m_renameCount;
1881 while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
1882 m_moveFinishedTriggers.takeFirst()();
1884 if (isPaused() && (m_renameCount == 0))
1885 saveResumeData(); // otherwise the new path will not be saved
1888 void TorrentHandle::handleFileCompletedAlert(const lt::file_completed_alert *p)
1890 // We don't really need to call updateStatus() in this place.
1891 // All we need to do is make sure we have a valid instance of the TorrentInfo object.
1892 m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()};
1894 qDebug("A file completed download in torrent \"%s\"", qUtf8Printable(name()));
1895 if (m_session->isAppendExtensionEnabled()) {
1896 QString name = filePath(LTUnderlyingType<LTFileIndex> {p->index});
1897 if (name.endsWith(QB_EXT)) {
1898 const QString oldName = name;
1899 name.chop(QB_EXT.size());
1900 qDebug("Renaming %s to %s", qUtf8Printable(oldName), qUtf8Printable(name));
1901 renameFile(LTUnderlyingType<LTFileIndex> {p->index}, name);
1906 void TorrentHandle::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
1908 Q_UNUSED(p);
1909 qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
1910 updateStatus();
1911 if (m_session->isAppendExtensionEnabled())
1912 manageIncompleteFiles();
1913 if (!m_hasRootFolder)
1914 m_torrentInfo.stripRootFolder();
1915 if (filesCount() == 1)
1916 m_hasRootFolder = false;
1917 m_session->handleTorrentMetadataReceived(this);
1919 if (isPaused()) {
1920 // XXX: Unfortunately libtorrent-rasterbar does not send a torrent_paused_alert
1921 // and the torrent can be paused when metadata is received
1922 m_speedMonitor.reset();
1923 m_session->handleTorrentPaused(this);
1926 // If first/last piece priority was specified when adding this torrent, we can set it
1927 // now that we have metadata:
1928 if (m_needsToSetFirstLastPiecePriority) {
1929 setFirstLastPiecePriority(true);
1930 m_needsToSetFirstLastPiecePriority = false;
1934 void TorrentHandle::handlePerformanceAlert(const lt::performance_alert *p) const
1936 LogMsg((tr("Performance alert: ") + QString::fromStdString(p->message()))
1937 , Log::INFO);
1940 void TorrentHandle::handleTempPathChanged()
1942 adjustActualSavePath();
1945 void TorrentHandle::handleCategorySavePathChanged()
1947 if (m_useAutoTMM)
1948 move_impl(m_session->categorySavePath(m_category), true);
1951 void TorrentHandle::handleAppendExtensionToggled()
1953 if (!hasMetadata()) return;
1955 manageIncompleteFiles();
1958 void TorrentHandle::handleAlert(const lt::alert *a)
1960 switch (a->type()) {
1961 case lt::file_renamed_alert::alert_type:
1962 handleFileRenamedAlert(static_cast<const lt::file_renamed_alert*>(a));
1963 break;
1964 case lt::file_rename_failed_alert::alert_type:
1965 handleFileRenameFailedAlert(static_cast<const lt::file_rename_failed_alert*>(a));
1966 break;
1967 case lt::file_completed_alert::alert_type:
1968 handleFileCompletedAlert(static_cast<const lt::file_completed_alert*>(a));
1969 break;
1970 case lt::torrent_finished_alert::alert_type:
1971 handleTorrentFinishedAlert(static_cast<const lt::torrent_finished_alert*>(a));
1972 break;
1973 case lt::save_resume_data_alert::alert_type:
1974 handleSaveResumeDataAlert(static_cast<const lt::save_resume_data_alert*>(a));
1975 break;
1976 case lt::save_resume_data_failed_alert::alert_type:
1977 handleSaveResumeDataFailedAlert(static_cast<const lt::save_resume_data_failed_alert*>(a));
1978 break;
1979 case lt::storage_moved_alert::alert_type:
1980 handleStorageMovedAlert(static_cast<const lt::storage_moved_alert*>(a));
1981 break;
1982 case lt::storage_moved_failed_alert::alert_type:
1983 handleStorageMovedFailedAlert(static_cast<const lt::storage_moved_failed_alert*>(a));
1984 break;
1985 case lt::torrent_paused_alert::alert_type:
1986 handleTorrentPausedAlert(static_cast<const lt::torrent_paused_alert*>(a));
1987 break;
1988 case lt::torrent_resumed_alert::alert_type:
1989 handleTorrentResumedAlert(static_cast<const lt::torrent_resumed_alert*>(a));
1990 break;
1991 case lt::tracker_error_alert::alert_type:
1992 handleTrackerErrorAlert(static_cast<const lt::tracker_error_alert*>(a));
1993 break;
1994 case lt::tracker_reply_alert::alert_type:
1995 handleTrackerReplyAlert(static_cast<const lt::tracker_reply_alert*>(a));
1996 break;
1997 case lt::tracker_warning_alert::alert_type:
1998 handleTrackerWarningAlert(static_cast<const lt::tracker_warning_alert*>(a));
1999 break;
2000 case lt::metadata_received_alert::alert_type:
2001 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert*>(a));
2002 break;
2003 case lt::fastresume_rejected_alert::alert_type:
2004 handleFastResumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert*>(a));
2005 break;
2006 case lt::torrent_checked_alert::alert_type:
2007 handleTorrentCheckedAlert(static_cast<const lt::torrent_checked_alert*>(a));
2008 break;
2009 case lt::performance_alert::alert_type:
2010 handlePerformanceAlert(static_cast<const lt::performance_alert*>(a));
2011 break;
2015 void TorrentHandle::manageIncompleteFiles()
2017 const bool isAppendExtensionEnabled = m_session->isAppendExtensionEnabled();
2018 const QVector<qreal> fp = filesProgress();
2019 if (fp.size() != filesCount()) {
2020 qDebug() << "skip manageIncompleteFiles because of invalid torrent meta-data or empty file-progress";
2021 return;
2024 for (int i = 0; i < filesCount(); ++i) {
2025 QString name = filePath(i);
2026 if (isAppendExtensionEnabled && (fileSize(i) > 0) && (fp[i] < 1)) {
2027 if (!name.endsWith(QB_EXT)) {
2028 const QString newName = name + QB_EXT;
2029 qDebug() << "Renaming" << name << "to" << newName;
2030 renameFile(i, newName);
2033 else {
2034 if (name.endsWith(QB_EXT)) {
2035 const QString oldName = name;
2036 name.chop(QB_EXT.size());
2037 qDebug() << "Renaming" << oldName << "to" << name;
2038 renameFile(i, name);
2044 void TorrentHandle::adjustActualSavePath()
2046 if (!isMoveInProgress())
2047 adjustActualSavePath_impl();
2048 else
2049 m_moveFinishedTriggers.append([this]() { adjustActualSavePath_impl(); });
2052 void TorrentHandle::adjustActualSavePath_impl()
2054 QString path;
2055 if (!useTempPath()) {
2056 // Disabling temp dir
2057 // Moving all torrents to their destination folder
2058 path = savePath();
2060 else {
2061 // Moving all downloading torrents to temporary folder
2062 path = m_session->torrentTempPath(info());
2063 qDebug() << "Moving torrent to its temporary folder:" << path;
2066 moveStorage(Utils::Fs::toNativePath(path), true);
2069 lt::torrent_handle TorrentHandle::nativeHandle() const
2071 return m_nativeHandle;
2074 void TorrentHandle::updateTorrentInfo()
2076 if (!hasMetadata()) return;
2078 m_torrentInfo = TorrentInfo(m_nativeStatus.torrent_file.lock());
2081 bool TorrentHandle::isMoveInProgress() const
2083 return !m_moveStorageInfo.newPath.isEmpty();
2086 bool TorrentHandle::useTempPath() const
2088 return !m_tempPathDisabled && m_session->isTempPathEnabled() && !(isSeed() || m_hasSeedStatus);
2091 void TorrentHandle::updateStatus()
2093 updateStatus(m_nativeHandle.status());
2096 void TorrentHandle::updateStatus(const lt::torrent_status &nativeStatus)
2098 m_nativeStatus = nativeStatus;
2100 updateState();
2101 updateTorrentInfo();
2103 // NOTE: Don't change the order of these conditionals!
2104 // Otherwise it will not work properly since torrent can be CheckingDownloading.
2105 if (isChecking())
2106 m_unchecked = false;
2107 else if (isDownloading())
2108 m_unchecked = true;
2110 m_speedMonitor.addSample({nativeStatus.download_payload_rate
2111 , nativeStatus.upload_payload_rate});
2114 void TorrentHandle::setRatioLimit(qreal limit)
2116 if (limit < USE_GLOBAL_RATIO)
2117 limit = NO_RATIO_LIMIT;
2118 else if (limit > MAX_RATIO)
2119 limit = MAX_RATIO;
2121 if (m_ratioLimit != limit) {
2122 m_ratioLimit = limit;
2123 m_session->handleTorrentShareLimitChanged(this);
2127 void TorrentHandle::setSeedingTimeLimit(int limit)
2129 if (limit < USE_GLOBAL_SEEDING_TIME)
2130 limit = NO_SEEDING_TIME_LIMIT;
2131 else if (limit > MAX_SEEDING_TIME)
2132 limit = MAX_SEEDING_TIME;
2134 if (m_seedingTimeLimit != limit) {
2135 m_seedingTimeLimit = limit;
2136 m_session->handleTorrentShareLimitChanged(this);
2140 void TorrentHandle::setUploadLimit(const int limit)
2142 m_nativeHandle.set_upload_limit(limit);
2145 void TorrentHandle::setDownloadLimit(const int limit)
2147 m_nativeHandle.set_download_limit(limit);
2150 void TorrentHandle::setSuperSeeding(const bool enable)
2152 #if (LIBTORRENT_VERSION_NUM < 10200)
2153 m_nativeHandle.super_seeding(enable);
2154 #else
2155 if (enable)
2156 m_nativeHandle.set_flags(lt::torrent_flags::super_seeding);
2157 else
2158 m_nativeHandle.unset_flags(lt::torrent_flags::super_seeding);
2159 #endif
2162 void TorrentHandle::flushCache()
2164 m_nativeHandle.flush_cache();
2167 QString TorrentHandle::toMagnetUri() const
2169 return QString::fromStdString(lt::make_magnet_uri(m_nativeHandle));
2172 void TorrentHandle::prioritizeFiles(const QVector<DownloadPriority> &priorities)
2174 if (!hasMetadata()) return;
2175 if (priorities.size() != filesCount()) return;
2177 // Save first/last piece first option state
2178 const bool firstLastPieceFirst = hasFirstLastPiecePriority();
2180 // Reset 'm_hasSeedStatus' if needed in order to react again to
2181 // 'torrent_finished_alert' and eg show tray notifications
2182 const QVector<qreal> progress = filesProgress();
2183 const QVector<DownloadPriority> oldPriorities = filePriorities();
2184 for (int i = 0; i < oldPriorities.size(); ++i) {
2185 if ((oldPriorities[i] == DownloadPriority::Ignored)
2186 && (priorities[i] > DownloadPriority::Ignored)
2187 && (progress[i] < 1.0)) {
2188 m_hasSeedStatus = false;
2189 break;
2193 qDebug() << Q_FUNC_INFO << "Changing files priorities...";
2194 m_nativeHandle.prioritize_files(toLTDownloadPriorities(priorities));
2196 qDebug() << Q_FUNC_INFO << "Moving unwanted files to .unwanted folder and conversely...";
2197 const QString spath = savePath(true);
2198 for (int i = 0; i < priorities.size(); ++i) {
2199 const QString filepath = filePath(i);
2200 // Move unwanted files to a .unwanted subfolder
2201 if (priorities[i] == DownloadPriority::Ignored) {
2202 const QString oldAbsPath = QDir(spath).absoluteFilePath(filepath);
2203 const QString parentAbsPath = Utils::Fs::branchPath(oldAbsPath);
2204 // Make sure the file does not already exists
2205 if (QDir(parentAbsPath).dirName() != ".unwanted") {
2206 const QString unwantedAbsPath = parentAbsPath + "/.unwanted";
2207 const QString newAbsPath = unwantedAbsPath + '/' + Utils::Fs::fileName(filepath);
2208 qDebug() << "Unwanted path is" << unwantedAbsPath;
2209 if (QFile::exists(newAbsPath)) {
2210 qWarning() << "File" << newAbsPath << "already exists at destination.";
2211 continue;
2214 const bool created = QDir().mkpath(unwantedAbsPath);
2215 qDebug() << "unwanted folder was created:" << created;
2216 #ifdef Q_OS_WIN
2217 if (created) {
2218 // Hide the folder on Windows
2219 qDebug() << "Hiding folder (Windows)";
2220 std::wstring winPath = Utils::Fs::toNativePath(unwantedAbsPath).toStdWString();
2221 DWORD dwAttrs = ::GetFileAttributesW(winPath.c_str());
2222 bool ret = ::SetFileAttributesW(winPath.c_str(), dwAttrs | FILE_ATTRIBUTE_HIDDEN);
2223 Q_ASSERT(ret != 0); Q_UNUSED(ret);
2225 #endif
2226 QString parentPath = Utils::Fs::branchPath(filepath);
2227 if (!parentPath.isEmpty() && !parentPath.endsWith('/'))
2228 parentPath += '/';
2229 renameFile(i, parentPath + ".unwanted/" + Utils::Fs::fileName(filepath));
2233 // Move wanted files back to their original folder
2234 if (priorities[i] > DownloadPriority::Ignored) {
2235 const QString parentRelPath = Utils::Fs::branchPath(filepath);
2236 if (QDir(parentRelPath).dirName() == ".unwanted") {
2237 const QString oldName = Utils::Fs::fileName(filepath);
2238 const QString newRelPath = Utils::Fs::branchPath(parentRelPath);
2239 if (newRelPath.isEmpty())
2240 renameFile(i, oldName);
2241 else
2242 renameFile(i, QDir(newRelPath).filePath(oldName));
2244 // Remove .unwanted directory if empty
2245 qDebug() << "Attempting to remove .unwanted folder at " << QDir(spath + '/' + newRelPath).absoluteFilePath(".unwanted");
2246 QDir(spath + '/' + newRelPath).rmdir(".unwanted");
2251 // Restore first/last piece first option if necessary
2252 if (firstLastPieceFirst)
2253 setFirstLastPiecePriorityImpl(true, priorities);
2256 QVector<qreal> TorrentHandle::availableFileFractions() const
2258 const int filesCount = this->filesCount();
2259 if (filesCount < 0) return {};
2261 const QVector<int> piecesAvailability = pieceAvailability();
2262 // libtorrent returns empty array for seeding only torrents
2263 if (piecesAvailability.empty()) return QVector<qreal>(filesCount, -1.);
2265 QVector<qreal> res;
2266 res.reserve(filesCount);
2267 const TorrentInfo info = this->info();
2268 for (int i = 0; i < filesCount; ++i) {
2269 const TorrentInfo::PieceRange filePieces = info.filePieces(i);
2271 int availablePieces = 0;
2272 for (int piece = filePieces.first(); piece <= filePieces.last(); ++piece) {
2273 availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
2275 res.push_back(static_cast<qreal>(availablePieces) / filePieces.size());
2277 return res;