Enable customizing the save statistics time interval
[qBittorrent.git] / src / base / bittorrent / torrentcreator.cpp
blob3cd26ba7c03ae337450eeb8f7a5391b13a51b67a
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 <QDirIterator>
40 #include <QFileInfo>
41 #include <QHash>
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"
50 namespace
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:
67 return {};
68 case BitTorrent::TorrentFormat::V2:
69 return lt::create_torrent::v2_only;
71 return {};
73 #endif
76 using namespace BitTorrent;
78 TorrentCreator::TorrentCreator(const TorrentCreatorParams &params, QObject *parent)
79 : QObject(parent)
80 , m_params {params}
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()
107 emit started();
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
116 lt::file_storage fs;
117 if (QFileInfo(m_params.sourcePath.data()).isFile())
119 lt::add_files(fs, m_params.sourcePath.toString().toStdString(), fileFilter);
121 else
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)};
163 #else
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 {}));
166 #endif
168 // Add url seeds
169 for (QString seed : asConst(m_params.urlSeeds))
171 seed = seed.trimmed();
172 if (!seed.isEmpty())
173 newTorrent.add_url_seed(seed.toStdString());
176 int tier = 0;
177 for (const QString &tracker : asConst(m_params.trackers))
179 if (tracker.isEmpty())
180 ++tier;
181 else
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());
197 // Is private ?
198 newTorrent.set_priv(m_params.isPrivate);
200 checkInterruptionRequested();
202 lt::entry entry = newTorrent.generate();
204 // add source field
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);
216 if (!result)
217 return nonstd::make_unexpected(result.error());
219 return torrentFilePath;
221 if (!result)
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
246 return m_params;
249 #ifdef QBT_USES_LIBTORRENT2
250 int TorrentCreator::calculateTotalPieces(const Path &inputPath, const int pieceSize, const TorrentFormat torrentFormat)
251 #else
252 int TorrentCreator::calculateTotalPieces(const Path &inputPath, const int pieceSize, const bool isAlignmentOptimized, const int paddedFileSizeLimit)
253 #endif
255 if (inputPath.isEmpty())
256 return 0;
258 lt::file_storage fs;
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();
263 #else
264 return lt::create_torrent(fs, pieceSize, paddedFileSizeLimit
265 , (isAlignmentOptimized ? lt::create_torrent::optimize_alignment : lt::create_flags_t {})).num_pieces();
266 #endif