2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2017 Mike Tzou (Chocobo1)
4 * Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
30 #include "torrentcreatordialog.h"
32 #include <QCloseEvent>
33 #include <QFileDialog>
34 #include <QMessageBox>
38 #include "base/bittorrent/session.h"
39 #include "base/bittorrent/torrentdescriptor.h"
40 #include "base/global.h"
41 #include "base/utils/fs.h"
42 #include "base/utils/misc.h"
43 #include "ui_torrentcreatordialog.h"
46 #define SETTINGS_KEY(name) u"TorrentCreator/" name
50 // When the root file/directory of the created torrent is a symlink, we want to keep the symlink name in the torrent.
51 // On Windows, however, QFileDialog::DontResolveSymlinks disables shortcuts (.lnk files) expansion, making it impossible to pick a file if its path contains a shortcut.
52 // As of NTFS symlinks, they don't seem to be resolved anyways.
54 const QFileDialog::Options FILE_DIALOG_OPTIONS
{QFileDialog::DontResolveSymlinks
};
56 const QFileDialog::Options FILE_DIALOG_OPTIONS
{};
60 TorrentCreatorDialog::TorrentCreatorDialog(QWidget
*parent
, const Path
&defaultPath
)
62 , m_ui(new Ui::TorrentCreatorDialog
)
64 , m_storeDialogSize(SETTINGS_KEY(u
"Size"_s
))
65 , m_storePieceSize(SETTINGS_KEY(u
"PieceSize"_s
))
66 , m_storePrivateTorrent(SETTINGS_KEY(u
"PrivateTorrent"_s
))
67 , m_storeStartSeeding(SETTINGS_KEY(u
"StartSeeding"_s
))
68 , m_storeIgnoreRatio(SETTINGS_KEY(u
"IgnoreRatio"_s
))
69 #ifdef QBT_USES_LIBTORRENT2
70 , m_storeTorrentFormat(SETTINGS_KEY(u
"TorrentFormat"_s
))
72 , m_storeOptimizeAlignment(SETTINGS_KEY(u
"OptimizeAlignment"_s
))
73 , m_paddedFileSizeLimit(SETTINGS_KEY(u
"PaddedFileSizeLimit"_s
))
75 , m_storeLastAddPath(SETTINGS_KEY(u
"LastAddPath"_s
))
76 , m_storeTrackerList(SETTINGS_KEY(u
"TrackerList"_s
))
77 , m_storeWebSeedList(SETTINGS_KEY(u
"WebSeedList"_s
))
78 , m_storeComments(SETTINGS_KEY(u
"Comments"_s
))
79 , m_storeLastSavePath(SETTINGS_KEY(u
"LastSavePath"_s
))
80 , m_storeSource(SETTINGS_KEY(u
"Source"_s
))
84 m_ui
->comboPieceSize
->addItem(tr("Auto"), 0);
85 #ifdef QBT_USES_LIBTORRENT2
86 for (int i
= 4; i
<= 18; ++i
)
88 for (int i
= 4; i
<= 17; ++i
)
91 const int size
= 1024 << i
;
92 const QString displaySize
= Utils::Misc::friendlyUnit(size
, false, 0);
93 m_ui
->comboPieceSize
->addItem(displaySize
, size
);
96 m_ui
->buttonBox
->button(QDialogButtonBox::Ok
)->setText(tr("Create Torrent"));
97 m_ui
->textInputPath
->setMode(FileSystemPathEdit::Mode::ReadOnly
);
99 connect(m_ui
->addFileButton
, &QPushButton::clicked
, this, &TorrentCreatorDialog::onAddFileButtonClicked
);
100 connect(m_ui
->addFolderButton
, &QPushButton::clicked
, this, &TorrentCreatorDialog::onAddFolderButtonClicked
);
101 connect(m_ui
->buttonBox
, &QDialogButtonBox::accepted
, this, &TorrentCreatorDialog::onCreateButtonClicked
);
102 connect(m_ui
->buttonBox
, &QDialogButtonBox::rejected
, this, &QDialog::reject
);
103 connect(m_ui
->buttonCalcTotalPieces
, &QPushButton::clicked
, this, &TorrentCreatorDialog::updatePiecesCount
);
104 connect(m_ui
->checkStartSeeding
, &QCheckBox::clicked
, m_ui
->checkIgnoreShareLimits
, &QWidget::setEnabled
);
107 updateInputPath(defaultPath
);
109 m_threadPool
.setMaxThreadCount(1);
111 #ifdef QBT_USES_LIBTORRENT2
112 m_ui
->checkOptimizeAlignment
->hide();
114 m_ui
->widgetTorrentFormat
->hide();
118 TorrentCreatorDialog::~TorrentCreatorDialog()
125 void TorrentCreatorDialog::updateInputPath(const Path
&path
)
127 if (path
.isEmpty()) return;
128 m_ui
->textInputPath
->setSelectedPath(path
);
129 updateProgressBar(0);
132 void TorrentCreatorDialog::onAddFolderButtonClicked()
134 const QString oldPath
= m_ui
->textInputPath
->selectedPath().data();
135 const Path path
{QFileDialog::getExistingDirectory(this, tr("Select folder")
136 , oldPath
, (QFileDialog::ShowDirsOnly
| FILE_DIALOG_OPTIONS
))};
137 updateInputPath(path
);
140 void TorrentCreatorDialog::onAddFileButtonClicked()
142 const QString oldPath
= m_ui
->textInputPath
->selectedPath().data();
143 const Path path
{QFileDialog::getOpenFileName(this, tr("Select file"), oldPath
, QString(), nullptr, FILE_DIALOG_OPTIONS
)};
144 updateInputPath(path
);
147 int TorrentCreatorDialog::getPieceSize() const
149 return m_ui
->comboPieceSize
->currentData().toInt();
152 #ifdef QBT_USES_LIBTORRENT2
153 BitTorrent::TorrentFormat
TorrentCreatorDialog::getTorrentFormat() const
155 switch (m_ui
->comboTorrentFormat
->currentIndex())
158 return BitTorrent::TorrentFormat::V2
;
160 return BitTorrent::TorrentFormat::Hybrid
;
162 return BitTorrent::TorrentFormat::V1
;
164 return BitTorrent::TorrentFormat::Hybrid
;
167 int TorrentCreatorDialog::getPaddedFileSizeLimit() const
169 const int value
= m_ui
->spinPaddedFileSizeLimit
->value();
170 return ((value
>= 0) ? (value
* 1024) : -1);
174 void TorrentCreatorDialog::dropEvent(QDropEvent
*event
)
176 event
->acceptProposedAction();
178 if (event
->mimeData()->hasUrls())
180 // only take the first one
181 const QUrl firstItem
= event
->mimeData()->urls().first();
183 (firstItem
.scheme().compare(u
"file", Qt::CaseInsensitive
) == 0)
184 ? firstItem
.toLocalFile() : firstItem
.toString()
186 updateInputPath(path
);
190 void TorrentCreatorDialog::dragEnterEvent(QDragEnterEvent
*event
)
192 if (event
->mimeData()->hasFormat(u
"text/plain"_s
) || event
->mimeData()->hasFormat(u
"text/uri-list"_s
))
193 event
->acceptProposedAction();
196 // Main function that create a .torrent file
197 void TorrentCreatorDialog::onCreateButtonClicked()
200 // Resolve the path in case it contains a shortcut (otherwise, the following usages will consider it invalid)
201 const Path inputPath
= Utils::Fs::toCanonicalPath(m_ui
->textInputPath
->selectedPath());
203 const Path inputPath
= m_ui
->textInputPath
->selectedPath();
207 if (!Utils::Fs::isReadable(inputPath
))
209 QMessageBox::critical(this, tr("Torrent creation failed"), tr("Reason: Path to file/folder is not readable."));
214 const Path lastSavePath
= (m_storeLastSavePath
.get(Utils::Fs::homePath()) / Path(inputPath
.filename() + u
".torrent"));
215 Path destPath
{QFileDialog::getSaveFileName(this, tr("Select where to save the new torrent"), lastSavePath
.data(), tr("Torrent Files (*.torrent)"))};
216 if (destPath
.isEmpty())
218 if (!destPath
.hasExtension(TORRENT_FILE_EXTENSION
))
219 destPath
+= TORRENT_FILE_EXTENSION
;
220 m_storeLastSavePath
= destPath
.parentPath();
222 // Disable dialog & set busy cursor
223 setInteractionEnabled(false);
224 setCursor(QCursor(Qt::WaitCursor
));
226 const QStringList trackers
= m_ui
->trackersList
->toPlainText().trimmed()
227 .replace(QRegularExpression(u
"\n\n[\n]+"_s
), u
"\n\n"_s
).split(u
'\n');
228 const BitTorrent::TorrentCreatorParams params
230 m_ui
->checkPrivate
->isChecked()
231 #ifdef QBT_USES_LIBTORRENT2
234 , m_ui
->checkOptimizeAlignment
->isChecked()
235 , getPaddedFileSizeLimit()
240 , m_ui
->txtComment
->toPlainText()
241 , m_ui
->lineEditSource
->text()
243 , m_ui
->URLSeedsList
->toPlainText().split(u
'\n', Qt::SkipEmptyParts
)
246 auto *torrentCreator
= new BitTorrent::TorrentCreator(params
);
247 connect(this, &QDialog::rejected
, torrentCreator
, &BitTorrent::TorrentCreator::requestInterruption
);
248 connect(torrentCreator
, &BitTorrent::TorrentCreator::creationSuccess
, this, &TorrentCreatorDialog::handleCreationSuccess
);
249 connect(torrentCreator
, &BitTorrent::TorrentCreator::creationFailure
, this, &TorrentCreatorDialog::handleCreationFailure
);
250 connect(torrentCreator
, &BitTorrent::TorrentCreator::updateProgress
, this, &TorrentCreatorDialog::updateProgressBar
);
252 // run the torrentCreator in a thread
253 m_threadPool
.start(torrentCreator
);
256 void TorrentCreatorDialog::handleCreationFailure(const QString
&msg
)
258 // Remove busy cursor
259 setCursor(QCursor(Qt::ArrowCursor
));
260 QMessageBox::information(this, tr("Torrent creation failed"), tr("Reason: %1").arg(msg
));
261 setInteractionEnabled(true);
264 void TorrentCreatorDialog::handleCreationSuccess(const Path
&path
, const Path
&branchPath
)
266 setCursor(QCursor(Qt::ArrowCursor
));
267 setInteractionEnabled(true);
269 QMessageBox::information(this, tr("Torrent creator")
270 , u
"%1\n%2"_s
.arg(tr("Torrent created:"), path
.toString()));
272 if (m_ui
->checkStartSeeding
->isChecked())
274 const auto loadResult
= BitTorrent::TorrentDescriptor::loadFromFile(path
);
277 const QString message
= tr("Add torrent to transfer list failed.") + u
'\n' + tr("Reason: \"%1\"").arg(loadResult
.error());
278 QMessageBox::critical(this, tr("Add torrent failed"), message
);
282 BitTorrent::AddTorrentParams params
;
283 params
.savePath
= branchPath
;
284 params
.skipChecking
= true;
285 if (m_ui
->checkIgnoreShareLimits
->isChecked())
287 params
.ratioLimit
= BitTorrent::Torrent::NO_RATIO_LIMIT
;
288 params
.seedingTimeLimit
= BitTorrent::Torrent::NO_SEEDING_TIME_LIMIT
;
289 params
.inactiveSeedingTimeLimit
= BitTorrent::Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT
;
291 params
.useAutoTMM
= false; // otherwise if it is on by default, it will overwrite `savePath` to the default save path
293 BitTorrent::Session::instance()->addTorrent(loadResult
.value(), params
);
297 void TorrentCreatorDialog::updateProgressBar(int progress
)
299 m_ui
->progressBar
->setValue(progress
);
302 void TorrentCreatorDialog::updatePiecesCount()
304 const Path path
= m_ui
->textInputPath
->selectedPath();
305 #ifdef QBT_USES_LIBTORRENT2
306 const int count
= BitTorrent::TorrentCreator::calculateTotalPieces(
307 path
, getPieceSize(), getTorrentFormat());
309 const bool isAlignmentOptimized
= m_ui
->checkOptimizeAlignment
->isChecked();
310 const int count
= BitTorrent::TorrentCreator::calculateTotalPieces(path
311 , getPieceSize(), isAlignmentOptimized
, getPaddedFileSizeLimit());
313 m_ui
->labelTotalPieces
->setText(QString::number(count
));
316 void TorrentCreatorDialog::setInteractionEnabled(const bool enabled
) const
318 m_ui
->textInputPath
->setEnabled(enabled
);
319 m_ui
->addFileButton
->setEnabled(enabled
);
320 m_ui
->addFolderButton
->setEnabled(enabled
);
321 m_ui
->trackersList
->setEnabled(enabled
);
322 m_ui
->URLSeedsList
->setEnabled(enabled
);
323 m_ui
->txtComment
->setEnabled(enabled
);
324 m_ui
->lineEditSource
->setEnabled(enabled
);
325 m_ui
->comboPieceSize
->setEnabled(enabled
);
326 m_ui
->buttonCalcTotalPieces
->setEnabled(enabled
);
327 m_ui
->checkPrivate
->setEnabled(enabled
);
328 m_ui
->checkStartSeeding
->setEnabled(enabled
);
329 m_ui
->buttonBox
->button(QDialogButtonBox::Ok
)->setEnabled(enabled
);
330 m_ui
->checkIgnoreShareLimits
->setEnabled(enabled
&& m_ui
->checkStartSeeding
->isChecked());
331 #ifdef QBT_USES_LIBTORRENT2
332 m_ui
->widgetTorrentFormat
->setEnabled(enabled
);
334 m_ui
->checkOptimizeAlignment
->setEnabled(enabled
);
335 m_ui
->spinPaddedFileSizeLimit
->setEnabled(enabled
);
339 void TorrentCreatorDialog::saveSettings()
341 m_storeLastAddPath
= m_ui
->textInputPath
->selectedPath();
343 m_storePieceSize
= m_ui
->comboPieceSize
->currentIndex();
344 m_storePrivateTorrent
= m_ui
->checkPrivate
->isChecked();
345 m_storeStartSeeding
= m_ui
->checkStartSeeding
->isChecked();
346 m_storeIgnoreRatio
= m_ui
->checkIgnoreShareLimits
->isChecked();
347 #ifdef QBT_USES_LIBTORRENT2
348 m_storeTorrentFormat
= m_ui
->comboTorrentFormat
->currentIndex();
350 m_storeOptimizeAlignment
= m_ui
->checkOptimizeAlignment
->isChecked();
351 m_paddedFileSizeLimit
= m_ui
->spinPaddedFileSizeLimit
->value();
354 m_storeTrackerList
= m_ui
->trackersList
->toPlainText();
355 m_storeWebSeedList
= m_ui
->URLSeedsList
->toPlainText();
356 m_storeComments
= m_ui
->txtComment
->toPlainText();
357 m_storeSource
= m_ui
->lineEditSource
->text();
359 m_storeDialogSize
= size();
362 void TorrentCreatorDialog::loadSettings()
364 m_ui
->textInputPath
->setSelectedPath(m_storeLastAddPath
.get(Utils::Fs::homePath()));
366 m_ui
->comboPieceSize
->setCurrentIndex(m_storePieceSize
);
367 m_ui
->checkPrivate
->setChecked(m_storePrivateTorrent
);
368 m_ui
->checkStartSeeding
->setChecked(m_storeStartSeeding
);
369 m_ui
->checkIgnoreShareLimits
->setChecked(m_storeIgnoreRatio
);
370 m_ui
->checkIgnoreShareLimits
->setEnabled(m_ui
->checkStartSeeding
->isChecked());
371 #ifdef QBT_USES_LIBTORRENT2
372 m_ui
->comboTorrentFormat
->setCurrentIndex(m_storeTorrentFormat
.get(1));
374 m_ui
->checkOptimizeAlignment
->setChecked(m_storeOptimizeAlignment
.get(true));
375 m_ui
->spinPaddedFileSizeLimit
->setValue(m_paddedFileSizeLimit
.get(-1));
378 m_ui
->trackersList
->setPlainText(m_storeTrackerList
);
379 m_ui
->URLSeedsList
->setPlainText(m_storeWebSeedList
);
380 m_ui
->txtComment
->setPlainText(m_storeComments
);
381 m_ui
->lineEditSource
->setText(m_storeSource
);
383 if (const QSize dialogSize
= m_storeDialogSize
; dialogSize
.isValid())