WebUI: disallow unnecessary quotes in property name
[qBittorrent.git] / src / base / bittorrent / torrentcreator.cpp
blob727e9a9dc3829bd88b976eb9152e002d0b5a99cd
1 /*
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"
33 #include <functional>
35 #include <libtorrent/create_torrent.hpp>
36 #include <libtorrent/file_storage.hpp>
37 #include <libtorrent/torrent_info.hpp>
39 #include <QtSystemDetection>
40 #include <QDirIterator>
41 #include <QFileInfo>
42 #include <QHash>
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"
51 namespace
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:
68 return {};
69 case BitTorrent::TorrentFormat::V2:
70 return lt::create_torrent::v2_only;
72 return {};
74 #endif
77 using namespace BitTorrent;
79 TorrentCreator::TorrentCreator(const TorrentCreatorParams &params, QObject *parent)
80 : QObject(parent)
81 , m_params {params}
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()
108 emit started();
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
117 lt::file_storage fs;
118 if (QFileInfo(m_params.sourcePath.data()).isFile())
120 lt::add_files(fs, m_params.sourcePath.toString().toStdString(), fileFilter);
122 else
124 // need to sort the file names by natural sort order
125 QStringList dirs = {m_params.sourcePath.data()};
127 #ifdef Q_OS_WIN
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};
131 #else
132 const QDir::Filters dirFilters {QDir::AllDirs | QDir::NoDotAndDotDot};
133 #endif
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
149 #ifdef Q_OS_WIN
150 const QDir::Filters fileFilters {QDir::Files | QDir::NoSymLinks};
151 #else
152 const QDir::Filters fileFilters {QDir::Files};
153 #endif
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)};
176 #else
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 {}));
179 #endif
181 // Add url seeds
182 for (QString seed : asConst(m_params.urlSeeds))
184 seed = seed.trimmed();
185 if (!seed.isEmpty())
186 newTorrent.add_url_seed(seed.toStdString());
189 int tier = 0;
190 for (const QString &tracker : asConst(m_params.trackers))
192 if (tracker.isEmpty())
193 ++tier;
194 else
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());
210 // Is private ?
211 newTorrent.set_priv(m_params.isPrivate);
213 checkInterruptionRequested();
215 lt::entry entry = newTorrent.generate();
217 // add source field
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);
229 if (!result)
230 return nonstd::make_unexpected(result.error());
232 return torrentFilePath;
234 if (!result)
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
259 return m_params;
262 #ifdef QBT_USES_LIBTORRENT2
263 int TorrentCreator::calculateTotalPieces(const Path &inputPath, const int pieceSize, const TorrentFormat torrentFormat)
264 #else
265 int TorrentCreator::calculateTotalPieces(const Path &inputPath, const int pieceSize, const bool isAlignmentOptimized, const int paddedFileSizeLimit)
266 #endif
268 if (inputPath.isEmpty())
269 return 0;
271 lt::file_storage fs;
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();
276 #else
277 return lt::create_torrent(fs, pieceSize, paddedFileSizeLimit
278 , (isAlignmentOptimized ? lt::create_torrent::optimize_alignment : lt::create_flags_t {})).num_pieces();
279 #endif