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 <QDirIterator>
43 #include "base/exceptions.h"
44 #include "base/global.h"
45 #include "base/utils/compare.h"
46 #include "base/utils/io.h"
47 #include "base/version.h"
48 #include "lttypecast.h"
52 // do not include files and folders whose
53 // name starts with a .
54 bool fileFilter(const std::string
&f
)
56 return !Path(f
).filename().startsWith(u
'.');
59 #ifdef QBT_USES_LIBTORRENT2
60 lt::create_flags_t
toNativeTorrentFormatFlag(const BitTorrent::TorrentFormat torrentFormat
)
62 switch (torrentFormat
)
64 case BitTorrent::TorrentFormat::V1
:
65 return lt::create_torrent::v1_only
;
66 case BitTorrent::TorrentFormat::Hybrid
:
68 case BitTorrent::TorrentFormat::V2
:
69 return lt::create_torrent::v2_only
;
76 using namespace BitTorrent
;
78 TorrentCreator::TorrentCreator(const TorrentCreatorParams
¶ms
, QObject
*parent
)
84 void TorrentCreator::sendProgressSignal(int currentPieceIdx
, int totalPieces
)
86 emit
progressUpdated(static_cast<int>((currentPieceIdx
* 100.) / totalPieces
));
89 void TorrentCreator::checkInterruptionRequested() const
91 if (isInterruptionRequested())
92 throw RuntimeError(tr("Operation aborted"));
95 void TorrentCreator::requestInterruption()
97 m_interruptionRequested
.store(true, std::memory_order_relaxed
);
100 bool TorrentCreator::isInterruptionRequested() const
102 return m_interruptionRequested
.load(std::memory_order_relaxed
);
105 void TorrentCreator::run()
108 emit
progressUpdated(0);
112 const Path parentPath
= m_params
.sourcePath
.parentPath();
113 const Utils::Compare::NaturalLessThan
<Qt::CaseInsensitive
> naturalLessThan
{};
115 // Adding files to the torrent
117 if (QFileInfo(m_params
.sourcePath
.data()).isFile())
119 lt::add_files(fs
, m_params
.sourcePath
.toString().toStdString(), fileFilter
);
123 // need to sort the file names by natural sort order
124 QStringList dirs
= {m_params
.sourcePath
.data()};
126 QDirIterator dirIter
{m_params
.sourcePath
.data(), (QDir::AllDirs
| QDir::NoDotAndDotDot
), QDirIterator::Subdirectories
};
127 while (dirIter
.hasNext())
129 const QString filePath
= dirIter
.next();
130 dirs
.append(filePath
);
132 std::sort(dirs
.begin(), dirs
.end(), naturalLessThan
);
134 QStringList fileNames
;
135 QHash
<QString
, qint64
> fileSizeMap
;
137 for (const QString
&dir
: asConst(dirs
))
139 QStringList tmpNames
; // natural sort files within each dir
141 QDirIterator fileIter
{dir
, QDir::Files
};
142 while (fileIter
.hasNext())
144 const QFileInfo fileInfo
= fileIter
.nextFileInfo();
146 const Path relFilePath
= parentPath
.relativePathOf(Path(fileInfo
.filePath()));
147 tmpNames
.append(relFilePath
.toString());
148 fileSizeMap
[tmpNames
.last()] = fileInfo
.size();
151 std::sort(tmpNames
.begin(), tmpNames
.end(), naturalLessThan
);
152 fileNames
+= tmpNames
;
155 for (const QString
&fileName
: asConst(fileNames
))
156 fs
.add_file(fileName
.toStdString(), fileSizeMap
[fileName
]);
159 checkInterruptionRequested();
161 #ifdef QBT_USES_LIBTORRENT2
162 lt::create_torrent newTorrent
{fs
, m_params
.pieceSize
, toNativeTorrentFormatFlag(m_params
.torrentFormat
)};
164 lt::create_torrent
newTorrent(fs
, m_params
.pieceSize
, m_params
.paddedFileSizeLimit
165 , (m_params
.isAlignmentOptimized
? lt::create_torrent::optimize_alignment
: lt::create_flags_t
{}));
169 for (QString seed
: asConst(m_params
.urlSeeds
))
171 seed
= seed
.trimmed();
173 newTorrent
.add_url_seed(seed
.toStdString());
177 for (const QString
&tracker
: asConst(m_params
.trackers
))
179 if (tracker
.isEmpty())
182 newTorrent
.add_tracker(tracker
.trimmed().toStdString(), tier
);
185 // calculate the hash for all pieces
186 lt::set_piece_hashes(newTorrent
, parentPath
.toString().toStdString()
187 , [this, &newTorrent
](const lt::piece_index_t n
)
189 checkInterruptionRequested();
190 sendProgressSignal(LT::toUnderlyingType(n
), newTorrent
.num_pieces());
193 // Set qBittorrent as creator and add user comment to
194 // torrent_info structure
195 newTorrent
.set_creator("qBittorrent " QBT_VERSION
);
196 newTorrent
.set_comment(m_params
.comment
.toUtf8().constData());
198 newTorrent
.set_priv(m_params
.isPrivate
);
200 checkInterruptionRequested();
202 lt::entry entry
= newTorrent
.generate();
205 if (!m_params
.source
.isEmpty())
206 entry
["info"]["source"] = m_params
.source
.toStdString();
208 checkInterruptionRequested();
210 const auto result
= std::invoke([torrentFilePath
= m_params
.torrentFilePath
, entry
]() -> nonstd::expected
<Path
, QString
>
212 if (!torrentFilePath
.isValid())
213 return Utils::IO::saveToTempFile(entry
);
215 const nonstd::expected
<void, QString
> result
= Utils::IO::saveToFile(torrentFilePath
, entry
);
217 return nonstd::make_unexpected(result
.error());
219 return torrentFilePath
;
222 throw RuntimeError(result
.error());
224 const BitTorrent::TorrentCreatorResult creatorResult
226 .torrentFilePath
= result
.value(),
227 .savePath
= parentPath
,
228 .pieceSize
= newTorrent
.piece_length()
231 emit
progressUpdated(100);
232 emit
creationSuccess(creatorResult
);
234 catch (const RuntimeError
&err
)
236 emit
creationFailure(tr("Create new torrent file failed. Reason: %1.").arg(err
.message()));
238 catch (const std::exception
&err
)
240 emit
creationFailure(tr("Create new torrent file failed. Reason: %1.").arg(QString::fromLocal8Bit(err
.what())));
244 const TorrentCreatorParams
&TorrentCreator::params() const
249 #ifdef QBT_USES_LIBTORRENT2
250 int TorrentCreator::calculateTotalPieces(const Path
&inputPath
, const int pieceSize
, const TorrentFormat torrentFormat
)
252 int TorrentCreator::calculateTotalPieces(const Path
&inputPath
, const int pieceSize
, const bool isAlignmentOptimized
, const int paddedFileSizeLimit
)
255 if (inputPath
.isEmpty())
259 lt::add_files(fs
, inputPath
.toString().toStdString(), fileFilter
);
261 #ifdef QBT_USES_LIBTORRENT2
262 return lt::create_torrent
{fs
, pieceSize
, toNativeTorrentFormatFlag(torrentFormat
)}.num_pieces();
264 return lt::create_torrent(fs
, pieceSize
, paddedFileSizeLimit
265 , (isAlignmentOptimized
? lt::create_torrent::optimize_alignment
: lt::create_flags_t
{})).num_pieces();