1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
4 *
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.
9 *
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.
14 *
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.
18 *
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.
27 */
28
29 #include "torrentcreatorthread.h"
30
31 #include <fstream>
32
33 #include <libtorrent/bencode.hpp>
34 #include <libtorrent/create_torrent.hpp>
35 #include <libtorrent/file_storage.hpp>
36 #include <libtorrent/torrent_info.hpp>
37
38 #include <QDirIterator>
39 #include <QFile>
40 #include <QFileInfo>
41 #include <QHash>
42
43 #include "base/exceptions.h"
44 #include "base/global.h"
45 #include "base/utils/fs.h"
46 #include "base/utils/io.h"
47 #include "base/utils/string.h"
48 #include "base/version.h"
49 #include "ltunderlyingtype.h"
50
51 namespace
52 {
53 // do not include files and folders whose
54 // name starts with a .
fileFilter(const std::string & f)55 bool fileFilter(const std::string &f)
56 {
57 return !Utils::Fs::fileName(QString::fromStdString(f)).startsWith('.');
58 }
59
60 #if (LIBTORRENT_VERSION_NUM >= 20000)
toNativeTorrentFormatFlag(const BitTorrent::TorrentFormat torrentFormat)61 lt::create_flags_t toNativeTorrentFormatFlag(const BitTorrent::TorrentFormat torrentFormat)
62 {
63 switch (torrentFormat)
64 {
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;
71 }
72 return {};
73 }
74 #endif
75 }
76
77 using namespace BitTorrent;
78
TorrentCreatorThread(QObject * parent)79 TorrentCreatorThread::TorrentCreatorThread(QObject *parent)
80 : QThread(parent)
81 {
82 }
83
~TorrentCreatorThread()84 TorrentCreatorThread::~TorrentCreatorThread()
85 {
86 requestInterruption();
87 wait();
88 }
89
create(const TorrentCreatorParams & params)90 void TorrentCreatorThread::create(const TorrentCreatorParams ¶ms)
91 {
92 m_params = params;
93 start();
94 }
95
sendProgressSignal(int currentPieceIdx,int totalPieces)96 void TorrentCreatorThread::sendProgressSignal(int currentPieceIdx, int totalPieces)
97 {
98 emit updateProgress(static_cast<int>((currentPieceIdx * 100.) / totalPieces));
99 }
100
run()101 void TorrentCreatorThread::run()
102 {
103 emit updateProgress(0);
104
105 try
106 {
107 const QString parentPath = Utils::Fs::branchPath(m_params.inputPath) + '/';
108
109 // Adding files to the torrent
110 lt::file_storage fs;
111 if (QFileInfo(m_params.inputPath).isFile())
112 {
113 lt::add_files(fs, Utils::Fs::toNativePath(m_params.inputPath).toStdString(), fileFilter);
114 }
115 else
116 {
117 // need to sort the file names by natural sort order
118 QStringList dirs = {m_params.inputPath};
119
120 QDirIterator dirIter(m_params.inputPath, (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories);
121 while (dirIter.hasNext())
122 {
123 dirIter.next();
124 dirs += dirIter.filePath();
125 }
126 std::sort(dirs.begin(), dirs.end(), Utils::String::naturalLessThan<Qt::CaseInsensitive>);
127
128 QStringList fileNames;
129 QHash<QString, qint64> fileSizeMap;
130
131 for (const auto &dir : asConst(dirs))
132 {
133 QStringList tmpNames; // natural sort files within each dir
134
135 QDirIterator fileIter(dir, QDir::Files);
136 while (fileIter.hasNext())
137 {
138 fileIter.next();
139
140 const QString relFilePath = fileIter.filePath().mid(parentPath.length());
141 tmpNames += relFilePath;
142 fileSizeMap[relFilePath] = fileIter.fileInfo().size();
143 }
144
145 std::sort(tmpNames.begin(), tmpNames.end(), Utils::String::naturalLessThan<Qt::CaseInsensitive>);
146 fileNames += tmpNames;
147 }
148
149 for (const auto &fileName : asConst(fileNames))
150 fs.add_file(fileName.toStdString(), fileSizeMap[fileName]);
151 }
152
153 if (isInterruptionRequested()) return;
154
155 #if (LIBTORRENT_VERSION_NUM >= 20000)
156 lt::create_torrent newTorrent {fs, m_params.pieceSize, toNativeTorrentFormatFlag(m_params.torrentFormat)};
157 #else
158 lt::create_torrent newTorrent(fs, m_params.pieceSize, m_params.paddedFileSizeLimit
159 , (m_params.isAlignmentOptimized ? lt::create_torrent::optimize_alignment : lt::create_flags_t {}));
160 #endif
161
162 // Add url seeds
163 for (QString seed : asConst(m_params.urlSeeds))
164 {
165 seed = seed.trimmed();
166 if (!seed.isEmpty())
167 newTorrent.add_url_seed(seed.toStdString());
168 }
169
170 int tier = 0;
171 for (const QString &tracker : asConst(m_params.trackers))
172 {
173 if (tracker.isEmpty())
174 ++tier;
175 else
176 newTorrent.add_tracker(tracker.trimmed().toStdString(), tier);
177 }
178
179 // calculate the hash for all pieces
180 lt::set_piece_hashes(newTorrent, Utils::Fs::toNativePath(parentPath).toStdString()
181 , [this, &newTorrent](const lt::piece_index_t n)
182 {
183 if (isInterruptionRequested())
184 throw RuntimeError {tr("Create new torrent aborted.")};
185
186 sendProgressSignal(static_cast<LTUnderlyingType<lt::piece_index_t>>(n), newTorrent.num_pieces());
187 });
188
189 // Set qBittorrent as creator and add user comment to
190 // torrent_info structure
191 newTorrent.set_creator("qBittorrent " QBT_VERSION);
192 newTorrent.set_comment(m_params.comment.toUtf8().constData());
193 // Is private ?
194 newTorrent.set_priv(m_params.isPrivate);
195
196 if (isInterruptionRequested()) return;
197
198 lt::entry entry = newTorrent.generate();
199
200 // add source field
201 if (!m_params.source.isEmpty())
202 entry["info"]["source"] = m_params.source.toStdString();
203
204 if (isInterruptionRequested()) return;
205
206 // create the torrent
207 QFile outfile {m_params.savePath};
208 if (!outfile.open(QIODevice::WriteOnly))
209 {
210 throw RuntimeError {tr("Create new torrent file failed. Reason: %1")
211 .arg(outfile.errorString())};
212 }
213
214 if (isInterruptionRequested()) return;
215
216 lt::bencode(Utils::IO::FileDeviceOutputIterator {outfile}, entry);
217 if (outfile.error() != QFileDevice::NoError)
218 {
219 throw RuntimeError {tr("Create new torrent file failed. Reason: %1")
220 .arg(outfile.errorString())};
221 }
222 outfile.close();
223
224 emit updateProgress(100);
225 emit creationSuccess(m_params.savePath, parentPath);
226 }
227 catch (const RuntimeError &err)
228 {
229 emit creationFailure(tr("Create new torrent file failed. Reason: %1.").arg(err.message()));
230 }
231 catch (const std::exception &err)
232 {
233 emit creationFailure(tr("Create new torrent file failed. Reason: %1.").arg(QString::fromLocal8Bit(err.what())));
234 }
235 }
236
237 #if (LIBTORRENT_VERSION_NUM >= 20000)
calculateTotalPieces(const QString & inputPath,const int pieceSize,const TorrentFormat torrentFormat)238 int TorrentCreatorThread::calculateTotalPieces(const QString &inputPath, const int pieceSize, const TorrentFormat torrentFormat)
239 #else
240 int TorrentCreatorThread::calculateTotalPieces(const QString &inputPath, const int pieceSize, const bool isAlignmentOptimized, const int paddedFileSizeLimit)
241 #endif
242 {
243 if (inputPath.isEmpty())
244 return 0;
245
246 lt::file_storage fs;
247 lt::add_files(fs, Utils::Fs::toNativePath(inputPath).toStdString(), fileFilter);
248
249 #if (LIBTORRENT_VERSION_NUM >= 20000)
250 return lt::create_torrent {fs, pieceSize, toNativeTorrentFormatFlag(torrentFormat)}.num_pieces();
251 #else
252 return lt::create_torrent(fs, pieceSize, paddedFileSizeLimit
253 , (isAlignmentOptimized ? lt::create_torrent::optimize_alignment : lt::create_flags_t {})).num_pieces();
254 #endif
255 }
256