2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * In addition, as a special exception, the copyright holders give permission to
20 * link this program with the OpenSSL project's "OpenSSL" library (or with
21 * modified versions of it that use the same license as the "OpenSSL" library),
22 * and distribute the linked executables. You must obey the GNU General Public
23 * License in all respects for all of the code used other than "OpenSSL". If you
24 * modify file(s), you may extend this exception to your version of the file(s),
25 * but you are not obligated to do so. If you do not wish to do so, delete this
26 * exception statement from your version.
29 #include "torrentinfo.h"
31 #include <libtorrent/bencode.hpp>
32 #include <libtorrent/create_torrent.hpp>
33 #include <libtorrent/error_code.hpp>
40 #include <QStringList>
44 #include "base/exceptions.h"
45 #include "base/global.h"
46 #include "base/utils/fs.h"
47 #include "base/utils/io.h"
48 #include "base/utils/misc.h"
50 #include "trackerentry.h"
52 using namespace BitTorrent
;
56 QString
getRootFolder(const QStringList
&filePaths
)
59 for (const QString
&filePath
: filePaths
)
61 if (QDir::isAbsolutePath(filePath
)) continue;
63 const auto filePathElements
= QStringView(filePath
).split(u
'/');
64 // if at least one file has no root folder, no common root folder exists
65 if (filePathElements
.count() <= 1) return {};
67 if (rootFolder
.isEmpty())
68 rootFolder
= filePathElements
.at(0).toString();
69 else if (rootFolder
!= filePathElements
.at(0))
77 const int torrentInfoId
= qRegisterMetaType
<TorrentInfo
>();
79 TorrentInfo::TorrentInfo(std::shared_ptr
<const lt::torrent_info
> nativeInfo
)
81 m_nativeInfo
= std::const_pointer_cast
<lt::torrent_info
>(nativeInfo
);
84 TorrentInfo::TorrentInfo(const TorrentInfo
&other
)
85 : m_nativeInfo(other
.m_nativeInfo
)
89 TorrentInfo
&TorrentInfo::operator=(const TorrentInfo
&other
)
91 m_nativeInfo
= other
.m_nativeInfo
;
95 TorrentInfo
TorrentInfo::load(const QByteArray
&data
, QString
*error
) noexcept
97 // 2-step construction to overcome default limits of `depth_limit` & `token_limit` which are
98 // used in `torrent_info()` constructor
99 const int depthLimit
= 100;
100 const int tokenLimit
= 10000000;
103 const lt::bdecode_node node
= lt::bdecode(data
, ec
104 , nullptr, depthLimit
, tokenLimit
);
108 *error
= QString::fromStdString(ec
.message());
109 return TorrentInfo();
112 TorrentInfo info
{std::shared_ptr
<lt::torrent_info
>(new lt::torrent_info(node
, ec
))};
116 *error
= QString::fromStdString(ec
.message());
117 return TorrentInfo();
123 TorrentInfo
TorrentInfo::loadFromFile(const QString
&path
, QString
*error
) noexcept
129 if (!file
.open(QIODevice::ReadOnly
))
132 *error
= file
.errorString();
133 return TorrentInfo();
136 if (file
.size() > MAX_TORRENT_SIZE
)
139 *error
= tr("File size exceeds max limit %1").arg(Utils::Misc::friendlyUnit(MAX_TORRENT_SIZE
));
140 return TorrentInfo();
146 data
= file
.readAll();
148 catch (const std::bad_alloc
&e
)
151 *error
= tr("Torrent file read error: %1").arg(e
.what());
152 return TorrentInfo();
154 if (data
.size() != file
.size())
157 *error
= tr("Torrent file read error: size mismatch");
158 return TorrentInfo();
163 return load(data
, error
);
166 void TorrentInfo::saveToFile(const QString
&path
) const
169 throw RuntimeError
{tr("Invalid metadata")};
173 const auto torrentCreator
= lt::create_torrent(*nativeInfo());
174 const lt::entry torrentEntry
= torrentCreator
.generate();
176 QFile torrentFile
{path
};
177 if (!torrentFile
.open(QIODevice::WriteOnly
))
178 throw RuntimeError(torrentFile
.errorString());
180 lt::bencode(Utils::IO::FileDeviceOutputIterator
{torrentFile
}, torrentEntry
);
181 if (torrentFile
.error() != QFileDevice::NoError
)
182 throw RuntimeError(torrentFile
.errorString());
184 catch (const lt::system_error
&err
)
186 throw RuntimeError(QString::fromLocal8Bit(err
.what()));
190 bool TorrentInfo::isValid() const
192 return (m_nativeInfo
&& m_nativeInfo
->is_valid() && (m_nativeInfo
->num_files() > 0));
195 InfoHash
TorrentInfo::infoHash() const
197 if (!isValid()) return {};
199 #ifdef QBT_USES_LIBTORRENT2
200 return m_nativeInfo
->info_hashes();
202 return m_nativeInfo
->info_hash();
206 QString
TorrentInfo::name() const
208 if (!isValid()) return {};
209 return QString::fromStdString(m_nativeInfo
->orig_files().name());
212 QDateTime
TorrentInfo::creationDate() const
214 if (!isValid()) return {};
216 const std::time_t date
= m_nativeInfo
->creation_date();
217 return ((date
!= 0) ? QDateTime::fromSecsSinceEpoch(date
) : QDateTime());
220 QString
TorrentInfo::creator() const
222 if (!isValid()) return {};
223 return QString::fromStdString(m_nativeInfo
->creator());
226 QString
TorrentInfo::comment() const
228 if (!isValid()) return {};
229 return QString::fromStdString(m_nativeInfo
->comment());
232 bool TorrentInfo::isPrivate() const
234 if (!isValid()) return false;
235 return m_nativeInfo
->priv();
238 qlonglong
TorrentInfo::totalSize() const
240 if (!isValid()) return -1;
241 return m_nativeInfo
->total_size();
244 int TorrentInfo::filesCount() const
246 if (!isValid()) return -1;
247 return m_nativeInfo
->num_files();
250 int TorrentInfo::pieceLength() const
252 if (!isValid()) return -1;
253 return m_nativeInfo
->piece_length();
256 int TorrentInfo::pieceLength(const int index
) const
258 if (!isValid()) return -1;
259 return m_nativeInfo
->piece_size(lt::piece_index_t
{index
});
262 int TorrentInfo::piecesCount() const
264 if (!isValid()) return -1;
265 return m_nativeInfo
->num_pieces();
268 QString
TorrentInfo::filePath(const int index
) const
270 if (!isValid()) return {};
271 return Utils::Fs::toUniformPath(
272 QString::fromStdString(m_nativeInfo
->files().file_path(lt::file_index_t
{index
})));
275 QStringList
TorrentInfo::filePaths() const
278 for (int i
= 0; i
< filesCount(); ++i
)
284 QString
TorrentInfo::fileName(const int index
) const
286 return Utils::Fs::fileName(filePath(index
));
289 QString
TorrentInfo::origFilePath(const int index
) const
291 if (!isValid()) return {};
292 return Utils::Fs::toUniformPath(
293 QString::fromStdString(m_nativeInfo
->orig_files().file_path(lt::file_index_t
{index
})));
296 qlonglong
TorrentInfo::fileSize(const int index
) const
298 if (!isValid()) return -1;
299 return m_nativeInfo
->files().file_size(lt::file_index_t
{index
});
302 qlonglong
TorrentInfo::fileOffset(const int index
) const
304 if (!isValid()) return -1;
305 return m_nativeInfo
->files().file_offset(lt::file_index_t
{index
});
308 QVector
<TrackerEntry
> TorrentInfo::trackers() const
310 if (!isValid()) return {};
312 const std::vector
<lt::announce_entry
> trackers
= m_nativeInfo
->trackers();
314 QVector
<TrackerEntry
> ret
;
315 ret
.reserve(static_cast<decltype(ret
)::size_type
>(trackers
.size()));
317 for (const lt::announce_entry
&tracker
: trackers
)
318 ret
.append({QString::fromStdString(tracker
.url
)});
323 QVector
<QUrl
> TorrentInfo::urlSeeds() const
325 if (!isValid()) return {};
327 const std::vector
<lt::web_seed_entry
> &nativeWebSeeds
= m_nativeInfo
->web_seeds();
329 QVector
<QUrl
> urlSeeds
;
330 urlSeeds
.reserve(static_cast<decltype(urlSeeds
)::size_type
>(nativeWebSeeds
.size()));
332 for (const lt::web_seed_entry
&webSeed
: nativeWebSeeds
)
334 if (webSeed
.type
== lt::web_seed_entry::url_seed
)
335 urlSeeds
.append(QUrl(webSeed
.url
.c_str()));
341 QByteArray
TorrentInfo::metadata() const
343 if (!isValid()) return {};
344 #ifdef QBT_USES_LIBTORRENT2
345 const lt::span
<const char> infoSection
{m_nativeInfo
->info_section()};
346 return {infoSection
.data(), static_cast<int>(infoSection
.size())};
348 return {m_nativeInfo
->metadata().get(), m_nativeInfo
->metadata_size()};
352 QStringList
TorrentInfo::filesForPiece(const int pieceIndex
) const
354 // no checks here because fileIndicesForPiece() will return an empty list
355 const QVector
<int> fileIndices
= fileIndicesForPiece(pieceIndex
);
358 res
.reserve(fileIndices
.size());
359 std::transform(fileIndices
.begin(), fileIndices
.end(), std::back_inserter(res
),
360 [this](int i
) { return filePath(i
); });
365 QVector
<int> TorrentInfo::fileIndicesForPiece(const int pieceIndex
) const
367 if (!isValid() || (pieceIndex
< 0) || (pieceIndex
>= piecesCount()))
370 const std::vector
<lt::file_slice
> files
= nativeInfo()->map_block(
371 lt::piece_index_t
{pieceIndex
}, 0, nativeInfo()->piece_size(lt::piece_index_t
{pieceIndex
}));
373 res
.reserve(static_cast<decltype(res
)::size_type
>(files
.size()));
374 std::transform(files
.begin(), files
.end(), std::back_inserter(res
),
375 [](const lt::file_slice
&s
) { return static_cast<int>(s
.file_index
); });
380 QVector
<QByteArray
> TorrentInfo::pieceHashes() const
385 const int count
= piecesCount();
386 QVector
<QByteArray
> hashes
;
387 hashes
.reserve(count
);
389 for (int i
= 0; i
< count
; ++i
)
390 hashes
+= {m_nativeInfo
->hash_for_piece_ptr(lt::piece_index_t
{i
}), SHA1Hash::length()};
395 TorrentInfo::PieceRange
TorrentInfo::filePieces(const QString
&file
) const
397 if (!isValid()) // if we do not check here the debug message will be printed, which would be not correct
400 const int index
= fileIndex(file
);
403 qDebug() << "Filename" << file
<< "was not found in torrent" << name();
406 return filePieces(index
);
409 TorrentInfo::PieceRange
TorrentInfo::filePieces(const int fileIndex
) const
414 if ((fileIndex
< 0) || (fileIndex
>= filesCount()))
416 qDebug() << "File index (" << fileIndex
<< ") is out of range for torrent" << name();
420 const lt::file_storage
&files
= nativeInfo()->files();
421 const auto fileSize
= files
.file_size(lt::file_index_t
{fileIndex
});
422 const auto fileOffset
= files
.file_offset(lt::file_index_t
{fileIndex
});
424 const int beginIdx
= (fileOffset
/ pieceLength());
425 const int endIdx
= ((fileOffset
+ fileSize
- 1) / pieceLength());
428 return {beginIdx
, 0};
429 return makeInterval(beginIdx
, endIdx
);
432 void TorrentInfo::renameFile(const int index
, const QString
&newPath
)
434 if (!isValid()) return;
435 nativeInfo()->rename_file(lt::file_index_t
{index
}, Utils::Fs::toNativePath(newPath
).toStdString());
438 int TorrentInfo::fileIndex(const QString
&fileName
) const
440 // the check whether the object is valid is not needed here
441 // because if filesCount() returns -1 the loop exits immediately
442 for (int i
= 0; i
< filesCount(); ++i
)
443 if (fileName
== filePath(i
))
449 QString
TorrentInfo::rootFolder() const
451 return getRootFolder(filePaths());
454 bool TorrentInfo::hasRootFolder() const
456 return !rootFolder().isEmpty();
459 void TorrentInfo::setContentLayout(const TorrentContentLayout layout
)
463 case TorrentContentLayout::Original
:
464 setContentLayout(defaultContentLayout());
466 case TorrentContentLayout::Subfolder
:
467 if (rootFolder().isEmpty())
470 case TorrentContentLayout::NoSubfolder
:
471 if (!rootFolder().isEmpty())
477 void TorrentInfo::stripRootFolder()
479 lt::file_storage files
= m_nativeInfo
->files();
481 // Solution for case of renamed root folder
482 const QString path
= filePath(0);
483 const std::string newName
= path
.left(path
.indexOf('/')).toStdString();
484 if (files
.name() != newName
)
486 files
.set_name(newName
);
487 for (int i
= 0; i
< files
.num_files(); ++i
)
488 files
.rename_file(lt::file_index_t
{i
}, files
.file_path(lt::file_index_t
{i
}));
492 m_nativeInfo
->remap_files(files
);
495 void TorrentInfo::addRootFolder()
497 const QString originalName
= name();
498 Q_ASSERT(!originalName
.isEmpty());
500 const QString extension
= Utils::Fs::fileExtension(originalName
);
501 const QString rootFolder
= extension
.isEmpty()
503 : originalName
.chopped(extension
.size() + 1);
504 const std::string rootPrefix
= Utils::Fs::toNativePath(rootFolder
+ QLatin1Char
{'/'}).toStdString();
505 lt::file_storage files
= m_nativeInfo
->files();
506 files
.set_name(rootFolder
.toStdString());
507 for (int i
= 0; i
< files
.num_files(); ++i
)
508 files
.rename_file(lt::file_index_t
{i
}, rootPrefix
+ files
.file_path(lt::file_index_t
{i
}));
509 m_nativeInfo
->remap_files(files
);
512 TorrentContentLayout
TorrentInfo::defaultContentLayout() const
514 QStringList origFilePaths
;
515 origFilePaths
.reserve(filesCount());
516 for (int i
= 0; i
< filesCount(); ++i
)
517 origFilePaths
<< origFilePath(i
);
519 const QString origRootFolder
= getRootFolder(origFilePaths
);
520 return (origRootFolder
.isEmpty()
521 ? TorrentContentLayout::NoSubfolder
522 : TorrentContentLayout::Subfolder
);
525 std::shared_ptr
<lt::torrent_info
> TorrentInfo::nativeInfo() const