2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2024 Radu Carpa <radu.carpa@cern.ch>
5 * Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 * In addition, as a special exception, the copyright holders give permission to
22 * link this program with the OpenSSL project's "OpenSSL" library (or with
23 * modified versions of it that use the same license as the "OpenSSL" library),
24 * and distribute the linked executables. You must obey the GNU General Public
25 * License in all respects for all of the code used other than "OpenSSL". If you
26 * modify file(s), you may extend this exception to your version of the file(s),
27 * but you are not obligated to do so. If you do not wish to do so, delete this
28 * exception statement from your version.
31 #include "torrentcreator.h"
35 #include <libtorrent/create_torrent.hpp>
36 #include <libtorrent/file_storage.hpp>
37 #include <libtorrent/torrent_info.hpp>
39 #include <QtSystemDetection>
40 #include <QDirIterator>
44 #include "base/exceptions.h"
45 #include "base/global.h"
46 #include "base/utils/compare.h"
47 #include "base/utils/io.h"
48 #include "base/version.h"
49 #include "lttypecast.h"
53 // do not include files and folders whose
54 // name starts with a .
55 bool fileFilter(const std::string
&f
)
57 return !Path(f
).filename().startsWith(u
'.');
60 #ifdef QBT_USES_LIBTORRENT2
61 lt::create_flags_t
toNativeTorrentFormatFlag(const BitTorrent::TorrentFormat torrentFormat
)
63 switch (torrentFormat
)
65 case BitTorrent::TorrentFormat::V1
:
66 return lt::create_torrent::v1_only
;
67 case BitTorrent::TorrentFormat::Hybrid
:
69 case BitTorrent::TorrentFormat::V2
:
70 return lt::create_torrent::v2_only
;
77 using namespace BitTorrent
;
79 TorrentCreator::TorrentCreator(const TorrentCreatorParams
¶ms
, QObject
*parent
)
85 void TorrentCreator::sendProgressSignal(int currentPieceIdx
, int totalPieces
)
87 emit
progressUpdated(static_cast<int>((currentPieceIdx
* 100.) / totalPieces
));
90 void TorrentCreator::checkInterruptionRequested() const
92 if (isInterruptionRequested())
93 throw RuntimeError(tr("Operation aborted"));
96 void TorrentCreator::requestInterruption()
98 m_interruptionRequested
.store(true, std::memory_order_relaxed
);
101 bool TorrentCreator::isInterruptionRequested() const
103 return m_interruptionRequested
.load(std::memory_order_relaxed
);
106 void TorrentCreator::run()
109 emit
progressUpdated(0);
113 const Path parentPath
= m_params
.sourcePath
.parentPath();
114 const Utils::Compare::NaturalLessThan
<Qt::CaseInsensitive
> naturalLessThan
{};
116 // Adding files to the torrent
118 if (QFileInfo(m_params
.sourcePath
.data()).isFile())
120 lt::add_files(fs
, m_params
.sourcePath
.toString().toStdString(), fileFilter
);
124 // need to sort the file names by natural sort order
125 QStringList dirs
= {m_params
.sourcePath
.data()};
128 // libtorrent couldn't handle .lnk files on Windows
129 // Also, Windows users do not expect torrent creator to traverse into .lnk files so skip over them
130 const QDir::Filters dirFilters
{QDir::AllDirs
| QDir::NoDotAndDotDot
| QDir::NoSymLinks
};
132 const QDir::Filters dirFilters
{QDir::AllDirs
| QDir::NoDotAndDotDot
};
134 QDirIterator dirIter
{m_params
.sourcePath
.data(), dirFilters
, QDirIterator::Subdirectories
};
135 while (dirIter
.hasNext())
137 const QString filePath
= dirIter
.next();
138 dirs
.append(filePath
);
140 std::sort(dirs
.begin(), dirs
.end(), naturalLessThan
);
142 QStringList fileNames
;
143 QHash
<QString
, qint64
> fileSizeMap
;
145 for (const QString
&dir
: asConst(dirs
))
147 QStringList tmpNames
; // natural sort files within each dir
150 const QDir::Filters fileFilters
{QDir::Files
| QDir::NoSymLinks
};
152 const QDir::Filters fileFilters
{QDir::Files
};
154 QDirIterator fileIter
{dir
, fileFilters
};
155 while (fileIter
.hasNext())
157 const QFileInfo fileInfo
= fileIter
.nextFileInfo();
159 const Path relFilePath
= parentPath
.relativePathOf(Path(fileInfo
.filePath()));
160 tmpNames
.append(relFilePath
.toString());
161 fileSizeMap
[tmpNames
.last()] = fileInfo
.size();
164 std::sort(tmpNames
.begin(), tmpNames
.end(), naturalLessThan
);
165 fileNames
+= tmpNames
;
168 for (const QString
&fileName
: asConst(fileNames
))
169 fs
.add_file(fileName
.toStdString(), fileSizeMap
[fileName
]);
172 checkInterruptionRequested();
174 #ifdef QBT_USES_LIBTORRENT2
175 lt::create_torrent newTorrent
{fs
, m_params
.pieceSize
, toNativeTorrentFormatFlag(m_params
.torrentFormat
)};
177 lt::create_torrent
newTorrent(fs
, m_params
.pieceSize
, m_params
.paddedFileSizeLimit
178 , (m_params
.isAlignmentOptimized
? lt::create_torrent::optimize_alignment
: lt::create_flags_t
{}));
182 for (QString seed
: asConst(m_params
.urlSeeds
))
184 seed
= seed
.trimmed();
186 newTorrent
.add_url_seed(seed
.toStdString());
190 for (const QString
&tracker
: asConst(m_params
.trackers
))
192 if (tracker
.isEmpty())
195 newTorrent
.add_tracker(tracker
.trimmed().toStdString(), tier
);
198 // calculate the hash for all pieces
199 lt::set_piece_hashes(newTorrent
, parentPath
.toString().toStdString()
200 , [this, &newTorrent
](const lt::piece_index_t n
)
202 checkInterruptionRequested();
203 sendProgressSignal(LT::toUnderlyingType(n
), newTorrent
.num_pieces());
206 // Set qBittorrent as creator and add user comment to
207 // torrent_info structure
208 newTorrent
.set_creator("qBittorrent " QBT_VERSION
);
209 newTorrent
.set_comment(m_params
.comment
.toUtf8().constData());
211 newTorrent
.set_priv(m_params
.isPrivate
);
213 checkInterruptionRequested();
215 lt::entry entry
= newTorrent
.generate();
218 if (!m_params
.source
.isEmpty())
219 entry
["info"]["source"] = m_params
.source
.toStdString();
221 checkInterruptionRequested();
223 const auto result
= std::invoke([torrentFilePath
= m_params
.torrentFilePath
, entry
]() -> nonstd::expected
<Path
, QString
>
225 if (!torrentFilePath
.isValid())
226 return Utils::IO::saveToTempFile(entry
);
228 const nonstd::expected
<void, QString
> result
= Utils::IO::saveToFile(torrentFilePath
, entry
);
230 return nonstd::make_unexpected(result
.error());
232 return torrentFilePath
;
235 throw RuntimeError(result
.error());
237 const BitTorrent::TorrentCreatorResult creatorResult
239 .torrentFilePath
= result
.value(),
240 .savePath
= parentPath
,
241 .pieceSize
= newTorrent
.piece_length()
244 emit
progressUpdated(100);
245 emit
creationSuccess(creatorResult
);
247 catch (const RuntimeError
&err
)
249 emit
creationFailure(tr("Create new torrent file failed. Reason: %1.").arg(err
.message()));
251 catch (const std::exception
&err
)
253 emit
creationFailure(tr("Create new torrent file failed. Reason: %1.").arg(QString::fromLocal8Bit(err
.what())));
257 const TorrentCreatorParams
&TorrentCreator::params() const
262 #ifdef QBT_USES_LIBTORRENT2
263 int TorrentCreator::calculateTotalPieces(const Path
&inputPath
, const int pieceSize
, const TorrentFormat torrentFormat
)
265 int TorrentCreator::calculateTotalPieces(const Path
&inputPath
, const int pieceSize
, const bool isAlignmentOptimized
, const int paddedFileSizeLimit
)
268 if (inputPath
.isEmpty())
272 lt::add_files(fs
, inputPath
.toString().toStdString(), fileFilter
);
274 #ifdef QBT_USES_LIBTORRENT2
275 return lt::create_torrent
{fs
, pieceSize
, toNativeTorrentFormatFlag(torrentFormat
)}.num_pieces();
277 return lt::create_torrent(fs
, pieceSize
, paddedFileSizeLimit
278 , (isAlignmentOptimized
? lt::create_torrent::optimize_alignment
: lt::create_flags_t
{})).num_pieces();