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"
33 #include <type_traits>
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>
57 #include <QStringList>
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"
70 #include "private/ltunderlyingtype.h"
72 #include "trackerentry.h"
74 const QString QB_EXT
{QStringLiteral(".!qB")};
76 using namespace BitTorrent
;
78 #if (LIBTORRENT_VERSION_NUM >= 10200)
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
);
94 #if (LIBTORRENT_VERSION_NUM < 10200)
95 using LTDownloadPriority
= int;
96 using LTPieceIndex
= int;
97 using LTQueuePosition
= int;
99 using LTDownloadPriority
= lt::download_priority_t
;
100 using LTPieceIndex
= lt::piece_index_t
;
101 using LTQueuePosition
= lt::queue_position_t
;
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
));
116 using ListType
= lt::entry::list_type
;
118 ListType
setToEntryList(const QSet
<QString
> &input
)
121 for (const QString
&setValue
: input
)
122 entryList
.emplace_back(setValue
.toStdString());
129 CreateTorrentParams::CreateTorrentParams()
131 , disableTempPath(false)
133 , firstLastPiecePriority(false)
134 , hasSeedStatus(false)
135 , skipChecking(false)
136 , hasRootFolder(true)
141 , ratioLimit(TorrentHandle::USE_GLOBAL_RATIO
)
142 , seedingTimeLimit(TorrentHandle::USE_GLOBAL_SEEDING_TIME
)
146 CreateTorrentParams::CreateTorrentParams(const AddTorrentParams
¶ms
)
149 , category(params
.category
)
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
);
175 else if (savePath
.trimmed().isEmpty())
176 savePath
= Session::instance()->defaultSavePath();
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
¶ms
)
194 , m_nativeHandle(nativeHandle
)
195 , m_state(TorrentState::Unknown
)
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
)
214 m_savePath
= Utils::Fs::toNativePath(m_session
->categorySavePath(m_category
));
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
);
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
264 QString
TorrentHandle::name() const
266 QString name
= m_name
;
268 name
= QString::fromStdString(m_nativeStatus
.name
);
270 if (name
.isEmpty() && hasMetadata())
271 name
= QString::fromStdString(m_torrentInfo
.nativeInfo()->orig_files().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
338 return Utils::Fs::toUniformPath(nativeActualSavePath());
340 return Utils::Fs::toUniformPath(m_savePath
);
343 QString
TorrentHandle::rootPath(bool actual
) const
345 if ((filesCount() > 1) && !hasRootFolder())
348 const QString firstFilePath
= filePath(0);
349 const int slashIndex
= firstFilePath
.indexOf('/');
351 return QDir(savePath(actual
)).absoluteFilePath(firstFilePath
.left(slashIndex
));
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
);
363 return savePath(actual
);
366 bool TorrentHandle::isAutoTMMEnabled() const
371 void TorrentHandle::setAutoTMMEnabled(bool enabled
)
373 if (m_useAutoTMM
== enabled
) return;
375 m_useAutoTMM
= enabled
;
376 m_session
->handleTorrentSavingModeChanged(this);
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
;
397 return bool {m_nativeStatus
.flags
& lt::torrent_flags::auto_managed
};
401 void TorrentHandle::setAutoManaged(const bool enable
)
403 #if (LIBTORRENT_VERSION_NUM < 10200)
404 m_nativeHandle
.auto_managed(enable
);
407 m_nativeHandle
.set_flags(lt::torrent_flags::auto_managed
);
409 m_nativeHandle
.unset_flags(lt::torrent_flags::auto_managed
);
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
)
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);
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()));
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());
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());
531 bool TorrentHandle::connectPeer(const PeerAddress
&peerAddress
)
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
);
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
571 if (!m_nativeStatus
.total_wanted
)
574 if (m_nativeStatus
.total_wanted_done
== m_nativeStatus
.total_wanted
)
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
));
582 return m_nativeStatus
.progress
;
585 QString
TorrentHandle::category() const
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
+ '/'))
603 QSet
<QString
> TorrentHandle::tags() const
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
))
619 if (!m_session
->hasTag(tag
))
620 if (!m_session
->addTag(tag
))
623 m_session
->handleTorrentTagAdded(this, tag
);
629 bool TorrentHandle::removeTag(const QString
&tag
)
631 if (m_tags
.remove(tag
)) {
632 m_session
->handleTorrentTagRemoved(this, tag
);
638 void TorrentHandle::removeAllTags()
640 for (const QString
&tag
: asConst(tags()))
644 QDateTime
TorrentHandle::addedTime() const
646 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.added_time
);
649 qreal
TorrentHandle::ratioLimit() const
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));
683 for (int i
= 0; i
< filesCount(); ++i
)
684 res
<< Utils::Fs::expandPathAbs(saveDir
.absoluteFilePath(filePath(i
)));
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();
696 const std::vector
<LTDownloadPriority
> fp
= m_nativeHandle
.get_file_priorities();
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"))
711 QVector
<DownloadPriority
> TorrentHandle::filePriorities() const
713 #if (LIBTORRENT_VERSION_NUM < 10200)
714 const std::vector
<LTDownloadPriority
> fp
= m_nativeHandle
.file_priorities();
716 const std::vector
<LTDownloadPriority
> fp
= m_nativeHandle
.get_file_priorities();
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
});
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());
737 return ((m_nativeStatus
.flags
& lt::torrent_flags::paused
)
738 && !isAutoManaged());
742 bool TorrentHandle::isResumed() const
747 bool TorrentHandle::isQueued() const
749 #if (LIBTORRENT_VERSION_NUM < 10200)
750 return (m_nativeStatus
.paused
&& isAutoManaged());
752 return ((m_nativeStatus
.flags
& lt::torrent_flags::paused
)
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
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
821 //result = m_nativeHandle.is_seed());
823 // May suffer from approximation problems
824 //return (progress() == 1.);
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());
835 return (!(m_nativeStatus
.flags
& lt::torrent_flags::paused
)
836 && !isAutoManaged());
840 bool TorrentHandle::isSequentialDownload() const
842 #if (LIBTORRENT_VERSION_NUM < 10200)
843 return m_nativeStatus
.sequential_download
;
845 return bool {m_nativeStatus
.flags
& lt::torrent_flags::sequential_download
};
849 bool TorrentHandle::hasFirstLastPiecePriority() const
852 return m_needsToSetFirstLastPiecePriority
;
854 #if (LIBTORRENT_VERSION_NUM < 10200)
855 const std::vector
<LTDownloadPriority
> filePriorities
= nativeHandle().file_priorities();
857 const std::vector
<LTDownloadPriority
> filePriorities
= nativeHandle().get_file_priorities();
859 for (int i
= 0; i
< static_cast<int>(filePriorities
.size()); ++i
) {
860 if (filePriorities
[i
] <= LTDownloadPriority
{0})
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}));
872 TorrentState
TorrentHandle::state() const
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
;
889 m_state
= TorrentState::Error
;
891 m_state
= isSeed() ? TorrentState::PausedUploading
: TorrentState::PausedDownloading
;
894 if (m_session
->isQueueingSystemEnabled() && isQueued() && !isChecking()) {
895 m_state
= isSeed() ? TorrentState::QueuedUploading
: TorrentState::QueuedDownloading
;
898 switch (m_nativeStatus
.state
) {
899 case lt::torrent_status::finished
:
900 case lt::torrent_status::seeding
:
902 m_state
= TorrentState::ForcedUploading
;
904 m_state
= m_nativeStatus
.upload_payload_rate
> 0 ? TorrentState::Uploading
: TorrentState::StalledUploading
;
906 case lt::torrent_status::allocating
:
907 m_state
= TorrentState::Allocating
;
909 case lt::torrent_status::checking_files
:
910 m_state
= m_hasSeedStatus
? TorrentState::CheckingUploading
: TorrentState::CheckingDownloading
;
912 case lt::torrent_status::downloading_metadata
:
913 m_state
= TorrentState::DownloadingMetadata
;
915 case lt::torrent_status::downloading
:
917 m_state
= TorrentState::ForcedDownloading
;
919 m_state
= m_nativeStatus
.download_payload_rate
> 0 ? TorrentState::Downloading
: TorrentState::StalledDownloading
;
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
);
944 return ((m_nativeStatus
.flags
& lt::torrent_flags::paused
)
945 && m_nativeStatus
.errc
);
949 bool TorrentHandle::hasFilteredPieces() const
951 #if (LIBTORRENT_VERSION_NUM < 10200)
952 const std::vector
<LTDownloadPriority
> pp
= m_nativeHandle
.piece_priorities();
954 const std::vector
<LTDownloadPriority
> pp
= m_nativeHandle
.get_piece_priorities();
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
;
989 return lt::total_seconds(m_nativeStatus
.active_duration
);
993 qlonglong
TorrentHandle::finishedTime() const
995 #if (LIBTORRENT_VERSION_NUM < 10200)
996 return m_nativeStatus
.finished_time
;
998 return lt::total_seconds(m_nativeStatus
.finished_duration
);
1002 qlonglong
TorrentHandle::seedingTime() const
1004 #if (LIBTORRENT_VERSION_NUM < 10200)
1005 return m_nativeStatus
.seeding_time
;
1007 return lt::total_seconds(m_nativeStatus
.seeding_duration
);
1011 qulonglong
TorrentHandle::eta() const
1013 if (isPaused()) return MAX_ETA
;
1015 const SpeedSampleAvg speedAverage
= m_speedMonitor
.average();
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();
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)
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
))
1062 result
<< (fp
[i
] / static_cast<qreal
>(size
));
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
);
1119 QDateTime
TorrentHandle::completedTime() const
1121 if (m_nativeStatus
.completed_time
> 0)
1122 return QDateTime::fromSecsSinceEpoch(m_nativeStatus
.completed_time
);
1127 qlonglong
TorrentHandle::timeSinceUpload() const
1129 #if (LIBTORRENT_VERSION_NUM < 10200)
1130 return m_nativeStatus
.time_since_upload
;
1132 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus
.last_upload
);
1136 qlonglong
TorrentHandle::timeSinceDownload() const
1138 #if (LIBTORRENT_VERSION_NUM < 10200)
1139 return m_nativeStatus
.time_since_download
;
1141 return lt::total_seconds(lt::clock_type::now() - m_nativeStatus
.last_download
);
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
;
1169 return bool {m_nativeStatus
.flags
& lt::torrent_flags::super_seeding
};
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
);
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
}));
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
);
1207 result
.setBit(LTUnderlyingType
<LTPieceIndex
> {info
.piece_index
});
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
;
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
) {
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
))
1305 const QString oldCategory
= m_category
;
1306 m_category
= category
;
1307 m_session
->handleTorrentCategoryChanged(this, oldCategory
);
1310 if (!m_session
->isDisableAutoTMMWhenCategoryChanged())
1311 move_impl(m_session
->categorySavePath(m_category
), true);
1313 setAutoTMMEnabled(false);
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());
1329 path
= m_session
->defaultSavePath();
1330 if (!path
.endsWith('/'))
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
);
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;
1369 #if (LIBTORRENT_VERSION_NUM < 10200)
1370 m_nativeHandle
.stop_when_ready(true);
1372 m_nativeHandle
.set_flags(lt::torrent_flags::stop_when_ready
);
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
1386 m_nativeHandle
.set_flags(lt::torrent_flags::sequential_download
);
1387 m_nativeStatus
.flags
|= lt::torrent_flags::sequential_download
; // prevent return cached value
1390 m_nativeHandle
.unset_flags(lt::torrent_flags::sequential_download
);
1391 m_nativeStatus
.flags
&= ~lt::torrent_flags::sequential_download
; // prevent return cached value
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
;
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();
1422 const std::vector
<LTDownloadPriority
> filePriorities
= !updatedFilePrio
.isEmpty() ? toLTDownloadPriorities(updatedFilePrio
)
1423 : nativeHandle().get_file_priorities();
1424 std::vector
<LTDownloadPriority
> piecePriorities
= nativeHandle().get_piece_priorities();
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})
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()));
1453 void TorrentHandle::toggleFirstLastPiecePriority()
1455 setFirstLastPiecePriority(!hasFirstLastPiecePriority());
1458 void TorrentHandle::pause()
1460 if (m_startupState
!= Started
) return;
1461 if (m_pauseWhenReady
) return;
1463 m_pauseWhenReady
= true;
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
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
)
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
;
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
));
1518 m_nativeHandle
.move_storage(newPath
.toUtf8().constData()
1519 , (overwrite
? lt::move_flags_t::always_replace_files
: lt::move_flags_t::dont_replace
));
1521 m_moveStorageInfo
.oldPath
= oldPath
;
1522 m_moveStorageInfo
.newPath
= newPath
;
1527 void TorrentHandle::renameFile(const int index
, const QString
&name
)
1529 m_oldPath
[LTFileIndex
{index
}].push_back(filePath(index
));
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);
1540 const lt::create_torrent torrentCreator
= lt::create_torrent(*(m_torrentInfo
.nativeInfo()));
1542 const lt::entry torrentEntry
= torrentCreator
.generate();
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());
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.";
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.";
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();
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.";
1606 LogMsg(tr("Could not move torrent: '%1'. Reason: %2")
1607 .arg(name(), QString::fromStdString(p
->message())), Log::CRITICAL
);
1609 m_moveStorageInfo
.newPath
.clear();
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
)
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
);
1666 // Torrent that has missing files is paused.
1667 m_startupState
= Started
;
1671 m_startupState
= Started
;
1672 m_pauseWhenReady
= false;
1673 if (m_fastresumeDataRejected
&& !m_hasMissingFiles
)
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
)
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;
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); });
1714 if (recheckTorrentsOnCompletion
&& m_unchecked
)
1716 m_session
->handleTorrentFinished(this);
1720 void TorrentHandle::handleTorrentPausedAlert(const lt::torrent_paused_alert
*p
)
1724 if (m_startupState
== Started
) {
1725 if (!m_pauseWhenReady
) {
1727 m_speedMonitor
.reset();
1728 m_session
->handleTorrentPaused(this);
1731 m_pauseWhenReady
= false;
1736 void TorrentHandle::handleTorrentResumedAlert(const lt::torrent_resumed_alert
*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
);
1754 const bool useDummyResumeData
= !p
;
1756 lt::entry resumeData
= useDummyResumeData
? lt::entry() : lt::write_resume_data(p
->params
);
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();
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);
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
);
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
;
1847 const Qt::CaseSensitivity caseSensitivity
= Qt::CaseSensitive
;
1851 while ((pathIdx
< oldPathParts
.size()) && (pathIdx
< newPathParts
.size())) {
1852 if (oldPathParts
[pathIdx
].compare(newPathParts
[pathIdx
], caseSensitivity
) != 0)
1857 for (int i
= (oldPathParts
.size() - 1); i
>= pathIdx
; --i
) {
1858 QDir().rmdir(savePath() + Utils::String::join(oldPathParts
, QLatin1String("/")));
1859 oldPathParts
.removeLast();
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
);
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
)
1909 qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
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);
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()))
1940 void TorrentHandle::handleTempPathChanged()
1942 adjustActualSavePath();
1945 void TorrentHandle::handleCategorySavePathChanged()
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
));
1964 case lt::file_rename_failed_alert::alert_type
:
1965 handleFileRenameFailedAlert(static_cast<const lt::file_rename_failed_alert
*>(a
));
1967 case lt::file_completed_alert::alert_type
:
1968 handleFileCompletedAlert(static_cast<const lt::file_completed_alert
*>(a
));
1970 case lt::torrent_finished_alert::alert_type
:
1971 handleTorrentFinishedAlert(static_cast<const lt::torrent_finished_alert
*>(a
));
1973 case lt::save_resume_data_alert::alert_type
:
1974 handleSaveResumeDataAlert(static_cast<const lt::save_resume_data_alert
*>(a
));
1976 case lt::save_resume_data_failed_alert::alert_type
:
1977 handleSaveResumeDataFailedAlert(static_cast<const lt::save_resume_data_failed_alert
*>(a
));
1979 case lt::storage_moved_alert::alert_type
:
1980 handleStorageMovedAlert(static_cast<const lt::storage_moved_alert
*>(a
));
1982 case lt::storage_moved_failed_alert::alert_type
:
1983 handleStorageMovedFailedAlert(static_cast<const lt::storage_moved_failed_alert
*>(a
));
1985 case lt::torrent_paused_alert::alert_type
:
1986 handleTorrentPausedAlert(static_cast<const lt::torrent_paused_alert
*>(a
));
1988 case lt::torrent_resumed_alert::alert_type
:
1989 handleTorrentResumedAlert(static_cast<const lt::torrent_resumed_alert
*>(a
));
1991 case lt::tracker_error_alert::alert_type
:
1992 handleTrackerErrorAlert(static_cast<const lt::tracker_error_alert
*>(a
));
1994 case lt::tracker_reply_alert::alert_type
:
1995 handleTrackerReplyAlert(static_cast<const lt::tracker_reply_alert
*>(a
));
1997 case lt::tracker_warning_alert::alert_type
:
1998 handleTrackerWarningAlert(static_cast<const lt::tracker_warning_alert
*>(a
));
2000 case lt::metadata_received_alert::alert_type
:
2001 handleMetadataReceivedAlert(static_cast<const lt::metadata_received_alert
*>(a
));
2003 case lt::fastresume_rejected_alert::alert_type
:
2004 handleFastResumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert
*>(a
));
2006 case lt::torrent_checked_alert::alert_type
:
2007 handleTorrentCheckedAlert(static_cast<const lt::torrent_checked_alert
*>(a
));
2009 case lt::performance_alert::alert_type
:
2010 handlePerformanceAlert(static_cast<const lt::performance_alert
*>(a
));
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";
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
);
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();
2049 m_moveFinishedTriggers
.append([this]() { adjustActualSavePath_impl(); });
2052 void TorrentHandle::adjustActualSavePath_impl()
2055 if (!useTempPath()) {
2056 // Disabling temp dir
2057 // Moving all torrents to their destination folder
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
;
2101 updateTorrentInfo();
2103 // NOTE: Don't change the order of these conditionals!
2104 // Otherwise it will not work properly since torrent can be CheckingDownloading.
2106 m_unchecked
= false;
2107 else if (isDownloading())
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
)
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
);
2156 m_nativeHandle
.set_flags(lt::torrent_flags::super_seeding
);
2158 m_nativeHandle
.unset_flags(lt::torrent_flags::super_seeding
);
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;
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.";
2214 const bool created
= QDir().mkpath(unwantedAbsPath
);
2215 qDebug() << "unwanted folder was created:" << 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
);
2226 QString parentPath
= Utils::Fs::branchPath(filepath
);
2227 if (!parentPath
.isEmpty() && !parentPath
.endsWith('/'))
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
);
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.);
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());