From e93c360db66b6cbf8df37ffaa9fb966a801d2f6d Mon Sep 17 00:00:00 2001 From: Vladimir Golovnev Date: Tue, 25 Jan 2022 08:22:35 +0300 Subject: [PATCH] Store hybrid torrents using legacy filenames * Make Digest32 implicitly shared class * Store hybrid torrents using legacy filenames PR #16237. --- src/base/bittorrent/infohash.cpp | 7 ++ src/base/bittorrent/infohash.h | 6 ++ src/base/bittorrent/session.cpp | 164 ++++++++++++++++++++++--------- src/base/digest32.h | 31 +++--- 4 files changed, 152 insertions(+), 56 deletions(-) diff --git a/src/base/bittorrent/infohash.cpp b/src/base/bittorrent/infohash.cpp index 907b05fe4..35c2622da 100644 --- a/src/base/bittorrent/infohash.cpp +++ b/src/base/bittorrent/infohash.cpp @@ -36,6 +36,13 @@ BitTorrent::InfoHash::InfoHash(const WrappedType &nativeHash) { } +#ifdef QBT_USES_LIBTORRENT2 +BitTorrent::InfoHash::InfoHash(const SHA1Hash &v1, const SHA256Hash &v2) + : InfoHash {WrappedType(v1, v2)} +{ +} +#endif + bool BitTorrent::InfoHash::isValid() const { return m_valid; diff --git a/src/base/bittorrent/infohash.h b/src/base/bittorrent/infohash.h index d7848a151..dc1ac6a67 100644 --- a/src/base/bittorrent/infohash.h +++ b/src/base/bittorrent/infohash.h @@ -65,6 +65,9 @@ namespace BitTorrent InfoHash() = default; InfoHash(const WrappedType &nativeHash); +#ifdef QBT_USES_LIBTORRENT2 + InfoHash(const SHA1Hash &v1, const SHA256Hash &v2); +#endif bool isValid() const; SHA1Hash v1() const; @@ -85,3 +88,6 @@ namespace BitTorrent } Q_DECLARE_METATYPE(BitTorrent::TorrentID) +// We can declare it as Q_MOVABLE_TYPE to improve performance +// since base type uses QSharedDataPointer as the only member +Q_DECLARE_TYPEINFO(BitTorrent::TorrentID, Q_MOVABLE_TYPE); diff --git a/src/base/bittorrent/session.cpp b/src/base/bittorrent/session.cpp index d82e1964f..a1af9fd84 100644 --- a/src/base/bittorrent/session.cpp +++ b/src/base/bittorrent/session.cpp @@ -291,6 +291,20 @@ namespace return {}; } #endif + + TorrentID resumeDataID(const BitTorrent::Torrent *torrent) + { +#ifdef QBT_USES_LIBTORRENT2 + // use legacy ID to store hybrid torrent + const InfoHash infoHash = torrent->infoHash(); + const TorrentID id = ((infoHash.v1().isValid() && infoHash.v2().isValid()) + ? InfoHash(infoHash.v1(), SHA256Hash()).toTorrentID() + : torrent->id()); +#else + const TorrentID id = torrent->id(); +#endif + return id; + } } const int addTorrentParamsId = qRegisterMetaType(); @@ -1841,7 +1855,7 @@ bool Session::deleteTorrent(const TorrentID &id, const DeleteOption deleteOption } // Remove it from torrent resume directory - m_resumeDataStorage->remove(torrent->id()); + m_resumeDataStorage->remove(resumeDataID(torrent)); delete torrent; return true; @@ -4011,7 +4025,7 @@ void Session::handleTorrentResumeDataReady(TorrentImpl *const torrent, const Loa { --m_numResumeData; - m_resumeDataStorage->store(torrent->id(), data); + m_resumeDataStorage->store(resumeDataID(torrent), data); } void Session::handleTorrentTrackerReply(TorrentImpl *const torrent, const QString &trackerUrl) @@ -4346,53 +4360,115 @@ void Session::startUpTorrents() const QVector torrents = startupStorage->registeredTorrents(); int resumedTorrentsCount = 0; QVector queue; - for (const TorrentID &torrentID : torrents) +#ifdef QBT_USES_LIBTORRENT2 + const QSet indexedTorrents {torrents.cbegin(), torrents.cend()}; + QSet skippedIDs; +#endif + for (TorrentID torrentID : torrents) { +#ifdef QBT_USES_LIBTORRENT2 + if (skippedIDs.contains(torrentID)) + continue; +#endif + const std::optional loadResumeDataResult = startupStorage->load(torrentID); - if (loadResumeDataResult) - { - LoadTorrentParams resumeData = *loadResumeDataResult; - bool needStore = false; - - if (m_resumeDataStorage != startupStorage) - { - needStore = true; - if (isQueueingSystemEnabled() && !resumeData.hasSeedStatus) - queue.append(torrentID); - } - - // TODO: Remove the following upgrade code in v4.5 - // == BEGIN UPGRADE CODE == - if (m_needUpgradeDownloadPath && isDownloadPathEnabled()) - { - if (!resumeData.useAutoTMM) - { - resumeData.downloadPath = downloadPath(); - needStore = true; - } - } - // == END UPGRADE CODE == - - if (needStore) - m_resumeDataStorage->store(torrentID, resumeData); - - qDebug() << "Starting up torrent" << torrentID.toString() << "..."; - if (!loadTorrent(resumeData)) - { - LogMsg(tr("Unable to resume torrent '%1'.", "e.g: Unable to resume torrent 'hash'.") - .arg(torrentID.toString()), Log::CRITICAL); - } - - // process add torrent messages before message queue overflow - if ((resumedTorrentsCount % 100) == 0) readAlerts(); - - ++resumedTorrentsCount; - } - else + if (!loadResumeDataResult) { LogMsg(tr("Unable to resume torrent '%1'.", "e.g: Unable to resume torrent 'hash'.") .arg(torrentID.toString()), Log::CRITICAL); + continue; } + + LoadTorrentParams resumeData = *loadResumeDataResult; + bool needStore = false; + +#ifdef QBT_USES_LIBTORRENT2 + const lt::info_hash_t infoHash = (resumeData.ltAddTorrentParams.ti + ? resumeData.ltAddTorrentParams.ti->info_hashes() + : resumeData.ltAddTorrentParams.info_hashes); + if (infoHash.has_v1() && infoHash.has_v2()) + { + const auto torrentIDv1 = TorrentID::fromInfoHash(lt::info_hash_t(infoHash.v1)); + const auto torrentIDv2 = TorrentID::fromInfoHash(infoHash); + if (torrentID == torrentIDv1) + { + if (indexedTorrents.contains(torrentIDv2)) + { + // if we has no metadata trying to find it in alternative "resume data" + if (!resumeData.ltAddTorrentParams.ti) + { + const std::optional loadAltResumeDataResult = startupStorage->load(torrentIDv2); + if (loadAltResumeDataResult) + { + LoadTorrentParams altResumeData = *loadAltResumeDataResult; + resumeData.ltAddTorrentParams.ti = altResumeData.ltAddTorrentParams.ti; + } + } + + + // remove alternative "resume data" and skip the attempt to load it + m_resumeDataStorage->remove(torrentIDv2); + skippedIDs.insert(torrentIDv2); + } + } + else + { + torrentID = torrentIDv1; + needStore = true; + m_resumeDataStorage->remove(torrentIDv2); + + if (indexedTorrents.contains(torrentID)) + { + skippedIDs.insert(torrentID); + + const std::optional loadPreferredResumeDataResult = startupStorage->load(torrentID); + if (loadPreferredResumeDataResult) + { + LoadTorrentParams preferredResumeData = *loadPreferredResumeDataResult; + std::shared_ptr ti = resumeData.ltAddTorrentParams.ti; + if (!preferredResumeData.ltAddTorrentParams.ti) + preferredResumeData.ltAddTorrentParams.ti = ti; + + resumeData = preferredResumeData; + } + } + } + } +#endif + + if (m_resumeDataStorage != startupStorage) + { + needStore = true; + if (isQueueingSystemEnabled() && !resumeData.hasSeedStatus) + queue.append(torrentID); + } + + // TODO: Remove the following upgrade code in v4.5 + // == BEGIN UPGRADE CODE == + if (m_needUpgradeDownloadPath && isDownloadPathEnabled()) + { + if (!resumeData.useAutoTMM) + { + resumeData.downloadPath = downloadPath(); + needStore = true; + } + } + // == END UPGRADE CODE == + + if (needStore) + m_resumeDataStorage->store(torrentID, resumeData); + + qDebug() << "Starting up torrent" << torrentID.toString() << "..."; + if (!loadTorrent(resumeData)) + { + LogMsg(tr("Unable to resume torrent '%1'.", "e.g: Unable to resume torrent 'hash'.") + .arg(torrentID.toString()), Log::CRITICAL); + } + + // process add torrent messages before message queue overflow + if ((resumedTorrentsCount % 100) == 0) readAlerts(); + + ++resumedTorrentsCount; } if (m_resumeDataStorage != startupStorage) @@ -4610,7 +4686,7 @@ void Session::createTorrent(const lt::torrent_handle &nativeHandle) } else { - m_resumeDataStorage->store(torrent->id(), params); + m_resumeDataStorage->store(resumeDataID(torrent), params); // The following is useless for newly added magnet if (hasMetadata) diff --git a/src/base/digest32.h b/src/base/digest32.h index 2241ef691..1c100d1ee 100644 --- a/src/base/digest32.h +++ b/src/base/digest32.h @@ -32,6 +32,8 @@ #include #include +#include +#include #include template @@ -43,11 +45,11 @@ public: Digest32() = default; Digest32(const UnderlyingType &nativeDigest) - : m_valid {true} - , m_nativeDigest {nativeDigest} { + m_dataPtr->valid = true; + m_dataPtr->nativeDigest = nativeDigest; const QByteArray raw = QByteArray::fromRawData(nativeDigest.data(), length()); - m_hashString = QString::fromLatin1(raw.toHex()); + m_dataPtr->hashString = QString::fromLatin1(raw.toHex()); } static constexpr int length() @@ -57,12 +59,12 @@ public: bool isValid() const { - return m_valid; + return m_dataPtr->valid; } operator UnderlyingType() const { - return m_nativeDigest; + return m_dataPtr->nativeDigest; } static Digest32 fromString(const QString &digestString) @@ -75,22 +77,27 @@ public: return {}; Digest32 result; - result.m_valid = true; - result.m_hashString = digestString; - result.m_nativeDigest.assign(raw.constData()); + result.m_dataPtr->valid = true; + result.m_dataPtr->hashString = digestString; + result.m_dataPtr->nativeDigest.assign(raw.constData()); return result; } QString toString() const { - return m_hashString; + return m_dataPtr->hashString; } private: - bool m_valid = false; - UnderlyingType m_nativeDigest; - QString m_hashString; + struct Data : public QSharedData + { + bool valid = false; + UnderlyingType nativeDigest; + QString hashString; + }; + + QSharedDataPointer m_dataPtr {new Data}; }; template